diff --git a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java index fa206cb0f63..24bb522b2ee 100644 --- a/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/DragCardGrid.java @@ -1,32 +1,26 @@ package mage.client.cards; import mage.cards.MageCard; -import mage.cards.decks.Deck; -import mage.cards.decks.importer.DeckImporterUtil; -import mage.client.MageFrame; +import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLayout; import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.*; import mage.client.util.Event; import mage.constants.CardType; -import mage.game.GameException; import mage.view.CardView; import mage.view.CardsView; import org.apache.log4j.Logger; import org.mage.card.arcane.CardRenderer; import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.PopupMenuEvent; -import javax.swing.event.PopupMenuListener; -import javax.swing.filechooser.FileFilter; import java.awt.*; import java.awt.event.*; -import java.io.File; -import java.io.IOException; import java.util.*; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Created by StravantUser on 2016-09-20. @@ -358,6 +352,15 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg public void setRole(Role role) { this.role = role; + if (role == Role.SIDEBOARD) { + creatureCountLabel.setVisible(false); + landCountLabel.setVisible(false); + cardSizeSliderLabel.setVisible(false); + } else { + creatureCountLabel.setVisible(true); + landCountLabel.setVisible(true); + cardSizeSliderLabel.setVisible(true); + } updateCounts(); } @@ -379,6 +382,23 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg cardContent.repaint(); } + public DeckCardLayout getCardLayout() { + // 2D Array to put entries into + List>> info = new ArrayList<>(); + for (ArrayList> gridRow : cardGrid) { + List> row = new ArrayList<>(); + info.add(row); + for (ArrayList stack : gridRow) { + row.add(stack.stream() + .map(card -> new DeckCardInfo(card.getName(), card.getCardNumber(), card.getExpansionSetCode())) + .collect(Collectors.toList())); + } + } + + // Store layout and settings then return them + return new DeckCardLayout(info, saveSettings().toString()); + } + public enum Sort { NONE("No Sort", new Comparator() { @Override @@ -476,6 +496,11 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg JPopupMenu sortPopup; JCheckBox separateCreaturesCb; + JSlider cardSizeSlider; + JLabel cardSizeSliderLabel; + + Map sortButtons = new HashMap<>(); + JLabel deckNameAndCountLabel; JLabel landCountLabel; JLabel creatureCountLabel; @@ -489,6 +514,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg // Card area selection panel SelectionBox selectionPanel; + Set selectionDragStartCards; int selectionDragStartX; int selectionDragStartY; @@ -526,6 +552,55 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg private String name; } + public static class Settings { + public Sort sort; + public boolean separateCreatures; + + private static Pattern parser = Pattern.compile("\\(([^,]*),([^)]*)\\)"); + + public static Settings parse(String str) { + Matcher m = parser.matcher(str); + if (m.find()) { + Settings s = new Settings(); + s.sort = Sort.valueOf(m.group(1)); + s.separateCreatures = Boolean.valueOf(m.group(2)); + return s; + } else { + return null; + } + } + + @Override + public String toString() { + return "(" + sort.toString() + "," + Boolean.toString(separateCreatures) + ")"; + } + } + + public Settings saveSettings() { + Settings s = new Settings(); + s.sort = cardSort; + s.separateCreatures = separateCreatures; + return s; + } + + public void loadSettings(Settings s) { + if (s != null) { + setSort(s.sort); + setSeparateCreatures(s.separateCreatures); + resort(); + } + } + + public void setSeparateCreatures(boolean state) { + separateCreatures = state; + separateCreaturesCb.setSelected(state); + } + + public void setSort(Sort s) { + cardSort = s; + sortButtons.get(s).setSelected(true); + } + // Constructor public DragCardGrid() { // Make sure that the card grid is populated with at least one (empty) stack to begin with @@ -540,22 +615,6 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg filterButton = new JButton("Filter"); visibilityButton = new JButton("Visibility"); - addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - deselectAll(); - } - }); - - // Tmp load button - JButton loadButton = new JButton("Load"); - loadButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - loadDeck(); - } - }); - // Name and count label deckNameAndCountLabel = new JLabel(); @@ -576,29 +635,26 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg toolbarInner.add(sortButton); toolbarInner.add(filterButton); toolbarInner.add(visibilityButton); - toolbarInner.add(loadButton); toolbar.add(toolbarInner, BorderLayout.WEST); JPanel sliderPanel = new JPanel(new GridBagLayout()); sliderPanel.setOpaque(false); - final JSlider sizeSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 100, 50); - sizeSlider.setOpaque(false); - sizeSlider.setPreferredSize(new Dimension(100, (int)sizeSlider.getPreferredSize().getHeight())); - sizeSlider.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - if (!sizeSlider.getValueIsAdjusting()) { - // Fraction in [-1, 1] - float sliderFrac = ((float) (sizeSlider.getValue() - 50)) / 50; - // Convert to frac in [0.5, 2.0] exponentially - cardSizeMod = (float) Math.pow(2, sliderFrac); - // Update grid - layoutGrid(); - cardContent.repaint(); - } + cardSizeSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 100, 50); + cardSizeSlider.setOpaque(false); + cardSizeSlider.setPreferredSize(new Dimension(100, (int) cardSizeSlider.getPreferredSize().getHeight())); + cardSizeSlider.addChangeListener(e -> { + if (!cardSizeSlider.getValueIsAdjusting()) { + // Fraction in [-1, 1] + float sliderFrac = ((float) (cardSizeSlider.getValue() - 50)) / 50; + // Convert to frac in [0.5, 2.0] exponentially + cardSizeMod = (float) Math.pow(2, sliderFrac); + // Update grid + layoutGrid(); + cardContent.repaint(); } }); - sliderPanel.add(new JLabel("Card Size:")); - sliderPanel.add(sizeSlider); + cardSizeSliderLabel = new JLabel("Card Size:"); + sliderPanel.add(cardSizeSliderLabel); + sliderPanel.add(cardSizeSlider); toolbar.add(sliderPanel, BorderLayout.EAST); this.add(toolbar, BorderLayout.NORTH); @@ -610,9 +666,11 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg private boolean isDragging = false; @Override public void mousePressed(MouseEvent e) { - isDragging = true; - beginSelectionDrag(e.getX(), e.getY()); - updateSelectionDrag(e.getX(), e.getY()); + if (SwingUtilities.isLeftMouseButton(e)) { + isDragging = true; + beginSelectionDrag(e.getX(), e.getY(), e.isShiftDown()); + updateSelectionDrag(e.getX(), e.getY()); + } } @Override public void mouseReleased(MouseEvent e) { @@ -673,15 +731,13 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg if (s == cardSort) { button.setSelected(true); } + sortButtons.put(s, button); sortMode.add(button); sortModeGroup.add(button); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - cardSort = s; - PreferencesDialog.saveValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SORT, s.toString()); - resort(); - } + button.addActionListener(e -> { + cardSort = s; + PreferencesDialog.saveValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SORT, s.toString()); + resort(); }); } @@ -695,13 +751,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg separateCreaturesCb = new JCheckBox(); separateCreaturesCb.setText("Creatures in separate row"); separateCreaturesCb.setSelected(separateCreatures); - separateCreaturesCb.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - separateCreatures = separateCreaturesCb.isSelected(); - PreferencesDialog.saveValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SEPARATE_CREATURES, Boolean.toString(separateCreatures)); - resort(); - } + separateCreaturesCb.addItemListener(e -> { + setSeparateCreatures(separateCreaturesCb.isSelected()); + PreferencesDialog.saveValue(PreferencesDialog.KEY_DECK_EDITOR_LAST_SEPARATE_CREATURES, Boolean.toString(separateCreatures)); + resort(); }); sortOptions.add(separateCreaturesCb); @@ -714,20 +767,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg { final JPopupMenu visPopup = new JPopupMenu(); JMenuItem hideSelected = new JMenuItem("Hide selected"); - hideSelected.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - hideSelection(); - } - }); + hideSelected.addActionListener(e -> hideSelection()); visPopup.add(hideSelected); JMenuItem showAll = new JMenuItem("Show all"); - showAll.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showAll(); - } - }); + showAll.addActionListener(e -> showAll()); visPopup.add(showAll); visibilityButton.addMouseListener(new MouseAdapter() { @Override @@ -743,7 +786,6 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg makeButtonPopup(filterButton, filterPopup); filterButton.setVisible(false); - loadButton.setVisible(false); // Right click in card area initCardAreaPopup(); @@ -756,44 +798,29 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg final JPopupMenu menu = new JPopupMenu(); final JMenuItem hideSelected = new JMenuItem("Hide selected"); - hideSelected.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - hideSelection(); - } - }); + hideSelected.addActionListener(e -> hideSelection()); menu.add(hideSelected); JMenuItem showAll = new JMenuItem("Show all"); - showAll.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showAll(); - } - }); + showAll.addActionListener(e -> showAll()); menu.add(showAll); JMenu sortMenu = new JMenu("Sort by..."); + final Map sortMenuItems = new LinkedHashMap<>(); for (final Sort sort : Sort.values()) { - JMenuItem subSort = new JMenuItem(sort.getText()); - subSort.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - cardSort = sort; - resort(); - } + JMenuItem subSort = new JCheckBoxMenuItem(sort.getText()); + sortMenuItems.put(sort, subSort); + subSort.addActionListener(e -> { + cardSort = sort; + resort(); }); sortMenu.add(subSort); } sortMenu.add(new JPopupMenu.Separator()); final JCheckBoxMenuItem separateButton = new JCheckBoxMenuItem("Separate creatures"); - separateButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - separateCreatures = !separateCreatures; - separateCreaturesCb.setSelected(separateCreatures); - resort(); - } + separateButton.addActionListener(e -> { + setSeparateCreatures(!separateCreatures); + resort(); }); sortMenu.add(separateButton); menu.add(sortMenu); @@ -803,6 +830,9 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg @Override public void mouseClicked(MouseEvent e) { if (SwingUtilities.isRightMouseButton(e)) { + for (Sort s : sortMenuItems.keySet()) { + sortMenuItems.get(s).setSelected(cardSort == s); + } hideSelected.setEnabled(dragCardList().size() > 0); separateButton.setSelected(separateCreatures); menu.show(e.getComponent(), e.getX(), e.getY()); @@ -843,7 +873,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg /** * Selection drag handling */ - private void beginSelectionDrag(int x, int y) { + private void beginSelectionDrag(int x, int y, boolean shiftHeld) { // Show the selection panel selectionPanel.setVisible(true); selectionPanel.setLocation(x, y); @@ -853,6 +883,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg selectionDragStartX = x; selectionDragStartY = y; + // Store the starting cards to include in the selection + selectionDragStartCards = new HashSet<>(); + if (shiftHeld) { + selectionDragStartCards.addAll(dragCardList()); + } + // Notify selection notifyCardsSelected(); } @@ -904,7 +940,8 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg boolean inBoundsX = (col >= col1 && col <= col2); boolean inBoundsY = (i >= stackStartIndex && i <= stackEndIndex); boolean lastCard = (i == stack.size()-1); - if (inBoundsX && (inBoundsY || (lastCard && (y2 >= stackBottomBegin && y1 <= stackBottomEnd)))) { + boolean inSeletionDrag = inBoundsX && (inBoundsY || (lastCard && (y2 >= stackBottomBegin && y1 <= stackBottomEnd))); + if (inSeletionDrag || selectionDragStartCards.contains(card)) { if (!card.isSelected()) { card.setSelected(true); view.update(card); @@ -921,42 +958,11 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg } } - private void endSelectionDrag(int x, int y) { + private void endSelectionDrag(@SuppressWarnings("unused") int x, @SuppressWarnings("unused") int y) { // Hide the selection panel selectionPanel.setVisible(false); } - - private void loadDeck() { - JFileChooser fcSelectDeck = new JFileChooser(); - String lastFolder = MageFrame.getPreferences().get("lastDeckFolder", ""); - if (!lastFolder.isEmpty()) { - fcSelectDeck.setCurrentDirectory(new File(lastFolder)); - } - int ret = fcSelectDeck.showOpenDialog(DragCardGrid.this); - if (ret == JFileChooser.APPROVE_OPTION) { - File file = fcSelectDeck.getSelectedFile(); - try { - setCursor(new Cursor(Cursor.WAIT_CURSOR)); - Deck deck = Deck.load(DeckImporterUtil.importDeck(file.getPath()), true, true); - setCards(new CardsView(deck.getCards()), null); - } catch (GameException ex) { - JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); - } - try { - if (file != null) { - MageFrame.getPreferences().put("lastDeckFolder", file.getCanonicalPath()); - } - } catch (IOException ex) { - } - } - fcSelectDeck.setSelectedFile(null); - } - // Resort the existing cards based on the current sort public void resort() { // First null out the grid and trim it down @@ -984,7 +990,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg } // Update the contents of the card grid - public void setCards(CardsView cardsView, BigCard bigCard) { + public void setCards(CardsView cardsView, DeckCardLayout layout, BigCard bigCard) { if (bigCard != null) { lastBigCard = bigCard; } @@ -1014,20 +1020,89 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg trimGrid(); } - // Add any new card views - for (CardView newCard: cardsView.values()) { - if (!cardViews.containsKey(newCard.getId())) { - // Is a new card - addCardView(newCard); + if (layout == null) { + // No layout -> add any new card views one at a time as par the current sort + for (CardView newCard: cardsView.values()) { + if (!cardViews.containsKey(newCard.getId())) { + // Is a new card + addCardView(newCard); - try { // Put it into the appropirate place in the grid given the current sort sortIntoGrid(newCard); - } catch (Exception e) { - e.printStackTrace(); + + // Mark + didModify = true; + } + } + } else { + // Layout given -> Build card grid using layout, and set sort / separate + + // Always modify when given a layout + didModify = true; + + // Load in settings + loadSettings(Settings.parse(layout.getSettings())); + + // Traverse the cards once and track them so we can pick ones to insert into the grid + Map>> trackedCards = new HashMap<>(); + for (CardView newCard: cardsView.values()) { + if (!cardViews.containsKey(newCard.getId())) { + // Add the new card + addCardView(newCard); + + // Add the new card to tracking + Map> forSetCode; + if (trackedCards.containsKey(newCard.getExpansionSetCode())) { + forSetCode = trackedCards.get(newCard.getExpansionSetCode()); + } else { + forSetCode = new HashMap<>(); + trackedCards.put(newCard.getExpansionSetCode(), forSetCode); + } + ArrayList list; + if (forSetCode.containsKey(newCard.getCardNumber())) { + list = forSetCode.get(newCard.getCardNumber()); + } else { + list = new ArrayList<>(); + forSetCode.put(newCard.getCardNumber(), list); + } + list.add(newCard); + } + } + + // Now go through the layout and use it to build the cardGrid + cardGrid = new ArrayList<>(); + maxStackSize = new ArrayList<>(); + for (List> row : layout.getCards()) { + ArrayList> gridRow = new ArrayList<>(); + int thisMaxStackSize = 0; + cardGrid.add(gridRow); + for (List stack : row) { + ArrayList gridStack = new ArrayList<>(); + gridRow.add(gridStack); + for (DeckCardInfo info : stack) { + if (trackedCards.containsKey(info.getSetCode()) && trackedCards.get(info.getSetCode()).containsKey(info.getCardNum())) { + ArrayList candidates = + trackedCards.get(info.getSetCode()).get(info.getCardNum()); + if (candidates.size() > 0) { + gridStack.add(candidates.remove(0)); + thisMaxStackSize = Math.max(thisMaxStackSize, gridStack.size()); + } + } + } + } + maxStackSize.add(thisMaxStackSize); + } + + // Check that there aren't any "orphans" not referenced in the layout. There should + // never be any under normal operation, but as a failsafe in case the user screwed with + // the file in an invalid way, sort them into the grid so that they aren't just left hanging. + for (Map> tracked : trackedCards.values()) { + for (ArrayList orphans : tracked.values()) { + for (CardView orphan : orphans) { + LOGGER.info("Orphan when setting with layout: "); + sortIntoGrid(orphan); + } } - // Mark - didModify = true; } } @@ -1048,18 +1123,10 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg landCountLabel.setText("" + landCounter.get()); } - private void showCardRightClickMenu(final CardView card, MouseEvent e) { + private void showCardRightClickMenu(@SuppressWarnings("unused") final CardView card, MouseEvent e) { JPopupMenu menu = new JPopupMenu(); JMenuItem hide = new JMenuItem("Hide"); - hide.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - //if (card.isSelected() && dragCardList().size() > 1) { - // Hide all selected - hideSelection(); - //} - } - }); + hide.addActionListener(e2 -> hideSelection()); menu.add(hide); menu.show(e.getComponent(), e.getX(), e.getY()); } @@ -1074,7 +1141,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg updateCounts(); // Create the card view - final MageCard cardPanel = Plugins.getInstance().getMageCard(card, lastBigCard, new Dimension(100, 140), null, true, true); + final MageCard cardPanel = Plugins.getInstance().getMageCard(card, lastBigCard, new Dimension(getCardWidth(), getCardHeight()), null, true, true); cardPanel.update(card); cardPanel.setTextOffset(0); @@ -1156,8 +1223,17 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg } } + private void toggleSelected(CardView targetCard) { + targetCard.setSelected(!targetCard.isSelected()); + cardViews.get(targetCard.getId()).update(targetCard); + } + private void cardClicked(CardView targetCard, MouseEvent e) { - selectCard(targetCard); + if (e.isShiftDown()) { + toggleSelected(targetCard); + } else { + selectCard(targetCard); + } notifyCardsSelected(); } @@ -1176,12 +1252,12 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg /** * Add a card to the cardGrid, in the position that the current sort dictates - * @param newCard + * @param newCard Card to add to the cardGrid array. */ private void sortIntoGrid(CardView newCard) { // Ensure row 1 exists if (cardGrid.size() == 0) { - cardGrid.add(0, new ArrayList>()); + cardGrid.add(0, new ArrayList<>()); maxStackSize.add(0, 0); } // What row to add it to? @@ -1189,11 +1265,11 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg if (separateCreatures && !newCard.getCardTypes().contains(CardType.CREATURE)) { // Ensure row 2 exists if (cardGrid.size() < 2) { - cardGrid.add(1, new ArrayList>()); + cardGrid.add(1, new ArrayList<>()); maxStackSize.add(1, 0); // Populate with stacks matching the first row for (int i = 0; i < cardGrid.get(0).size(); ++i) { - cardGrid.get(1).add(new ArrayList()); + cardGrid.get(1).add(new ArrayList<>()); } } targetRow = cardGrid.get(1); @@ -1223,7 +1299,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg // Insert into this col, but if less, then we need to create a new col here first if (res < 0) { for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) { - cardGrid.get(rowIndex).add(currentColumn, new ArrayList()); + cardGrid.get(rowIndex).add(currentColumn, new ArrayList<>()); } } targetRow.get(currentColumn).add(newCard); @@ -1238,7 +1314,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg // If nothing else, insert in a new column after everything else if (!didInsert) { for (int rowIndex = 0; rowIndex < cardGrid.size(); ++rowIndex) { - cardGrid.get(rowIndex).add(new ArrayList()); + cardGrid.get(rowIndex).add(new ArrayList<>()); } targetRow.get(targetRow.size()-1).add(newCard); } @@ -1335,7 +1411,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg // Stack count label if (stackCountLabels.size() <= rowIndex) { - stackCountLabels.add(new ArrayList()); + stackCountLabels.add(new ArrayList<>()); } if (stackCountLabels.get(rowIndex).size() <= colIndex) { JLabel countLabel = new JLabel("", SwingConstants.CENTER); @@ -1379,70 +1455,7 @@ public class DragCardGrid extends JPanel implements DragCardSource, DragCardTarg } private static void makeButtonPopup(final AbstractButton button, final JPopupMenu popup) { - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - popup.show(button, 0, button.getHeight()); - } - }); - } - - static class DeckFilter extends FileFilter { - @Override - public boolean accept(File f) { - if (f.isDirectory()) { - return true; - } - - String ext = null; - String s = f.getName(); - int i = s.lastIndexOf('.'); - - if (i > 0 && i < s.length() - 1) { - ext = s.substring(i + 1).toLowerCase(); - } - return (ext == null) ? false : ext.equals("dck"); - } - - @Override - public String getDescription() { - return "Deck Files"; - } - } - - public static void main(String[] args) { - JFrame frame = new JFrame(); - /* - GUISizeHelper.calculateGUISizes(); - Plugins.getInstance().loadPlugins(); - frame.setTitle("Test"); - frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - frame.setBackground(Color.BLUE); - DragCardGrid grid = new DragCardGrid(); - grid.setPreferredSize(new Dimension(800, 600)); - */ - try { - UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); - } catch (UnsupportedLookAndFeelException | ClassNotFoundException | IllegalAccessException | InstantiationException e) {} - frame.setVisible(true); - JFileChooser choose = new JFileChooser(); - choose.setAcceptAllFileFilterUsed(false); - choose.addChoosableFileFilter(new DeckFilter()); - choose.showOpenDialog(frame); - LOGGER.info("File: " + choose.getSelectedFile()); - String st = ""; - try { - st = choose.getSelectedFile().getCanonicalPath(); - } catch (Exception e) { - e.printStackTrace(); - } - LOGGER.info("Selected file: " + st); - choose.setSelectedFile(new File(st)); - choose.showOpenDialog(frame); - LOGGER.info("File: " + choose.getSelectedFile()); - //frame.add(grid, BorderLayout.CENTER); - //frame.pack(); - frame.setVisible(false); + button.addActionListener(e -> popup.show(button, 0, button.getHeight())); } } diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java index 68dd871c894..34b0ab24374 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckArea.java @@ -34,6 +34,9 @@ package mage.client.deckeditor; import mage.cards.Card; import mage.cards.decks.Deck; +import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLayout; +import mage.cards.decks.DeckCardLists; import mage.client.cards.BigCard; import mage.client.cards.CardEventSource; import mage.client.cards.DragCardGrid; @@ -43,9 +46,12 @@ import mage.client.util.GUISizeHelper; import mage.client.util.Listener; import mage.view.CardView; import mage.view.CardsView; +import org.apache.log4j.Logger; import javax.swing.*; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @@ -58,6 +64,45 @@ public class DeckArea extends javax.swing.JPanel { private Set hiddenCards = new HashSet<>(); private Deck lastDeck = new Deck(); private BigCard lastBigCard = null; + private int dividerLocationNormal = 0; + private int dividerLocationLimited = 0; + private boolean isLimitedBuildingOrientation = false; + + public DeckCardLayout getCardLayout() { + return deckList.getCardLayout(); + } + + public DeckCardLayout getSideboardLayout() { + return sideboardList.getCardLayout(); + } + + public static class Settings { + public DragCardGrid.Settings maindeckSettings; + public DragCardGrid.Settings sideboardSetings; + public int dividerLocationLimited; + public int dividerLocationNormal; + + private static Pattern parser = Pattern.compile("([^|]*)\\|([^|]*)\\|([^|]*)\\|([^|]*)"); + + public static Settings parse(String s) { + Matcher m = parser.matcher(s); + if (m.find()) { + Settings settings = new Settings(); + settings.maindeckSettings = DragCardGrid.Settings.parse(m.group(1)); + settings.sideboardSetings = DragCardGrid.Settings.parse(m.group(2)); + settings.dividerLocationNormal = Integer.parseInt(m.group(3)); + settings.dividerLocationLimited = Integer.parseInt(m.group(4)); + return settings; + } else { + return null; + } + } + + @Override + public String toString() { + return maindeckSettings.toString() + "|" + sideboardSetings.toString() + "|" + dividerLocationNormal + "|" + dividerLocationLimited; + } + } /** * Creates new form DeckArea @@ -117,6 +162,38 @@ public class DeckArea extends javax.swing.JPanel { }); } + public Settings saveSettings() { + Settings settings = new Settings(); + settings.maindeckSettings = deckList.saveSettings(); + settings.sideboardSetings = sideboardList.saveSettings(); + if (isLimitedBuildingOrientation) { + dividerLocationLimited = deckAreaSplitPane.getDividerLocation(); + } else { + dividerLocationNormal = deckAreaSplitPane.getDividerLocation(); + } + settings.dividerLocationLimited = dividerLocationLimited; + settings.dividerLocationNormal = dividerLocationNormal; + return settings; + } + + public void loadSettings(Settings s) { + if (s != null) { + deckList.loadSettings(s.maindeckSettings); + sideboardList.loadSettings(s.sideboardSetings); + dividerLocationLimited = s.dividerLocationLimited; + dividerLocationNormal = s.dividerLocationNormal; + if (isLimitedBuildingOrientation) { + if (dividerLocationLimited != 0) { + deckAreaSplitPane.setDividerLocation(s.dividerLocationLimited); + } + } else { + if (dividerLocationNormal != 0) { + deckAreaSplitPane.setDividerLocation(s.dividerLocationNormal); + } + } + } + } + public void cleanUp() { deckList.cleanUp(); sideboardList.cleanUp(); @@ -135,8 +212,14 @@ public class DeckArea extends javax.swing.JPanel { public void setOrientation(boolean limitedBuildingOrientation) { if (limitedBuildingOrientation) { deckAreaSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT); + if (dividerLocationLimited != 0) { + deckAreaSplitPane.setDividerLocation(dividerLocationLimited); + } } else { deckAreaSplitPane.setOrientation(JSplitPane.HORIZONTAL_SPLIT); + if (dividerLocationNormal != 0) { + deckAreaSplitPane.setDividerLocation(dividerLocationNormal); + } } } @@ -161,11 +244,21 @@ public class DeckArea extends javax.swing.JPanel { } public void loadDeck(Deck deck, BigCard bigCard) { + loadDeck(deck, false, bigCard); + } + + public void loadDeck(Deck deck, boolean useLayout, BigCard bigCard) { lastDeck = deck; lastBigCard = bigCard; - deckList.setCards(new CardsView(filterHidden(lastDeck.getCards())), lastBigCard); + deckList.setCards( + new CardsView(filterHidden(lastDeck.getCards())), + useLayout ? deck.getCardsLayout() : null, + lastBigCard); if (sideboardList.isVisible()) { - sideboardList.setCards(new CardsView(filterHidden(lastDeck.getSideboard())), lastBigCard); + sideboardList.setCards( + new CardsView(filterHidden(lastDeck.getSideboard())), + useLayout ? deck.getSideboardLayout() : null, + lastBigCard); } } diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index a6ab56a1c66..a040df0464c 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -28,10 +28,7 @@ package mage.client.deckeditor; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; +import java.awt.event.*; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -50,6 +47,7 @@ import javax.swing.filechooser.FileFilter; import mage.cards.Card; import mage.cards.Sets; import mage.cards.decks.Deck; +import mage.cards.decks.DeckCardLists; import mage.cards.decks.importer.DeckImporter; import mage.cards.decks.importer.DeckImporterUtil; import mage.cards.repository.CardInfo; @@ -62,6 +60,7 @@ import mage.client.constants.Constants.DeckEditorMode; import mage.client.deck.generator.DeckGenerator.DeckGeneratorException; import mage.client.deck.generator.DeckGenerator; import mage.client.dialog.AddLandDialog; +import mage.client.dialog.PreferencesDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.Event; import mage.client.util.Listener; @@ -106,12 +105,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { deckArea.setOpaque(false); jPanel1.setOpaque(false); jSplitPane1.setOpaque(false); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - jSplitPane1.setDividerLocation(0.3); - } - }); + restoreDividerLocationsAndDeckAreaSettings(); countdown = new Timer(1000, new ActionListener() { @Override @@ -128,12 +122,22 @@ public class DeckEditorPanel extends javax.swing.JPanel { } } }); + + // Set up tracking to save the deck editor settings when the deck editor is hidden. + addHierarchyListener((HierarchyEvent e) -> { + if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { + if (!isShowing()) { + saveDividerLocationsAndDeckAreaSettings(); + } + } + }); } /** * Free resources so GC can remove unused objects from memory */ public void cleanUp() { + saveDividerLocationsAndDeckAreaSettings(); if (updateDeckTask != null) { updateDeckTask.cancel(true); } @@ -152,6 +156,24 @@ public class DeckEditorPanel extends javax.swing.JPanel { this.bigCard = null; } + private void saveDividerLocationsAndDeckAreaSettings() { + PreferencesDialog.saveValue(PreferencesDialog.KEY_EDITOR_HORIZONTAL_DIVIDER_LOCATION, Integer.toString(jSplitPane1.getDividerLocation())); + PreferencesDialog.saveValue(PreferencesDialog.KEY_EDITOR_DECKAREA_SETTINGS, this.deckArea.saveSettings().toString()); + } + + private void restoreDividerLocationsAndDeckAreaSettings() { + // Load horizontal split position setting + String dividerLocation = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_EDITOR_HORIZONTAL_DIVIDER_LOCATION, ""); + if (!dividerLocation.isEmpty()) { + jSplitPane1.setDividerLocation(Integer.parseInt(dividerLocation)); + } + + // Load deck area settings + this.deckArea.loadSettings( + DeckArea.Settings.parse( + PreferencesDialog.getCachedValue(PreferencesDialog.KEY_EDITOR_DECKAREA_SETTINGS, ""))); + } + public void changeGUISize() { this.cardSelector.changeGUISize(); this.deckArea.changeGUISize(); @@ -556,10 +578,14 @@ public class DeckEditorPanel extends javax.swing.JPanel { } private void refreshDeck() { + refreshDeck(false); + } + + private void refreshDeck(boolean useLayout) { try { setCursor(new Cursor(Cursor.WAIT_CURSOR)); this.txtDeckName.setText(deck.getName()); - deckArea.loadDeck(deck, bigCard); + deckArea.loadDeck(deck, useLayout, bigCard); } finally { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } @@ -615,13 +641,6 @@ public class DeckEditorPanel extends javax.swing.JPanel { jSplitPane1.setTopComponent(cardSelector); jSplitPane1.setBottomComponent(deckArea); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - jSplitPane1.setDividerLocation(0.6); - } - }); - bigCard.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); cardInfoPane = Plugins.getInstance().getCardInfoPane(); @@ -878,7 +897,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { } finally { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } - refreshDeck(); + refreshDeck(true); try { if (file != null) { MageFrame.getPreferences().put("lastDeckFolder", file.getCanonicalPath()); @@ -919,7 +938,10 @@ public class DeckEditorPanel extends javax.swing.JPanel { fileName += ".dck"; } setCursor(new Cursor(Cursor.WAIT_CURSOR)); - Sets.saveDeck(fileName, deck.getDeckCardLists()); + DeckCardLists cardLists = deck.getDeckCardLists(); + cardLists.setCardLayout(deckArea.getCardLayout()); + cardLists.setSideboardLayout(deckArea.getSideboardLayout()); + Sets.saveDeck(fileName, cardLists); } catch (FileNotFoundException ex) { JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage() + "\nTry ensuring that the selected directory is writable.", "Error saving deck", JOptionPane.ERROR_MESSAGE); } finally { diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index 4f322571801..57ab43e6e32 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -185,6 +185,10 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_TABLES_DIVIDER_LOCATION_2 = "tablePanelDividerLocation2"; public static final String KEY_TABLES_DIVIDER_LOCATION_3 = "tablePanelDividerLocation3"; + // Positions of deck editor divider bars + public static final String KEY_EDITOR_HORIZONTAL_DIVIDER_LOCATION = "editorHorizontalDividerLocation"; + public static final String KEY_EDITOR_DECKAREA_SETTINGS = "editorDeckAreaSettings"; + // user list public static final String KEY_USERS_COLUMNS_WIDTH = "userPanelColumnWidth"; public static final String KEY_USERS_COLUMNS_ORDER = "userPanelColumnSort"; diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 30e67c53b4f..9e366c2ceaf 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -602,7 +602,7 @@ public class TablesPanel extends javax.swing.JPanel { formatFilterList.add(RowFilter.regexFilter("^Limited", TableTableModel.COLUMN_DECK_TYPE)); } if (btnFormatOther.isSelected()) { - formatFilterList.add(RowFilter.regexFilter("^Momir Basic|^Constructed - Pauper|^Constructed - Frontier|^Constructed - Extended|^Constructed - Historical|^Constructed - Super|^Constructed - Freeform", TableTableModel.COLUMN_DECK_TYPE)); + formatFilterList.add(RowFilter.regexFilter("^Momir Basic|^Constructed - Pauper|^Constructed - Frontier|^Constructed - Extended|^Constructed - Eternal|^Constructed - Historical|^Constructed - Super|^Constructed - Freeform", TableTableModel.COLUMN_DECK_TYPE)); } List> skillFilterList = new ArrayList<>(); diff --git a/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java b/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java index cf424ae636f..ab060fd3f2e 100644 --- a/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java +++ b/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java @@ -25,6 +25,10 @@ public class ConstructedFormats { public static final String EXTENDED = "- Extended"; public static final String FRONTIER = "- Frontier"; public static final String MODERN = "- Modern"; + public static final String VINTAGE_LEGACY = "- Vintage / Legacy"; + ; + public static final String CUSTOM = "- Custom"; + ; public static final Standard STANDARD_CARDS = new Standard(); private static final Map> underlyingSetCodesPerFormat = new HashMap<>(); @@ -50,50 +54,49 @@ public class ConstructedFormats { } public static void ensureLists() { - if (getSetsByFormat(ConstructedFormats.STANDARD) == null) { + if (underlyingSetCodesPerFormat.isEmpty()) { buildLists(); } } private static void buildLists() { + underlyingSetCodesPerFormat.put(STANDARD, new ArrayList<>()); + underlyingSetCodesPerFormat.put(EXTENDED, new ArrayList<>()); + underlyingSetCodesPerFormat.put(FRONTIER, new ArrayList<>()); + underlyingSetCodesPerFormat.put(MODERN, new ArrayList<>()); + underlyingSetCodesPerFormat.put(VINTAGE_LEGACY, new ArrayList<>()); + underlyingSetCodesPerFormat.put(CUSTOM, new ArrayList<>()); final Map expansionInfo = new HashMap<>(); formats.clear(); // prevent NPE on sorting if this is not the first try for (ExpansionInfo set : ExpansionRepository.instance.getAll()) { expansionInfo.put(set.getName(), set); formats.add(set.getName()); + + underlyingSetCodesPerFormat.put(set.getName(), new ArrayList<>()); + underlyingSetCodesPerFormat.get(set.getName()).add(set.getCode()); + + // create the play formats + if (set.getType().equals(SetType.CUSTOM_SET)) { + underlyingSetCodesPerFormat.get(CUSTOM).add(set.getCode()); + continue; + } + underlyingSetCodesPerFormat.get(VINTAGE_LEGACY).add(set.getCode()); if (set.getType().equals(SetType.CORE) || set.getType().equals(SetType.EXPANSION) || set.getType().equals(SetType.SUPPLEMENTAL_STANDARD_LEGAL)) { if (STANDARD_CARDS.getSetCodes().contains(set.getCode())) { - if (underlyingSetCodesPerFormat.get(STANDARD) == null) { - underlyingSetCodesPerFormat.put(STANDARD, new ArrayList<>()); - } underlyingSetCodesPerFormat.get(STANDARD).add(set.getCode()); } if (set.getReleaseDate().after(extendedDate)) { - if (underlyingSetCodesPerFormat.get(EXTENDED) == null) { - underlyingSetCodesPerFormat.put(EXTENDED, new ArrayList<>()); - } underlyingSetCodesPerFormat.get(EXTENDED).add(set.getCode()); } if (set.getReleaseDate().after(frontierDate)) { - if (underlyingSetCodesPerFormat.get(FRONTIER) == null) { - underlyingSetCodesPerFormat.put(FRONTIER, new ArrayList<>()); - } underlyingSetCodesPerFormat.get(FRONTIER).add(set.getCode()); } if (set.getReleaseDate().after(modernDate)) { - if (underlyingSetCodesPerFormat.get(MODERN) == null) { - underlyingSetCodesPerFormat.put(MODERN, new ArrayList<>()); - } underlyingSetCodesPerFormat.get(MODERN).add(set.getCode()); } } - if (underlyingSetCodesPerFormat.get(set.getName()) == null) { - underlyingSetCodesPerFormat.put(set.getName(), new ArrayList<>()); - } - - underlyingSetCodesPerFormat.get(set.getName()).add(set.getCode()); - + // Create the Block formats if (set.getType().equals(SetType.EXPANSION) && set.getBlockName() != null) { String blockDisplayName = getBlockDisplayName(set.getBlockName()); if (underlyingSetCodesPerFormat.get(blockDisplayName) == null) { @@ -209,10 +212,13 @@ public class ConstructedFormats { }); if (!formats.isEmpty()) { + formats.add(0, CUSTOM); + formats.add(0, VINTAGE_LEGACY); formats.add(0, MODERN); - formats.add(0, FRONTIER); formats.add(0, EXTENDED); + formats.add(0, FRONTIER); formats.add(0, STANDARD); + } formats.add(0, ALL); } diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index d61c388b4fc..abcd2656d31 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -153,11 +153,13 @@ public class ManaSymbols { sizedSymbols.put(symbol, notResized); } else { Rectangle r = new Rectangle(size, size); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + //Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + BufferedImage image = ImageIO.read(file); + //BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + BufferedImage resized = ImageHelper.getResizedImage(image, r); sizedSymbols.put(symbol, resized); } - } catch (Exception e) { + } catch (IOException e) { LOGGER.error("Error for symbol:" + symbol); fileErrors = true; } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java index 7aaaa9d0ad0..485582d6a4f 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java @@ -73,6 +73,28 @@ public class GathererSymbols implements Iterable { String symbol = sym.replaceAll("/", ""); File dst = new File(dir, symbol + ".gif"); + /** + * Handle a bug on Gatherer where a few symbols are missing at the large size. + * Fall back to using the medium symbol for those cases. + */ + int modSizeIndex = sizeIndex; + if (sizeIndex == 2) { + switch (sym) { + case "WP": + case "UP": + case "BP": + case "RP": + case "GP": + case "E": + case "C": + modSizeIndex = 1; + break; + + default: + // Nothing to do, symbol is available in the large size + } + } + switch (symbol) { case "T": symbol = "tap"; @@ -85,7 +107,7 @@ public class GathererSymbols implements Iterable { break; } - String url = format(urlFmt, sizes[sizeIndex], symbol); + String url = format(urlFmt, sizes[modSizeIndex], symbol); return new DownloadJob(sym, fromURL(url), toFile(dst)); } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgOnlTokensImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgOnlTokensImageSource.java index acc2f83e027..cf09fcefb59 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgOnlTokensImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MtgOnlTokensImageSource.java @@ -177,6 +177,7 @@ public class MtgOnlTokensImageSource implements CardImageSource { copyUrlToImage.put("Elemental_BR_5_5.jpg", "ELEMENTAL.BR.ELEMENTAL.CREATURE.1.1.full.jpg"); copyUrlToImage.put("Elemental_GW_y_y.jpg", "ELEMENTAL.WG.ELEMENTAL.CREATURE.S.S.full.jpg"); copyUrlToImage.put("Elemental_G_2_2.jpg", "ELEMENTAL.G.ELEMENTAL.CREATURE.2.2.full.jpg"); + copyUrlToImage.put("Elemental_R_3_1.jpg", "ELEMENTAL.R.ELEMENTAL.CREATURE.3.1.full.jpg"); copyUrlToImage.put("Elemental_G_4_4.jpg", "ELEMENTAL.G.ELEMENTAL.CREATURE.4.4.full.jpg"); copyUrlToImage.put("Elemental_G_5_3.jpg", "ELEMENTAL.G.ELEMENTAL.CREATURE.5.3.full.jpg"); copyUrlToImage.put("Elemental_G_7_7.jpg", "ELEMENTAL.G.ELEMENTAL.CREATURE.7.7.full.jpg"); diff --git a/Mage.Common/src/mage/view/AbilityPickerView.java b/Mage.Common/src/mage/view/AbilityPickerView.java index 489883f31af..b0572c0bb42 100644 --- a/Mage.Common/src/mage/view/AbilityPickerView.java +++ b/Mage.Common/src/mage/view/AbilityPickerView.java @@ -24,8 +24,7 @@ * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. -*/ - + */ package mage.view; import java.io.Serializable; @@ -40,12 +39,13 @@ import mage.abilities.Ability; * @author BetaSteward_at_googlemail.com */ public class AbilityPickerView implements Serializable { + private static final long serialVersionUID = 1L; - private Map choices = new LinkedHashMap(); + private Map choices = new LinkedHashMap<>(); public AbilityPickerView(String objectName, List abilities) { - for (Ability ability: abilities) { + for (Ability ability : abilities) { if (objectName == null) { choices.put(ability.getId(), ability.getRule(true)); } else { diff --git a/Mage.Common/src/mage/view/CardView.java b/Mage.Common/src/mage/view/CardView.java index 2c1aa4c5aed..69b27f15dcf 100644 --- a/Mage.Common/src/mage/view/CardView.java +++ b/Mage.Common/src/mage/view/CardView.java @@ -324,7 +324,7 @@ public class CardView extends SimpleCardView { for (UUID modeId : spellAbility.getModes().getSelectedModes()) { Mode mode = spellAbility.getModes().get(modeId); if (mode.getTargets().size() > 0) { - setTargets(spellAbility.getTargets()); + setTargets(mode.getTargets()); } } } diff --git a/Mage.Common/src/mage/view/PlayerView.java b/Mage.Common/src/mage/view/PlayerView.java index d64d2cb41e5..9752a89a8d8 100644 --- a/Mage.Common/src/mage/view/PlayerView.java +++ b/Mage.Common/src/mage/view/PlayerView.java @@ -125,7 +125,7 @@ public class PlayerView implements Serializable { } catch (ConcurrentModificationException e) { // can happen as a player left battlefield while PlayerView is created } - this.topCard = player.isTopCardRevealed() && player.getLibrary().size() > 0 + this.topCard = (player.isTopCardRevealed() && player.getLibrary().size() > 0) ? new CardView(player.getLibrary().getFromTop(game)) : null; if (player.getUserData() != null) { this.userData = player.getUserData(); diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Legacy.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Legacy.java index f8c24589b7b..13fc4428630 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Legacy.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Legacy.java @@ -24,11 +24,13 @@ * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. -*/ - + */ package mage.deck; +import mage.cards.ExpansionSet; +import mage.cards.Sets; import mage.cards.decks.Constructed; +import mage.constants.SetType; /** * @@ -38,7 +40,11 @@ public class Legacy extends Constructed { public Legacy() { super("Constructed - Legacy"); - + for (ExpansionSet set : Sets.getInstance().values()) { + if (set.getSetType() != SetType.CUSTOM_SET) { + setCodes.add(set.getCode()); + } + } banned.add("Advantageous Proclamation"); banned.add("Amulet of Quoz"); banned.add("Ancestral Recall"); @@ -112,6 +118,6 @@ public class Legacy extends Constructed { banned.add("Worldknit"); banned.add("Yawgmoth's Bargain"); banned.add("Yawgmoth's Will"); - + } } diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Vintage.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Vintage.java index 8646bcf2905..1dcdb14614a 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Vintage.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Vintage.java @@ -27,7 +27,10 @@ */ package mage.deck; +import mage.cards.ExpansionSet; +import mage.cards.Sets; import mage.cards.decks.Constructed; +import mage.constants.SetType; /** * @@ -37,7 +40,11 @@ public class Vintage extends Constructed { public Vintage() { super("Constructed - Vintage"); - + for (ExpansionSet set : Sets.getInstance().values()) { + if (set.getSetType() != SetType.CUSTOM_SET) { + setCodes.add(set.getCode()); + } + } banned.add("Advantageous Proclamation"); banned.add("Amulet of Quoz"); banned.add("Backup Plan"); diff --git a/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuelMatch.java b/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuelMatch.java index c20af5e9bf3..1090c4bd645 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuelMatch.java +++ b/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuelMatch.java @@ -1,16 +1,16 @@ /* * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. - * + * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR @@ -20,7 +20,7 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. @@ -47,7 +47,7 @@ public class CommanderDuelMatch extends MatchImpl { boolean alsoHand = true; // Don't like it to compare but seems like it's complicated to do it in another way if (options.getDeckType().equals("Variant Magic - Duel Commander")) { - startLife = 30; + startLife = 20; // Starting with the Commander 2016 update (on November 11th, 2016), Duel Commander will be played with 20 life points instead of 30. alsoHand = true; // commander going to hand allowed to go to command zone effective July 17, 2015 } CommanderDuel game = new CommanderDuel(options.getAttackOption(), options.getRange(), options.getFreeMulligans(), startLife); diff --git a/Mage.Server/src/main/java/mage/server/Session.java b/Mage.Server/src/main/java/mage/server/Session.java index 602281c4e89..d6aabf7a068 100644 --- a/Mage.Server/src/main/java/mage/server/Session.java +++ b/Mage.Server/src/main/java/mage/server/Session.java @@ -284,7 +284,7 @@ public class Session { lockSet = true; logger.debug("SESSION LOCK SET sessionId: " + sessionId); } else { - logger.error("CAN'T GET LOCK - userId: " + userId); + logger.error("CAN'T GET LOCK - userId: " + userId + " hold count: " + lock.getHoldCount()); } User user = UserManager.getInstance().getUser(userId); if (user == null || !user.isConnected()) { diff --git a/Mage.Sets/src/mage/sets/mercadianmasques/ThievesAuction.java b/Mage.Sets/src/mage/sets/mercadianmasques/ThievesAuction.java index 84ffe10daf5..a153561d28d 100644 --- a/Mage.Sets/src/mage/sets/mercadianmasques/ThievesAuction.java +++ b/Mage.Sets/src/mage/sets/mercadianmasques/ThievesAuction.java @@ -109,8 +109,8 @@ class ThievesAuctionEffect extends OneShotEffect { // Starting with you, each player PlayerList playerList = game.getState().getPlayersInRange(controller.getId(), game); Player player = playerList.getCurrent(game); - while (!exiledCards.isEmpty()) { - if (player.canRespond()) { + while (!exiledCards.isEmpty() && !game.hasEnded()) { + if (player != null && player.canRespond()) { // chooses one of the exiled cards TargetCard target = new TargetCardInExile(new FilterCard()); if (player.choose(Outcome.PutCardInPlay, exiledCards, target, game)) { diff --git a/Mage.Sets/src/mage/sets/prophecy/AvatarOfMight.java b/Mage.Sets/src/mage/sets/prophecy/AvatarOfMight.java index 2b7b581f5c0..00bdd683ce7 100644 --- a/Mage.Sets/src/mage/sets/prophecy/AvatarOfMight.java +++ b/Mage.Sets/src/mage/sets/prophecy/AvatarOfMight.java @@ -105,11 +105,13 @@ class AvatarOfMightCostReductionEffect extends CostModificationEffectImpl { @Override public boolean applies(Ability abilityToModify, Ability source, Game game) { - int creatures = game.getBattlefield().countAll(new FilterCreaturePermanent(), source.getControllerId(), game); - for (UUID playerId : game.getOpponents(source.getControllerId())) { - Player opponent = game.getPlayer(playerId); - if (opponent != null && game.getBattlefield().countAll(new FilterCreaturePermanent(), opponent.getId(), game) >= creatures + 4) { - return true; + if (abilityToModify.getSourceId().equals(source.getSourceId()) && (abilityToModify instanceof SpellAbility)) { + int creatures = game.getBattlefield().countAll(new FilterCreaturePermanent(), source.getControllerId(), game); + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player opponent = game.getPlayer(playerId); + if (opponent != null && game.getBattlefield().countAll(new FilterCreaturePermanent(), opponent.getId(), game) >= creatures + 4) { + return true; + } } } return false; diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/modal/SameModeMoreThanOnceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/modal/SameModeMoreThanOnceTest.java new file mode 100644 index 00000000000..4bdaf851531 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/modal/SameModeMoreThanOnceTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.modal; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class SameModeMoreThanOnceTest extends CardTestPlayerBase { + + @Test + public void testEachModeOnce() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + // Choose three. You may choose the same mode more than once. + // - Target player draws a card and loses 1 life; + // - Target creature gets -2/-2 until end of turn; + // - Return target creature card from your graveyard to your hand. + addCard(Zone.HAND, playerA, "Wretched Confluence"); // Instant {3}{B}{B} + + addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wretched Confluence", "mode=1targetPlayer=PlayerA^mode=2Pillarfield Ox^mode=3Silvercoat Lion"); + setModeChoice(playerA, "1"); + setModeChoice(playerA, "2"); + setModeChoice(playerA, "3"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Wretched Confluence", 1); + assertLife(playerA, 19); + assertLife(playerB, 20); + assertHandCount(playerA, 2); + assertPowerToughness(playerB, "Pillarfield Ox", 0, 2); + assertGraveyardCount(playerA, "Silvercoat Lion", 0); + + } + + @Test + public void testSecondModeTwiceThridModeOnce() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + // Choose three. You may choose the same mode more than once. + // - Target player draws a card and loses 1 life; + // - Target creature gets -2/-2 until end of turn; + // - Return target creature card from your graveyard to your hand. + addCard(Zone.HAND, playerA, "Wretched Confluence"); // Instant {3}{B}{B} + + addCard(Zone.BATTLEFIELD, playerB, "Wall of Air"); + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox"); + addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wretched Confluence", "mode=1Pillarfield Ox^mode=2Wall of Air^mode=3Silvercoat Lion"); + setModeChoice(playerA, "2"); + setModeChoice(playerA, "2"); + setModeChoice(playerA, "3"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Wretched Confluence", 1); + assertLife(playerA, 20); + assertLife(playerB, 20); + assertPowerToughness(playerB, "Wall of Air", -1, 3); + assertPowerToughness(playerB, "Pillarfield Ox", 0, 2); + assertGraveyardCount(playerA, "Silvercoat Lion", 0); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/canttarget/DenseFoliageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/canttarget/DenseFoliageTest.java index 9d7bd0ab72c..2cbe32d2403 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/canttarget/DenseFoliageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/canttarget/DenseFoliageTest.java @@ -35,7 +35,9 @@ public class DenseFoliageTest extends CardTestPlayerBase { */ @Test public void testAbilityCanTarget() { + // Creatures can't be the targets of spells addCard(Zone.BATTLEFIELD, playerA, "Dense Foliage"); + //{T}: Prodigal Sorcerer deals 1 damage to target creature or player. addCard(Zone.BATTLEFIELD, playerA, "Prodigal Sorcerer"); addCard(Zone.BATTLEFIELD, playerB, "Eager Cadet"); diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 4c7fd6698fd..7c554025fd0 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -282,19 +282,14 @@ public class TestPlayer implements Player { Mode selectedMode = null; if (targetName.startsWith("mode=")) { int modeNr = Integer.parseInt(targetName.substring(5, 6)); - if (modeNr == 0 || modeNr > ability.getModes().size()) { + if (modeNr == 0 || modeNr > (ability.getModes().isEachModeMoreThanOnce() ? ability.getModes().getSelectedModes().size() : ability.getModes().size())) { throw new UnsupportedOperationException("Given mode number (" + modeNr + ") not available for " + ability.toString()); } UUID modeId = ability.getModes().getModeId(modeNr); - - for (UUID currentModeId : ability.getModes().getSelectedModes()) { - Mode mode = ability.getModes().get(currentModeId); - if (mode.getId().equals(modeId)) { - selectedMode = mode; - ability.getModes().setActiveMode(mode); - index = 0; // reset target index if mode changes - break; - } + selectedMode = ability.getModes().get(modeId); + if (modeId != ability.getModes().getMode().getId()) { + ability.getModes().setActiveMode(modeId); + index = 0; // reset target index if mode changes } targetName = targetName.substring(6); } else { diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index fc92814a8b6..acba31dad0e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -1131,7 +1131,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * additional by setcode e.g. "creatureName-M15" you can add [no copy] to * the end of the target name to prohibite targets that are copied you can * add [only copy] to the end of the target name to allow only targets that - * are copies + * are copies For modal spells use a prefix with the mode number: + * mode=1Lightning Bolt^mode=2Silvercoat Lion */ public void addTarget(TestPlayer player, String target) { player.addTarget(target); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index c79e37f28bc..cf82795eff0 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -307,8 +307,7 @@ public abstract class AbilityImpl implements Ability { return false; } for (UUID modeId : this.getModes().getSelectedModes()) { - Mode mode = this.getModes().get(modeId); - this.getModes().setActiveMode(mode); + this.getModes().setActiveMode(modeId); //20121001 - 601.2c // 601.2c The player announces his or her choice of an appropriate player, object, or zone for // each target the spell requires. A spell may require some targets only if an alternative or @@ -330,7 +329,7 @@ public abstract class AbilityImpl implements Ability { sourceObject.adjustTargets(this, game); } // Flashback abilities haven't made the choices the underlying spell might need for targetting. - if (!(this instanceof FlashbackAbility) && mode.getTargets().size() > 0 && mode.getTargets().chooseTargets( + if (!(this instanceof FlashbackAbility) && getTargets().size() > 0 && getTargets().chooseTargets( getEffects().get(0).getOutcome(), this.controllerId, this, noMana, game) == false) { if ((variableManaCost != null || announceString != null) && !game.isSimulation()) { game.informPlayer(controller, (sourceObject != null ? sourceObject.getIdName() : "") + ": no valid targets with this value of X"); diff --git a/Mage/src/main/java/mage/abilities/Mode.java b/Mage/src/main/java/mage/abilities/Mode.java index 26942355841..113907d70d7 100644 --- a/Mage/src/main/java/mage/abilities/Mode.java +++ b/Mage/src/main/java/mage/abilities/Mode.java @@ -1,16 +1,16 @@ /* * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. - * + * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR @@ -20,7 +20,7 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. @@ -54,6 +54,10 @@ public class Mode implements Serializable { this.effects = mode.effects.copy(); } + public UUID setRandomId() { + return this.id = UUID.randomUUID(); + } + public Mode copy() { return new Mode(this); } diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index dad2af68c02..a10edeba125 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -55,13 +55,14 @@ public class Modes extends LinkedHashMap { private TargetController modeChooser; private boolean eachModeMoreThanOnce; // each mode can be selected multiple times during one choice private boolean eachModeOnlyOnce; // state if each mode can be chosen only once as long as the source object exists + private final LinkedHashMap duplicateModes = new LinkedHashMap<>(); public Modes() { this.currentMode = new Mode(); this.put(currentMode.getId(), currentMode); this.minModes = 1; this.maxModes = 1; - this.selectedModes.add(currentMode.getId()); + this.addSelectedMode(currentMode.getId()); this.modeChooser = TargetController.YOU; this.eachModeOnlyOnce = false; this.eachModeMoreThanOnce = false; @@ -71,6 +72,9 @@ public class Modes extends LinkedHashMap { for (Map.Entry entry : modes.entrySet()) { this.put(entry.getKey(), entry.getValue().copy()); } + for (Map.Entry entry : modes.duplicateModes.entrySet()) { + this.put(entry.getKey(), entry.getValue().copy()); + } this.minModes = modes.minModes; this.maxModes = modes.maxModes; this.selectedModes.addAll(modes.getSelectedModes()); @@ -78,7 +82,7 @@ public class Modes extends LinkedHashMap { if (modes.getSelectedModes().isEmpty()) { this.currentMode = values().iterator().next(); } else { - this.currentMode = get(selectedModes.get(0)); + this.currentMode = get(modes.getMode().getId()); } this.modeChooser = modes.modeChooser; this.eachModeOnlyOnce = modes.eachModeOnlyOnce; @@ -89,16 +93,41 @@ public class Modes extends LinkedHashMap { return new Modes(this); } + @Override + public Mode get(Object key) { + Mode modeToGet = super.get(key); + if (modeToGet == null && eachModeMoreThanOnce) { + modeToGet = duplicateModes.get(key); + } + return modeToGet; + } + public Mode getMode() { return currentMode; } + /** + * Returns the mode by index. For modal spells with eachModeMoreThanOnce, + * the index returns the n selected mode + * + * @param index + * @return + */ public UUID getModeId(int index) { int idx = 0; - for (Mode mode : this.values()) { - idx++; - if (idx == index) { - return mode.getId(); + if (eachModeMoreThanOnce) { + for (UUID modeId : this.selectedModes) { + idx++; + if (idx == index) { + return modeId; + } + } + } else { + for (Mode mode : this.values()) { + idx++; + if (idx == index) { + return mode.getId(); + } } } return null; @@ -138,6 +167,12 @@ public class Modes extends LinkedHashMap { } } + public void setActiveMode(UUID modeId) { + if (selectedModes.contains(modeId)) { + this.currentMode = get(modeId); + } + } + public void addMode(Mode mode) { this.put(mode.getId(), mode); } @@ -145,6 +180,7 @@ public class Modes extends LinkedHashMap { public boolean choose(Game game, Ability source) { if (this.size() > 1) { this.selectedModes.clear(); + this.duplicateModes.clear(); // check if mode modifying abilities exist Card card = game.getCard(source.getSourceId()); if (card != null) { @@ -163,7 +199,7 @@ public class Modes extends LinkedHashMap { for (Mode mode : this.values()) { if ((!isEachModeOnlyOnce() || onceSelectedModes == null || !onceSelectedModes.contains(mode.getId())) && mode.getTargets().canChoose(source.getSourceId(), source.getControllerId(), game)) { - this.selectedModes.add(mode.getId()); + this.addSelectedMode(mode.getId()); } } if (isEachModeOnlyOnce()) { @@ -200,7 +236,7 @@ public class Modes extends LinkedHashMap { } return this.selectedModes.size() >= this.getMinModes(); } - this.selectedModes.add(choice.getId()); + this.addSelectedMode(choice.getId()); if (currentMode == null) { currentMode = choice; } @@ -209,17 +245,15 @@ public class Modes extends LinkedHashMap { setAlreadySelectedModes(selectedModes, source, game); } return true; + } else { // only one mode + if (currentMode == null) { + this.selectedModes.clear(); + Mode mode = this.values().iterator().next(); + this.addSelectedMode(mode.getId()); + this.setActiveMode(mode); + } + return true; } - if (currentMode == null) { - this.selectedModes.clear(); - Mode copiedMode = this.values().iterator().next().copy(); - this.selectedModes.add(copiedMode.getId()); - this.setActiveMode(copiedMode); - } - if (isEachModeOnlyOnce()) { - setAlreadySelectedModes(selectedModes, source, game); - } - return true; } /** @@ -236,6 +270,23 @@ public class Modes extends LinkedHashMap { } } + /** + * Adds a mode as selected. If the mode is already selected, it copies the + * mode and adds it to the duplicate modes + * + * @param modeId + */ + private void addSelectedMode(UUID modeId) { + if (selectedModes.contains(modeId) && eachModeMoreThanOnce) { + Mode duplicateMode = get(modeId).copy(); + duplicateMode.setRandomId(); + modeId = duplicateMode.getId(); + duplicateModes.put(modeId, duplicateMode); + + } + this.selectedModes.add(modeId); + } + // The already once selected modes for a modal card are stored as a state value // That's important for modal abilities with modes that can only selected once while the object stays in its zone @SuppressWarnings("unchecked") diff --git a/Mage/src/main/java/mage/abilities/effects/common/CantBeTargetedAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CantBeTargetedAllEffect.java index f2ff47c9c4e..40635ee0c37 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CantBeTargetedAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CantBeTargetedAllEffect.java @@ -34,6 +34,7 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.filter.FilterObject; import mage.filter.FilterPermanent; +import mage.filter.FilterSpell; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; @@ -93,6 +94,10 @@ public class CantBeTargetedAllEffect extends ContinuousRuleModifyingEffectImpl { StackObject stackObject = game.getStack().getStackObject(event.getSourceId()); MageObject sourceObject; if (stackObject instanceof StackAbility) { + if (filterSource instanceof FilterSpell) { + // only spells have to be selected + return false; + } sourceObject = ((StackAbility) stackObject).getSourceObject(game); } else { sourceObject = stackObject; diff --git a/Mage/src/main/java/mage/abilities/effects/common/DynamicManaEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DynamicManaEffect.java index 3ab765c7cce..7cfb2118115 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DynamicManaEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DynamicManaEffect.java @@ -146,7 +146,7 @@ public class DynamicManaEffect extends BasicManaEffect { } else { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - ChoiceColor choiceColor = new ChoiceColor(); + ChoiceColor choiceColor = new ChoiceColor(true); for (int i = 0; i < count; i++) { if (!choiceColor.isChosen()) { while (!controller.choose(Outcome.Benefit, choiceColor, game)) { diff --git a/Mage/src/main/java/mage/cards/Sets.java b/Mage/src/main/java/mage/cards/Sets.java index 642bef29bb3..ee5f2ba64a3 100644 --- a/Mage/src/main/java/mage/cards/Sets.java +++ b/Mage/src/main/java/mage/cards/Sets.java @@ -33,6 +33,7 @@ import java.io.PrintWriter; import java.util.*; import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLayout; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardCriteria; import mage.cards.repository.CardInfo; @@ -166,16 +167,47 @@ public class Sets extends HashMap { } } + // Write out all of the cards for (Map.Entry entry: deckCards.entrySet()) { out.printf("%d [%s:%s] %s%n", entry.getValue().getQuantity(), entry.getValue().getSetCode(), entry.getValue().getCardNum(), entry.getValue().getCardName()); } for (Map.Entry entry: sideboard.entrySet()) { out.printf("SB: %d [%s:%s] %s%n", entry.getValue().getQuantity(), entry.getValue().getSetCode(), entry.getValue().getCardNum(), entry.getValue().getCardName()); } + + // Write out the layout + out.print("LAYOUT MAIN:"); + writeCardLayout(out, deck.getCardLayout()); + out.print("\n"); + out.print("LAYOUT SIDEBOARD:"); + writeCardLayout(out, deck.getSideboardLayout()); + out.print("\n"); } finally { out.close(); } } + private static void writeCardLayout(PrintWriter out, DeckCardLayout layout) { + List>> cardGrid = layout.getCards(); + int height = cardGrid.size(); + int width = (height > 0) ? cardGrid.get(0).size() : 0; + out.print("(" + height + "," + width + ")"); + out.print(layout.getSettings()); + out.print("|"); + for (List> row : cardGrid) { + for (List stack : row) { + out.print("("); + for (int i = 0; i < stack.size(); ++i) { + DeckCardInfo info = stack.get(i); + out.printf("[%s:%s]", info.getSetCode(), info.getCardNum()); + if (i != stack.size() - 1) { + out.print(","); + } + } + out.print(")"); + } + } + } + } diff --git a/Mage/src/main/java/mage/cards/decks/Constructed.java b/Mage/src/main/java/mage/cards/decks/Constructed.java index 5851f1ec66c..dc15a1e9cee 100644 --- a/Mage/src/main/java/mage/cards/decks/Constructed.java +++ b/Mage/src/main/java/mage/cards/decks/Constructed.java @@ -48,9 +48,6 @@ public class Constructed extends DeckValidator { protected List setCodes = new ArrayList<>(); protected List rarities = new ArrayList<>(); - protected boolean allowAllCustomSets = false; - protected Set allowedCustomSetCodes = new HashSet<>(); - public Constructed() { super("Constructed"); } @@ -173,12 +170,7 @@ public class Constructed extends DeckValidator { * @return Whether the set is legal in this format. */ protected boolean isSetAllowed(String code) { - // To check here for custom set makes no sens IMHO because the format does define what's aloowed and what not -// if (Sets.isCustomSet(code)) { -// return allowAllCustomSets || allowedCustomSetCodes.contains(code); -// } else { return setCodes.isEmpty() || setCodes.contains(code); -// } } /** diff --git a/Mage/src/main/java/mage/cards/decks/Deck.java b/Mage/src/main/java/mage/cards/decks/Deck.java index 4cd189a1ca2..3ee7dfd1943 100644 --- a/Mage/src/main/java/mage/cards/decks/Deck.java +++ b/Mage/src/main/java/mage/cards/decks/Deck.java @@ -44,6 +44,8 @@ public class Deck implements Serializable { private String name; private final Set cards = new LinkedHashSet<>(); private final Set sideboard = new LinkedHashSet<>(); + private DeckCardLayout cardsLayout; + private DeckCardLayout sideboardLayout; private long deckHashCode = 0; public static Deck load(DeckCardLists deckCardLists) throws GameException { @@ -57,6 +59,8 @@ public class Deck implements Serializable { public static Deck load(DeckCardLists deckCardLists, boolean ignoreErrors, boolean mockCards) throws GameException { Deck deck = new Deck(); deck.setName(deckCardLists.getName()); + deck.cardsLayout = deckCardLists.getCardLayout(); + deck.sideboardLayout = deckCardLists.getSideboardLayout(); List deckCardNames = new ArrayList<>(); for (DeckCardInfo deckCardInfo : deckCardLists.getCards()) { Card card = createCard(deckCardInfo, mockCards); @@ -141,10 +145,18 @@ public class Deck implements Serializable { return cards; } + public DeckCardLayout getCardsLayout() { + return cardsLayout; + } + public Set getSideboard() { return sideboard; } + public DeckCardLayout getSideboardLayout() { + return sideboardLayout; + } + public long getDeckHashCode() { return deckHashCode; } diff --git a/Mage/src/main/java/mage/cards/decks/DeckCardLayout.java b/Mage/src/main/java/mage/cards/decks/DeckCardLayout.java new file mode 100644 index 00000000000..2cbab4ac286 --- /dev/null +++ b/Mage/src/main/java/mage/cards/decks/DeckCardLayout.java @@ -0,0 +1,24 @@ +package mage.cards.decks; + +import java.util.List; + +/** + * Created by stravant@gmail.com on 2016-10-03. + */ +public class DeckCardLayout { + private List>> cards; + private String settings; + + public DeckCardLayout(List>> cards, String settings) { + this.cards = cards; + this.settings = settings; + } + + public List>> getCards() { + return cards; + } + + public String getSettings() { + return settings; + } +} diff --git a/Mage/src/main/java/mage/cards/decks/DeckCardLists.java b/Mage/src/main/java/mage/cards/decks/DeckCardLists.java index 7079d8996bc..2272aa3e114 100644 --- a/Mage/src/main/java/mage/cards/decks/DeckCardLists.java +++ b/Mage/src/main/java/mage/cards/decks/DeckCardLists.java @@ -42,6 +42,26 @@ public class DeckCardLists implements Serializable { private List cards = new ArrayList<>(); private List sideboard = new ArrayList<>(); + // Layout (if supported) + private DeckCardLayout cardLayout = null; + private DeckCardLayout sideboardLayout = null; + + /** + * @return The layout of the cards + */ + public DeckCardLayout getCardLayout() { + return cardLayout; + } + public void setCardLayout(DeckCardLayout layout) { + this.cardLayout = layout; + } + public DeckCardLayout getSideboardLayout() { + return sideboardLayout; + } + public void setSideboardLayout(DeckCardLayout layout) { + this.sideboardLayout = layout; + } + /** * @return the cards */ diff --git a/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java b/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java index bb934de2879..38ebb24e46a 100644 --- a/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java +++ b/Mage/src/main/java/mage/cards/decks/importer/DckDeckImporter.java @@ -27,9 +27,12 @@ */ package mage.cards.decks.importer; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import mage.cards.decks.DeckCardInfo; +import mage.cards.decks.DeckCardLayout; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; @@ -42,6 +45,12 @@ public class DckDeckImporter extends DeckImporter { private static final Pattern pattern = Pattern.compile("(SB:)?\\s*(\\d*)\\s*\\[([^]:]+):([^]:]+)\\].*"); + private static final Pattern layoutPattern = Pattern.compile("LAYOUT (\\w+):\\((\\d+),(\\d+)\\)([^|]+)\\|(.*)$"); + + private static final Pattern layoutStackPattern = Pattern.compile("\\(([^)]*)\\)"); + + private static final Pattern layoutStackEntryPattern = Pattern.compile("\\[(\\w+):(\\w+)]"); + @Override protected void readLine(String line, DeckCardLists deckList) { @@ -79,6 +88,56 @@ public class DckDeckImporter extends DeckImporter { deckList.setName(line.substring(5, line.length())); } else if (line.startsWith("AUTHOR:")) { deckList.setAuthor(line.substring(7, line.length())); + } else if (line.startsWith("LAYOUT")) { + Matcher m2 = layoutPattern.matcher(line); + if (m2.find()) { + String target = m2.group(1); + int rows = Integer.parseInt(m2.group(2)); + int cols = Integer.parseInt(m2.group(3)); + String settings = m2.group(4); + String stackData = m2.group(5); + Matcher stackMatcher = layoutStackPattern.matcher(stackData); + // + List>> grid = new ArrayList<>(); + int totalCardCount = 0; + for (int row = 0; row < rows; ++row) { + List> rowData = new ArrayList<>(); + grid.add(rowData); + for (int col = 0; col < cols; ++col) { + List stack = new ArrayList<>(); + rowData.add(stack); + if (stackMatcher.find()) { + String thisStackData = stackMatcher.group(1); + Matcher stackEntries = layoutStackEntryPattern.matcher(thisStackData); + while (stackEntries.find()) { + ++totalCardCount; + stack.add(new DeckCardInfo("", stackEntries.group(2), stackEntries.group(1))); + } + } else { + sbMessage.append("Missing stack\n."); + } + } + } + // + DeckCardLayout layout = new DeckCardLayout(grid, settings); + int expectedCount = 0; + if (target.equals("MAIN")) { + deckList.setCardLayout(layout); + expectedCount = deckList.getCards().size(); + } else if (target.equals("SIDEBOARD")) { + deckList.setSideboardLayout(layout); + expectedCount = deckList.getSideboard().size(); + } else { + sbMessage.append("Bad target `" + target + "` for layout.\n"); + } + // + if (totalCardCount != expectedCount) { + sbMessage.append("Layout mismatch: Expected " + expectedCount + " cards, but got " + totalCardCount + " in layout `" + target + "`\n."); + } + + } else { + sbMessage.append("Malformed layout line"); + } } } } diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index bffef3e5ba2..69dfb83d4be 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -211,9 +211,8 @@ public class Spell extends StackObjImpl implements Card { for (SpellAbility spellAbility : this.spellAbilities) { if (spellAbilityHasLegalParts(spellAbility, game)) { for (UUID modeId : spellAbility.getModes().getSelectedModes()) { - Mode mode = spellAbility.getModes().get(modeId); - spellAbility.getModes().setActiveMode(mode); - if (mode.getTargets().stillLegal(spellAbility, game)) { + spellAbility.getModes().setActiveMode(modeId); + if (spellAbility.getTargets().stillLegal(spellAbility, game)) { if (!spellAbility.getSpellAbilityType().equals(SpellAbilityType.SPLICE)) { updateOptionalCosts(index); }