From 9dc71d299669b86f2290029bb1b86942960b7386 Mon Sep 17 00:00:00 2001 From: Simown Date: Fri, 11 Mar 2016 11:22:56 +0000 Subject: [PATCH 01/44] Added advanced deck generator functions and tidied up the layout. --- .../client/deck/generator/DeckGenerator.java | 22 +- .../deck/generator/DeckGeneratorCMC.java | 125 +++++--- .../deck/generator/DeckGeneratorDialog.java | 281 +++++++++++++++--- .../deck/generator/DeckGeneratorPool.java | 138 +++++---- .../generator/RatioAdjustingSliderPanel.java | 248 ++++++++++++++++ .../mage/client/dialog/PreferencesDialog.java | 6 + 6 files changed, 665 insertions(+), 155 deletions(-) create mode 100644 Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java index 5f654e18b75..b7738c5a6f6 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java @@ -155,7 +155,10 @@ public class DeckGenerator { * @return the final deck to use. */ private static Deck generateDeck(int deckSize, List allowedColors, List setsToUse) { - genPool = new DeckGeneratorPool(deckSize, allowedColors, genDialog.isSingleton(), genDialog.isColorless()); + + genPool = new DeckGeneratorPool(deckSize, genDialog.getCreaturePercentage(), genDialog.getNonCreaturePercentage(), + genDialog.getLandPercentage(), allowedColors, genDialog.isSingleton(), genDialog.isColorless(), + genDialog.isAdvanced(), genDialog.getDeckGeneratorCMC()); final String[] sets = setsToUse.toArray(new String[setsToUse.size()]); @@ -210,7 +213,7 @@ public class DeckGenerator { private static void generateSpells(CardCriteria criteria, int spellCount) { List cardPool = CardRepository.instance.findCards(criteria); int retrievedCount = cardPool.size(); - List deckCMCs = genPool.getCMCsForSpellCount(spellCount); + List deckCMCs = genPool.getCMCsForSpellCount(spellCount); Random random = new Random(); int count = 0; int reservesAdded = 0; @@ -221,7 +224,7 @@ public class DeckGenerator { Card card = cardPool.get(random.nextInt(retrievedCount)).getMockCard(); if (genPool.isValidSpellCard(card)) { int cardCMC = card.getManaCost().convertedManaCost(); - for (DeckGeneratorCMC deckCMC : deckCMCs) { + for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) { if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) { int currentAmount = deckCMC.getAmount(); if (currentAmount > 0) { @@ -339,8 +342,9 @@ public class DeckGenerator { * database. */ private static void addBasicLands(int landsNeeded, Map percentage, Map count, Map> basicLands) { + int colorTotal = 0; - ColoredManaSymbol colorToAdd = null; + ColoredManaSymbol colorToAdd = ColoredManaSymbol.U; // Add up the totals for all colors, to keep track of the percentage a color is. for (Map.Entry c : count.entrySet()) { @@ -372,12 +376,10 @@ public class DeckGenerator { minPercentage = (neededPercentage - thisPercentage); } } - if (colorToAdd != null) { - genPool.addCard(getBasicLand(colorToAdd, basicLands)); - count.put(colorToAdd.toString(), count.get(colorToAdd.toString()) + 1); - colorTotal++; - landsNeeded--; - } + genPool.addCard(getBasicLand(colorToAdd, basicLands)); + count.put(colorToAdd.toString(), count.get(colorToAdd.toString()) + 1); + colorTotal++; + landsNeeded--; } } diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorCMC.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorCMC.java index 06ceac14c42..e56f2d8a61f 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorCMC.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorCMC.java @@ -27,44 +27,101 @@ */ package mage.client.deck.generator; -/** - * Stores a range of converted mana costs (CMC) for use in deck generation. - */ -public class DeckGeneratorCMC -{ - public final int min; - public final int max; - public final float percentage; - private int amount = 0; +import java.util.ArrayList; - /** - * Constructs a CMC range given a minimum and maximum, and the percentage of cards that are in this range. - * @param min the minimum CMC a card in this range can be. - * @param max the maximum CMC a card in this range can be. - * @param percentage the percentage of cards in the range (min, max) - */ - DeckGeneratorCMC(int min, int max, float percentage) - { - this.min = min; - this.max = max; - this.percentage = percentage; +public enum DeckGeneratorCMC { + + Low( + new ArrayList() {{ + add(new CMC(0, 2, 0.60f)); + add(new CMC(3, 4, 0.30f)); + add(new CMC(5, 6, 0.10f)); + }}, + new ArrayList() {{ + add(new CMC(0, 2, 0.65f)); + add(new CMC(3, 4, 0.30f)); + add(new CMC(5, 5, 0.05f)); + }}), + Default( + new ArrayList() {{ + add(new CMC(0, 2, 0.20f)); + add(new CMC(3, 5, 0.50f)); + add(new CMC(6, 7, 0.25f)); + add(new CMC(8, 100, 0.05f)); + }}, + new ArrayList() {{ + add(new CMC(0, 2, 0.30f)); + add(new CMC(3, 4, 0.45f)); + add(new CMC(5, 6, 0.20f)); + add(new CMC(7, 100, 0.05f)); + }}), + High( + new ArrayList() {{ + add(new CMC(0, 2, 0.05f)); + add(new CMC(3, 5, 0.35f)); + add(new CMC(6, 7, 0.40f)); + add(new CMC(8, 100, 0.15f)); + }}, + new ArrayList() {{ + add(new CMC(0, 2, 0.10f)); + add(new CMC(3, 4, 0.30f)); + add(new CMC(5, 6, 0.45f)); + add(new CMC(7, 100, 0.15f)); + }}); + + private ArrayList poolCMCs60, poolCMCs40; + + DeckGeneratorCMC(ArrayList CMCs60, ArrayList CMCs40) { + this.poolCMCs60 = CMCs60; + this.poolCMCs40 = CMCs40; } - /** - * Sets the amount of cards needed in this CMC range. - * @param amount the number of cards needed. - */ - public void setAmount(int amount) - { - this.amount = amount; + public ArrayList get40CardPoolCMC() { + return this.poolCMCs40; } - /** - * Gets the number of cards needed in this CMC range. - * @return the number of cards needed in this CMC range. - */ - public int getAmount() - { - return this.amount; + public ArrayList get60CardPoolCMC() { + return this.poolCMCs60; } + + static class CMC + { + public final int min; + public final int max; + public final float percentage; + private int amount = 0; + + /** + * Constructs a CMC range given a minimum and maximum, and the percentage of cards that are in this range. + * @param min the minimum CMC a card in this range can be. + * @param max the maximum CMC a card in this range can be. + * @param percentage the percentage of cards in the range (min, max) + */ + CMC(int min, int max, float percentage) + { + this.min = min; + this.max = max; + this.percentage = percentage; + } + + /** + * Sets the amount of cards needed in this CMC range. + * @param amount the number of cards needed. + */ + public void setAmount(int amount) + { + this.amount = amount; + } + + /** + * Gets the number of cards needed in this CMC range. + * @return the number of cards needed in this CMC range. + */ + public int getAmount() + { + return this.amount; + } + } + } + diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java index 0c0ca158507..3d541a75eb7 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java @@ -35,9 +35,14 @@ import mage.client.util.gui.ColorsChooser; import mage.client.util.sets.ConstructedFormats; import javax.swing.*; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.EtchedBorder; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; @@ -48,13 +53,14 @@ import java.util.Date; */ public class DeckGeneratorDialog { - private JDialog dlg; - private String selectedColors; - private JComboBox cbSets; - private JComboBox cbDeckSize; - private JButton btnGenerate, btnCancel; - private JCheckBox cArtifacts, cSingleton, cNonBasicLands, cColorless; - private SimpleDateFormat dateFormat; + private static JDialog dlg; + private static String selectedColors; + private static JComboBox cbSets, cbDeckSize, cbCMC; + private static JButton btnGenerate, btnCancel, btnReset; + private static JCheckBox cArtifacts, cSingleton, cNonBasicLands, cColorless, cAdvanced; + private static JLabel averageCMCLabel; + private static SimpleDateFormat dateFormat; + private static RatioAdjustingSliderPanel adjustingSliderPanel; public DeckGeneratorDialog() { @@ -63,59 +69,92 @@ public class DeckGeneratorDialog { } private void initDialog() { - JPanel p0 = new JPanel(); - p0.setLayout(new BoxLayout(p0, BoxLayout.Y_AXIS)); - JLabel text = new JLabel("Choose color for your deck: "); - text.setAlignmentX(Component.CENTER_ALIGNMENT); - p0.add(text); + JPanel mainPanel = new JPanel(); - p0.add(Box.createVerticalStrut(5)); + mainPanel.setLayout(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(0, 15, 0, 0); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 0; + c.gridy = 0; + c.weightx = 0.10; + JLabel text = new JLabel("Choose color for your deck:"); + mainPanel.add(text, c); + + // Color selector dropdown + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 0.80; + c.ipadx = 30; + c.insets = new Insets(5, 10, 0, 10); + c.gridx = 1; + c.gridy = 0; String chosen = MageFrame.getPreferences().get("genDeckColor", "u"); final ColorsChooser colorsChooser = new ColorsChooser(chosen); - p0.add(colorsChooser); + mainPanel.add(colorsChooser, c); - p0.add(Box.createVerticalStrut(5)); - JLabel text2 = new JLabel("(X - random color)"); - text2.setAlignmentX(Component.CENTER_ALIGNMENT); - p0.add(text2); + c.insets = new Insets(0, 15, 0, 0); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 0.10; + c.gridx = 2; + c.gridy = 0; + c.ipadx = 0; + JLabel text2 = new JLabel("(X = random color)"); + mainPanel.add(text2); - p0.add(Box.createVerticalStrut(5)); - JPanel jPanel = new JPanel(); - JLabel text3 = new JLabel("Choose sets:"); - cbSets = new JComboBox(ConstructedFormats.getTypes()); + // Format/set label + JLabel formatSetText = new JLabel("Choose format/set for your deck:"); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 0; + c.gridy = 1; + c.weightx = 0.10; + mainPanel.add(formatSetText, c); + + // Format/set dropdown + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 1; + c.gridy = 1; + c.ipadx = 30; + c.insets = new Insets(5, 10, 0, 10); + c.weightx = 0.90; + cbSets = new JComboBox<>(ConstructedFormats.getTypes()); cbSets.setSelectedIndex(0); - cbSets.setPreferredSize(new Dimension(300, 25)); - cbSets.setMaximumSize(new Dimension(300, 25)); - cbSets.setAlignmentX(Component.LEFT_ALIGNMENT); - jPanel.add(text3); - jPanel.add(cbSets); - p0.add(jPanel); + mainPanel.add(cbSets, c); String prefSet = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SET, null); if (prefSet != null) { cbSets.setSelectedItem(prefSet); } - p0.add(Box.createVerticalStrut(5)); - JPanel jPanel2 = new JPanel(); + // Deck size label + c.fill = GridBagConstraints.HORIZONTAL; + c.insets = new Insets(0, 15, 0, 0); + c.ipadx = 0; + c.gridx = 0; + c.gridy = 2; + c.weightx = 0.10; JLabel textDeckSize = new JLabel("Deck size:"); - cbDeckSize = new JComboBox(new String[] { "40", "60" }); + mainPanel.add(textDeckSize, c); + + // Deck size dropdown + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 1; + c.gridy = 2; + c.ipadx = 30; + c.insets = new Insets(5, 10, 0, 10); + c.weightx = 0.90; + cbDeckSize = new JComboBox<>(new String[] { "40", "60" }); cbDeckSize.setSelectedIndex(0); - cbDeckSize.setPreferredSize(new Dimension(300, 25)); - cbDeckSize.setMaximumSize(new Dimension(300, 25)); cbDeckSize.setAlignmentX(Component.LEFT_ALIGNMENT); - jPanel2.add(textDeckSize); - jPanel2.add(cbDeckSize); - p0.add(jPanel2); + mainPanel.add(cbDeckSize, c); + String prefSize = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_DECK_SIZE, "60"); if (prefSet != null) { cbDeckSize.setSelectedItem(prefSize); } - p0.add(Box.createVerticalStrut(5)); - JPanel jCheckBoxes = new JPanel(); + JPanel jCheckBoxes = new JPanel(new FlowLayout(FlowLayout.LEFT)); // Singletons cSingleton = new JCheckBox("Singleton", false); @@ -138,16 +177,47 @@ public class DeckGeneratorDialog { cNonBasicLands.setSelected(Boolean.valueOf(nonBasicEnabled)); jCheckBoxes.add(cNonBasicLands); - // Non-basic lands + // Colorless mana cColorless = new JCheckBox("Colorless mana", false); cColorless.setToolTipText("Allow cards with colorless mana cost."); String colorlessEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORLESS, "false"); cColorless.setSelected(Boolean.valueOf(colorlessEnabled)); jCheckBoxes.add(cColorless); + c.ipadx = 0; + c.gridx = 0; + c.gridy = 3; + c.weightx = 1; + c.gridwidth = 3; + mainPanel.add(jCheckBoxes, c); + + // Create the advanced configuration panel + JPanel advancedPanel = createAdvancedPanel(); + + // Advanced checkbox (enable/disable advanced configuration) + cAdvanced = new JCheckBox("Advanced"); + cAdvanced.setToolTipText("Enable advanced configuration options"); + cAdvanced.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent itemEvent) { + boolean enable = cAdvanced.isSelected(); + enableAdvancedPanel(enable); + } + }); + + // Advanced Checkbox + String advancedSavedValue = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED, "false"); + boolean advancedEnabled = Boolean.valueOf(advancedSavedValue); + enableAdvancedPanel(advancedEnabled); + cAdvanced.setSelected(advancedEnabled); + c.gridy = 4; + c.weightx = 0; + c.insets = new Insets(10, 15, 10, 0); + mainPanel.add(cAdvanced, c); + c.gridy = 5; + c.weightx = 1; + c.insets = new Insets(5, 10, 0, 5); + mainPanel.add(advancedPanel, c); - jCheckBoxes.setPreferredSize(new Dimension(450, 25)); - jCheckBoxes.setMaximumSize(new Dimension(450, 25)); - p0.add(jCheckBoxes); btnGenerate = new JButton("Ok"); btnGenerate.addActionListener(new ActionListener() { @@ -169,12 +239,95 @@ public class DeckGeneratorDialog { } }); JButton[] options = {btnGenerate, btnCancel}; - JOptionPane optionPane = new JOptionPane(p0, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, options, options[1]); - dlg = optionPane.createDialog("Generating deck"); + JOptionPane optionPane = new JOptionPane(mainPanel, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, options, options[1]); + dlg = optionPane.createDialog("Generating Deck"); + dlg.setResizable(false); dlg.setVisible(true); dlg.dispose(); } + private void enableAdvancedPanel(boolean enable) { + adjustingSliderPanel.setEnabled(enable); + btnReset.setEnabled(enable); + cbCMC.setEnabled(enable); + averageCMCLabel.setEnabled(enable); + } + + private JPanel createAdvancedPanel() { + + JPanel advancedPanel = new JPanel(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + + // Average CMC Label + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 0; + c.gridy = 0; + c.weightx = 0.10; + averageCMCLabel = new JLabel("Average CMC:"); + advancedPanel.add(averageCMCLabel, c); + + // CMC selection dropdown + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 0.90; + c.gridx = 2; + c.gridy = 0; + cbCMC = new JComboBox<>(DeckGeneratorCMC.values()); + cbCMC.setSelectedItem(DeckGeneratorCMC.Default); + String cmcSelected = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED_CMC, DeckGeneratorCMC.Default.name()); + cbCMC.setSelectedItem(DeckGeneratorCMC.valueOf(cmcSelected)); + advancedPanel.add(cbCMC, c); + + // Advanced percentage sliders + c.fill = GridBagConstraints.HORIZONTAL; + c.ipady = 20; + c.ipadx = 40; + c.weightx = 1; + c.gridwidth = 3; + c.gridx = 0; + c.gridy = 1; + c.insets = new Insets(10, 0, 0, 0); + adjustingSliderPanel = new RatioAdjustingSliderPanel(); + + // Restore saved slider values + String creaturePercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE, + Integer.toString(DeckGeneratorPool.DEFAULT_CREATURE_PERCENTAGE)); + adjustingSliderPanel.setCreaturePercentage(Integer.parseInt(creaturePercentage)); + String nonCreaturePercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE, + Integer.toString(DeckGeneratorPool.DEFAULT_NON_CREATURE_PERCENTAGE)); + adjustingSliderPanel.setNonCreaturePercentage(Integer.parseInt(nonCreaturePercentage)); + String landPercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_LAND_PERCENTAGE, + Integer.toString(DeckGeneratorPool.DEFAULT_LAND_PERCENTAGE)); + adjustingSliderPanel.setLandPercentage(Integer.parseInt(landPercentage)); + advancedPanel.add(adjustingSliderPanel, c); + + // Reset + c.fill = GridBagConstraints.NONE; + c.ipadx = 0; + c.ipady = 0; + c.weightx = 1.0; + c.anchor = GridBagConstraints.LAST_LINE_END; + c.insets = new Insets(10,10, 0, 0); + c.gridx = 2; + c.gridwidth = 1; + c.gridy = 2; + btnReset = new JButton("Reset"); + btnReset.setToolTipText("Reset advanced dialog to default values"); + btnReset.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + cbCMC.setSelectedItem(DeckGeneratorCMC.Default); + adjustingSliderPanel.resetValues(); + } + }); + advancedPanel.add(btnReset, c); + + // Add a border around the advanced bits + CompoundBorder border = BorderFactory.createCompoundBorder(new EtchedBorder(), new EmptyBorder(10, 10, 10, 10)); + advancedPanel.setBorder(border); + + return advancedPanel; + } + public void cleanUp() { for (ActionListener al: btnGenerate.getActionListeners()) { btnGenerate.removeActionListener(al); @@ -182,6 +335,12 @@ public class DeckGeneratorDialog { for (ActionListener al: btnCancel.getActionListeners()) { btnCancel.removeActionListener(al); } + for (ActionListener al: btnReset.getActionListeners()) { + btnReset.removeActionListener(al); + } + for(ItemListener il: cAdvanced.getItemListeners()) { + cAdvanced.removeItemListener(il); + } } public String saveDeck(Deck deck) { @@ -189,7 +348,7 @@ public class DeckGeneratorDialog { // Random directory through the system property to avoid random numeric string attached to temp files. String tempDir = System.getProperty("java.io.tmpdir"); // Generated deck has a nice unique name which corresponds to the timestamp at which it was created. - String deckName = "Generated-Deck-" + dateFormat.format( new Date()); + String deckName = "Generated-Deck-" + dateFormat.format(new Date()); File tmp = new File(tempDir + File.separator + deckName + ".dck"); tmp.createNewFile(); deck.setName(deckName); @@ -230,10 +389,42 @@ public class DeckGeneratorDialog { return selected; } + public boolean isAdvanced() { + boolean selected = cAdvanced.isSelected(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED, Boolean.toString(selected)); + return selected; + } + + public int getCreaturePercentage() { + int percentage = adjustingSliderPanel.getCreaturePercentage(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE, Integer.toString(percentage)); + return percentage; + } + + public int getNonCreaturePercentage() { + int percentage = adjustingSliderPanel.getNonCreaturePercentage(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE, Integer.toString(percentage)); + return percentage; + } + + public int getLandPercentage() { + int percentage = adjustingSliderPanel.getLandPercentage(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_LAND_PERCENTAGE, Integer.toString(percentage)); + return percentage; + } + public int getDeckSize() { return Integer.parseInt(cbDeckSize.getSelectedItem().toString()); } + public DeckGeneratorCMC getDeckGeneratorCMC() { + DeckGeneratorCMC selectedCMC = (DeckGeneratorCMC)cbCMC.getSelectedItem(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED_CMC, selectedCMC.name()); + return selectedCMC; + } + + + public String getSelectedColors() { if (selectedColors != null) { PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_DECK_SIZE, cbDeckSize.getSelectedItem().toString()); diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java index 849a31997d1..7c7ff556f18 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java @@ -41,18 +41,15 @@ import java.util.*; */ public class DeckGeneratorPool { - // Constants for a 40 card deck - private static final int CREATURE_COUNT_40 = 15; - private static final int LAND_COUNT_40 = 17; - private static final int NONCREATURE_COUNT_40 = 8; - // Constants for a 60 card deck - private static final int CREATURE_COUNT_60 = 23; - private static final int LAND_COUNT_60 = 24; - private static final int NONCREATURE_COUNT_60 = 13; + + + public static int DEFAULT_CREATURE_PERCENTAGE = 38; + public static int DEFAULT_NON_CREATURE_PERCENTAGE = 21; + public static int DEFAULT_LAND_PERCENTAGE = 41; private final List allowedColors; private boolean colorlessAllowed; - private final List poolCMCs; + private final List poolCMCs; private final int creatureCount; private final int nonCreatureCount; private final int landCount; @@ -69,7 +66,21 @@ public class DeckGeneratorPool private List reserveSpells = new ArrayList<>(); private Deck deck; - public DeckGeneratorPool(final int deckSize, final List allowedColors, boolean isSingleton, boolean colorlessAllowed) + /** + * Creates a card pool with specified criterea used when generating a deck. + * + * @param deckSize the size of the complete deck + * @param creaturePercentage what percentage of creatures to use when generating the deck. + * @param nonCreaturePercentage percentage of non-creatures to use when generating the deck. + * @param landPercentage percentage of lands to use when generating the deck. + * @param allowedColors which card colors are allowed in the generated deck. + * @param isSingleton if the deck only has 1 copy of each non-land card. + * @param colorlessAllowed if colourless mana symbols are allowed in costs in the deck. + * @param isAdvanced if the user has provided advanced options to generate the deck. + * @param deckGeneratorCMC the CMC curve to use for this deck + */ + public DeckGeneratorPool(final int deckSize, final int creaturePercentage, final int nonCreaturePercentage, final int landPercentage, + final List allowedColors, boolean isSingleton, boolean colorlessAllowed, boolean isAdvanced, DeckGeneratorCMC deckGeneratorCMC) { this.deckSize = deckSize; this.allowedColors = allowedColors; @@ -78,28 +89,26 @@ public class DeckGeneratorPool this.deck = new Deck(); - if(this.deckSize > 40) { - this.creatureCount = CREATURE_COUNT_60; - this.nonCreatureCount = NONCREATURE_COUNT_60; - this.landCount = LAND_COUNT_60; - poolCMCs = new ArrayList() {{ - add(new DeckGeneratorCMC(0, 2, 0.20f)); - add(new DeckGeneratorCMC(3, 5, 0.50f)); - add(new DeckGeneratorCMC(6, 7, 0.25f)); - add(new DeckGeneratorCMC(8, 100, 0.05f)); - }}; - - } - else { - this.creatureCount = CREATURE_COUNT_40; - this.nonCreatureCount = NONCREATURE_COUNT_40; - this.landCount = LAND_COUNT_40; - poolCMCs = new ArrayList() {{ - add(new DeckGeneratorCMC(0, 2, 0.30f)); - add(new DeckGeneratorCMC(3, 4, 0.45f)); - add(new DeckGeneratorCMC(5, 6, 0.20f)); - add(new DeckGeneratorCMC(7, 100, 0.05f)); - }}; + // Advanced (CMC Slider panel and curve drop-down in the dialog) + if(isAdvanced) { + this.creatureCount = (int)Math.ceil((deckSize / 100.0) * creaturePercentage); + this.nonCreatureCount = (int)Math.ceil((deckSize / 100.0)* nonCreaturePercentage); + this.landCount = (int)Math.ceil((deckSize / 100.0)* landPercentage); + if(this.deckSize == 60) { + this.poolCMCs = deckGeneratorCMC.get60CardPoolCMC(); + } else { + this.poolCMCs = deckGeneratorCMC.get40CardPoolCMC(); + } + } else { + // Ignore the advanced group, just use defaults + this.creatureCount = (int)Math.ceil((deckSize / 100.0) * DEFAULT_CREATURE_PERCENTAGE); + this.nonCreatureCount = (int)Math.ceil((deckSize / 100.0) * DEFAULT_NON_CREATURE_PERCENTAGE); + this.landCount = (int)Math.ceil((deckSize / 100.0) * DEFAULT_LAND_PERCENTAGE); + if(this.deckSize == 60) { + this.poolCMCs = DeckGeneratorCMC.Default.get60CardPoolCMC(); + } else { + this.poolCMCs = DeckGeneratorCMC.Default.get40CardPoolCMC(); + } } if(allowedColors.size() == 1) { @@ -114,16 +123,15 @@ public class DeckGeneratorPool * @param cardsCount the number of total cards. * @return a list of CMC ranges, with the amount of cards for each CMC range */ - public List getCMCsForSpellCount(int cardsCount) { - List adjustedCMCs = new ArrayList<>(this.poolCMCs); + public List getCMCsForSpellCount(int cardsCount) { + List adjustedCMCs = new ArrayList<>(this.poolCMCs); // For each CMC calculate how many spell cards are needed, given the total amount of cards - for(DeckGeneratorCMC deckCMC : adjustedCMCs) { + for(DeckGeneratorCMC.CMC deckCMC : adjustedCMCs) { deckCMC.setAmount((int)Math.ceil(deckCMC.percentage * cardsCount)); } return adjustedCMCs; } - /** * Verifies if the spell card supplied is valid for this pool of cards. * Checks that there isn't too many copies of this card in the deck. @@ -391,45 +399,54 @@ public class DeckGeneratorPool int spellsNeeded = nonLandSize-spellSize; - // If we haven't got enough spells in reserve to fulfil the amount we need, we can't continue. - if(reserveSpells.size() < spellsNeeded) { - throw new IllegalStateException("Not enough cards found to generate deck. Please try again"); - } + // If we haven't got enough spells in reserve to fulfil the amount we need, skip adding any. + if(reserveSpells.size() >= spellsNeeded) { - List spellsToAdd = new ArrayList<>(spellsNeeded); + List spellsToAdd = new ArrayList<>(spellsNeeded); - // Initial reservoir - for(int i = 0; i < spellsNeeded; i++) - spellsToAdd.add(reserveSpells.get(i)); + // Initial reservoir + for (int i = 0; i < spellsNeeded; i++) + spellsToAdd.add(reserveSpells.get(i)); - for(int i = spellsNeeded+1; i < reserveSpells.size()-1; i++) { - int j = random.nextInt(i); - Card randomCard = reserveSpells.get(j); - if (isValidSpellCard(randomCard) && j < spellsToAdd.size()) { - spellsToAdd.set(j, randomCard); + for (int i = spellsNeeded + 1; i < reserveSpells.size() - 1; i++) { + int j = random.nextInt(i); + Card randomCard = reserveSpells.get(j); + if (isValidSpellCard(randomCard) && j < spellsToAdd.size()) { + spellsToAdd.set(j, randomCard); + } } + // Add randomly selected spells needed + deckCards.addAll(spellsToAdd); } - // Add randomly selected spells needed - deckCards.addAll(spellsToAdd); } + // More spells than needed else if(spellSize > (deckSize - landCount)) { - int spellsRemoved = (spellSize)-(deckSize-landCount); for(int i = 0; i < spellsRemoved; ++i) { deckCards.remove(random.nextInt(deckCards.size())); } } - // Not strictly necessary as we check when adding cards, but worth double checking anyway. + // Check we have exactly the right amount of cards for a deck. if(deckCards.size() != nonLandSize) { - throw new IllegalStateException("Not enough cards found to generate deck. Please try again"); + throw new IllegalStateException("Not enough cards found to generate deck."); } - // Return the fixed amount return deckCards; } + /** + * Returns if this land taps for the given color. + * Basic string matching to check the ability adds one of the chosen mana when tapped. + * @param ability MockAbility of the land card + * @param symbol colored mana symbol. + * @return if the ability is tapping to produce the mana the symbol represents. + */ + private boolean landTapsForAllowedColor(String ability, String symbol) { + return ability.matches(".*Add \\{" + symbol + "\\} to your mana pool."); + } + /** * Returns if this land will produce the chosen colors for this pool. * @param card a non-basic land card. @@ -455,17 +472,6 @@ public class DeckGeneratorPool return false; } - /** - * Returns if this land taps for the given color. - * Basic string matching to check the ability adds one of the chosen mana when tapped. - * @param ability MockAbility of the land card - * @param symbol colored mana symbol. - * @return if the ability is tapping to produce the mana the symbol represents. - */ - private boolean landTapsForAllowedColor(String ability, String symbol) { - return ability.matches(".*Add \\{" + symbol + "\\} to your mana pool."); - } - /** * Returns if the symbol is a colored mana symbol. * @param symbol the symbol to check. diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java new file mode 100644 index 00000000000..665fdbbfef5 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java @@ -0,0 +1,248 @@ +/* + * 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 mage.client.deck.generator; + +import mage.client.deck.generator.DeckGeneratorPool; + +import javax.swing.*; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.EtchedBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.util.*; +import java.util.List; + +/** + * @author Simown + */ +public class RatioAdjustingSliderPanel extends JPanel { + + private JStorageSlider creatureSlider, nonCreatureSlider, landSlider; + private List textLabels = new ArrayList<>(); + private AdjustingSliderGroup sg; + + private class JStorageSlider extends JSlider { + + // Slider stores its initial value to revert to when reset + private int defaultValue; + private int previousValue; + + public JStorageSlider(int min, int max, int value) { + super(min, max, value); + previousValue = value; + defaultValue = value; + setMinorTickSpacing(5); + setMajorTickSpacing(10); + setPaintTicks(true); + setPaintLabels(true); + setLabelTable(createStandardLabels(10)); + } + + public int getPreviousValue() { + return previousValue; + } + + public void setPreviousValue(int value) { + previousValue = value; + } + + public void resetDefault() { + this.setValue(defaultValue); + previousValue = defaultValue; + } + + } + + private class AdjustingSliderGroup + { + private final ArrayList storageSliders; + private int sliderIndex = 0; + + AdjustingSliderGroup(JStorageSlider... sliders) + { + storageSliders = new ArrayList<>(); + for(JStorageSlider slider: sliders) { + storageSliders.add(slider); + slider.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + fireSliderChangedEvent((JStorageSlider) e.getSource()); + } + }); + } + } + public void fireSliderChangedEvent(JStorageSlider source) { + // We don't want to do anything if the value isn't changing + if(!source.getValueIsAdjusting()) + return; + // Update the slider depending on how much it's changed relative to its previous position + int change = (source.getValue() - source.getPreviousValue()); + updateSliderPosition(change, source); + } + + private void updateSliderPosition(int change, JStorageSlider source) { + int remaining = change; + while (remaining != 0) { + // Get the currently indexed slider + JStorageSlider slider = storageSliders.get(sliderIndex); + // If it's not the slider that fired the event + if (slider != source) { + // Check we don't go over the upper and lower bounds + if (remaining < 0 || (remaining > 0 && slider.getValue() > 0)) { + // Adjust the currently selected slider by +/- 1 + int adjustment = Integer.signum(remaining); + slider.setValue(slider.getValue() - adjustment); + remaining -= adjustment; + } + } + // Select the next slider in the list of sliders + sliderIndex = (sliderIndex + 1) % storageSliders.size(); + } + for (JStorageSlider slider : storageSliders) { + slider.setPreviousValue(slider.getValue()); + } + } + + List getSliders() { + return storageSliders; + } + + } + + public RatioAdjustingSliderPanel() { + initPanel(); + } + + private void initPanel() { + + // Create three sliders with default values + creatureSlider = new JStorageSlider(0, 100, DeckGeneratorPool.DEFAULT_CREATURE_PERCENTAGE); + nonCreatureSlider = new JStorageSlider(0, 100, DeckGeneratorPool.DEFAULT_NON_CREATURE_PERCENTAGE); + landSlider = new JStorageSlider(0, 100, DeckGeneratorPool.DEFAULT_LAND_PERCENTAGE); + + sg = new AdjustingSliderGroup(creatureSlider, nonCreatureSlider, landSlider); + + this.setLayout(new GridLayout(3, 1)); + + this.add(createSliderPanel("Creatures ", creatureSlider)); + this.add(createSliderPanel("Non-creatures ", nonCreatureSlider)); + this.add(createSliderPanel("Lands ", landSlider)); + + setEnabled(true); + } + + private JPanel createSliderPanel(String label, JStorageSlider slider) { + + JPanel sliderPanel = new JPanel(new BorderLayout()); + + // Title + JLabel titleLabel = new JLabel(label); + textLabels.add(titleLabel); + sliderPanel.add(titleLabel, BorderLayout.WEST); + // Slider + slider.setToolTipText("Percentage of " + label.trim().toLowerCase() + " in the generated deck."); + sliderPanel.add(slider, BorderLayout.CENTER); + // Percentage + JLabel percentageLabel = createChangingPercentageLabel(slider); + textLabels.add(percentageLabel); + sliderPanel.add(percentageLabel, BorderLayout.EAST); + + return sliderPanel; + } + + private static JLabel createChangingPercentageLabel(final JSlider slider) { + + final JLabel label = new JLabel(" " + String.valueOf(slider.getValue()) + "%"); + + slider.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + String value = String.valueOf(slider.getValue()); + StringBuilder labelBuilder = new StringBuilder(); + // Pad with spaces so all percentage labels are of equal size + for(int i = 0; i < (5-value.length()); i++) { + labelBuilder.append(" "); + } + labelBuilder.append(value); + labelBuilder.append("%"); + label.setText(labelBuilder.toString()); + } + }); + return label; + } + + @Override + public void setEnabled(boolean enabled) { + for(JStorageSlider slider: sg.getSliders()) { + slider.setEnabled(enabled); + } + for(JLabel label: textLabels) { + label.setEnabled(enabled); + } + } + + public void resetValues() { + for(JStorageSlider slider: sg.getSliders()) { + slider.resetDefault(); + } + } + + public int getCreaturePercentage() { + return creatureSlider.getValue(); + } + + public int getNonCreaturePercentage() { + return nonCreatureSlider.getValue(); + } + + public int getLandPercentage() { + return landSlider.getValue(); + } + + public void setCreaturePercentage(int percentage) { + creatureSlider.setValue(percentage); + creatureSlider.previousValue = percentage; + } + + public void setNonCreaturePercentage(int percentage) { + nonCreatureSlider.setValue(percentage); + nonCreatureSlider.previousValue = percentage; + } + + public void setLandPercentage(int percentage) { + landSlider.setValue(percentage); + landSlider.previousValue = percentage; + } + + + + +} 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 abe9cea1d36..ca80f4c1609 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -236,6 +236,12 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_DECK_GENERATOR_ARTIFACTS = "newDeckGeneratorArtifacts"; public static final String KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS = "newDeckGeneratorNonBasicLands"; public static final String KEY_NEW_DECK_GENERATOR_COLORLESS = "newDeckGeneratorColorless"; + public static final String KEY_NEW_DECK_GENERATOR_ADVANCED = "newDeckGeneratorAdvanced"; + public static final String KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE = "newDeckGeneratorCreaturePercentage"; + public static final String KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE = "newDeckGeneratorNonCreaturePercentage"; + public static final String KEY_NEW_DECK_GENERATOR_LAND_PERCENTAGE = "newDeckGeneratorLandPercentage"; + public static final String KEY_NEW_DECK_GENERATOR_ADVANCED_CMC = "newDeckGeneratorAdvancedCMC"; + // used to save and restore the settings for the cardArea (draft, sideboarding, deck builder) public static final String KEY_DRAFT_VIEW = "draftView"; From 812c348ecea6e2f389b05d2e858015170a6bcc39 Mon Sep 17 00:00:00 2001 From: Jerrad Bieno Date: Sun, 13 Mar 2016 19:04:00 -0500 Subject: [PATCH 02/44] Added card "Cradle to Grave". Added implementation of "Cradle to Grave". --- .../mage/sets/planarchaos/CradleToGrave.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/planarchaos/CradleToGrave.java diff --git a/Mage.Sets/src/mage/sets/planarchaos/CradleToGrave.java b/Mage.Sets/src/mage/sets/planarchaos/CradleToGrave.java new file mode 100644 index 00000000000..e82e4c61be2 --- /dev/null +++ b/Mage.Sets/src/mage/sets/planarchaos/CradleToGrave.java @@ -0,0 +1,71 @@ +/* + * 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 mage.sets.planarchaos; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.filter.predicate.permanent.EnteredThisTurnPredicate; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author bieno002 + */ +public class CradleToGrave extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nonblack creature that came into play this turn"); + + static { + filter.add(Predicates.and(Predicates.not(new ColorPredicate(ObjectColor.BLACK)), new EnteredThisTurnPredicate())); + } + + public CradleToGrave(UUID ownerId) { + super(ownerId, 67, "Cradle to Grave", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{B}"); + this.expansionSetCode = "PLC"; + + // Destroy target nonblack creature that entered the battlefield this turn. + this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + } + + public CradleToGrave(final CradleToGrave card) { + super(card); + } + + @Override + public CradleToGrave copy() { + return new CradleToGrave(this); + } +} From f56b82a65ffc565fc955fed3e5ea4c938fd03230 Mon Sep 17 00:00:00 2001 From: DjB Date: Sun, 13 Mar 2016 22:22:42 -0500 Subject: [PATCH 03/44] Create Misstep.java --- .../mage/sets/mercadianmasques/Misstep.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/mercadianmasques/Misstep.java diff --git a/Mage.Sets/src/mage/sets/mercadianmasques/Misstep.java b/Mage.Sets/src/mage/sets/mercadianmasques/Misstep.java new file mode 100644 index 00000000000..c11c5f566ca --- /dev/null +++ b/Mage.Sets/src/mage/sets/mercadianmasques/Misstep.java @@ -0,0 +1,100 @@ +/* + * 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 mage.sets.mercadianmasques; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author djbrez + */ +public class Misstep extends CardImpl { + + public Misstep(UUID ownerId) { + super(ownerId, 88, "Misstep", Rarity.COMMON, new CardType[]{CardType.SORCERY}, "{1}{U}"); + this.expansionSetCode = "MMQ"; + + // Creatures target player controls don't untap during that player's next untap step. + this.getSpellAbility().addEffect(new MisstepEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + public Misstep(final Misstep card) { + super(card); + } + + @Override + public Misstep copy() { + return new Misstep(this); + } +} + +class MisstepEffect extends OneShotEffect { + + MisstepEffect() { + super(Outcome.Detriment); + this.staticText = "Creatures target player controls don't untap during his or her next untap step"; + } + + MisstepEffect(final MisstepEffect effect) { + super(effect); + } + + @Override + public MisstepEffect copy() { + return new MisstepEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getFirstTarget()); + if (player != null) { + for (Permanent creature: game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game)) { + ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); + effect.setTargetPointer(new FixedTarget(creature.getId())); + game.addEffect(effect, source); + } + return true; + } + return false; + } +} From 72cbaaf3ab7dcf4c5d4aec4556f64968dcf80e14 Mon Sep 17 00:00:00 2001 From: drmDev Date: Mon, 14 Mar 2016 02:27:32 -0400 Subject: [PATCH 04/44] Tests for Thought-Knot Seer reported bug --- .../single/ogw/ThoughtKnotSeerTests.java | 130 ++++++++++++++++++ .../EnterLeaveBattlefieldExileTargetTest.java | 1 - 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTests.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTests.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTests.java new file mode 100644 index 00000000000..accc6f15a30 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTests.java @@ -0,0 +1,130 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.single.ogw; + +import java.util.Set; +import mage.cards.Card; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class ThoughtKnotSeerTests extends CardTestPlayerBase { + + /** + * Reported bug + * I bounced a Thought-Knot Seer my opponent controlled with enter the battlefield ability of a Reflector Mage. + * I should have drawn a card since the Thought-Knot Seer left the battlefield but I didn't. + */ + @Test + public void testThoughtKnotSeerBouncedReflectorMage() { + + // {1}{W}{U} When Reflector Mage enters the battlefield, return target creature an opponent controls to its owner's hand. + // That creature's owner can't cast spells with the same name as that creature until your next turn. + addCard(Zone.HAND, playerA, "Reflector Mage"); // 2/3 + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // {3}{<>} 4/4 + // When Thought-Knot Seer enters the battlefield, target opponent reveals his or her hand. You choose a nonland card from it and exile that card. + // When Thought-Knot Seer leaves the battlefield, target opponent draws a card. + addCard(Zone.BATTLEFIELD, playerB, "Thought-Knot Seer"); + addCard(Zone.BATTLEFIELD, playerB, "Wastes", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reflector Mage"); + addTarget(playerA, "Thought-Knot Seer"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertHandCount(playerB, "Thought-Knot Seer", 1); + Set hand = playerA.getHand().getCards(currentGame); + assertHandCount(playerA, 1); // should have drawn a card from Thought-Knot Seer leaving + } + + /** + * Simple bounce test on Thought-Knot Seer to differentiate between this and Reflector Mage issue + */ + @Test + public void testThoughtKnotSeerBouncedUnsummon() { + + // {U} Return target creature to its owner's hand. + addCard(Zone.HAND, playerA, "Unsummon"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + // {3}{<>} 4/4 + // When Thought-Knot Seer enters the battlefield, target opponent reveals his or her hand. You choose a nonland card from it and exile that card. + // When Thought-Knot Seer leaves the battlefield, target opponent draws a card. + addCard(Zone.BATTLEFIELD, playerB, "Thought-Knot Seer"); + addCard(Zone.BATTLEFIELD, playerB, "Wastes", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Unsummon"); + addTarget(playerA, "Thought-Knot Seer"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Unsummon", 1); + assertHandCount(playerB, "Thought-Knot Seer", 1); + Set hand = playerA.getHand().getCards(currentGame); + assertHandCount(playerA, 1); // should have drawn a card from Thought-Knot Seer leaving + } + + /** + * + */ + @Test + public void testThoughtKnotSeerDestroyed() { + + // {1}{B} Destroy target nonblack creature. + addCard(Zone.HAND, playerA, "Doom Blade"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + // {3}{<>} 4/4 + // When Thought-Knot Seer enters the battlefield, target opponent reveals his or her hand. You choose a nonland card from it and exile that card. + // When Thought-Knot Seer leaves the battlefield, target opponent draws a card. + addCard(Zone.BATTLEFIELD, playerB, "Thought-Knot Seer"); + addCard(Zone.BATTLEFIELD, playerB, "Wastes", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Doom Blade"); + addTarget(playerA, "Thought-Knot Seer"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Thought-Knot Seer", 1); + Set hand = playerA.getHand().getCards(currentGame); + assertHandCount(playerA, 1); // should have drawn a card from Thought-Knot Seer leaving + } + + /** + * + */ + @Test + public void testThoughtKnotSeerExiled() { + + // {W} Exile target creature. Its controller may search his or her library for a basic land card, put that card onto the battlefield tapped, then shuffle his or her library. + addCard(Zone.HAND, playerA, "Path to Exile"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + + // {3}{<>} 4/4 + // When Thought-Knot Seer enters the battlefield, target opponent reveals his or her hand. You choose a nonland card from it and exile that card. + // When Thought-Knot Seer leaves the battlefield, target opponent draws a card. + addCard(Zone.BATTLEFIELD, playerB, "Thought-Knot Seer"); + addCard(Zone.BATTLEFIELD, playerB, "Wastes", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Path to Exile"); + addTarget(playerA, "Thought-Knot Seer"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount(playerB, 1); + assertExileCount("Thought-Knot Seer", 1); + Set hand = playerA.getHand().getCards(currentGame); + assertHandCount(playerA, 1); // should have drawn a card from Thought-Knot Seer leaving + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java index a8088969696..abd2067e6b6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java @@ -94,6 +94,5 @@ public class EnterLeaveBattlefieldExileTargetTest extends CardTestPlayerBase { assertHandCount(playerB, "Silvercoat Lion", 1); assertHandCount(playerB, "Pillarfield Ox", 1); assertExileCount(playerB, 0); - } } From 79c4798ff9e0164d4ba7fe24853bc18172ee9445 Mon Sep 17 00:00:00 2001 From: drmDev Date: Mon, 14 Mar 2016 02:54:10 -0400 Subject: [PATCH 05/44] Renamed Thought-Knot Seer tests to match conventions. --- .../org/mage/test/cards/requirement/BlockRequirementTest.java | 2 +- .../ogw/{ThoughtKnotSeerTests.java => ThoughtKnotSeerTest.java} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/{ThoughtKnotSeerTests.java => ThoughtKnotSeerTest.java} (98%) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java index a1c33fa1846..c277096383f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java @@ -156,7 +156,7 @@ public class BlockRequirementTest extends CardTestPlayerBase { assertLife(playerB, 18); } - /** + /** * Okk is red creature that can't block unless a creature with greater power also blocks. */ @Test diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTests.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTest.java similarity index 98% rename from Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTests.java rename to Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTest.java index accc6f15a30..fa35e6c80ed 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTests.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTest.java @@ -12,7 +12,7 @@ import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; -public class ThoughtKnotSeerTests extends CardTestPlayerBase { +public class ThoughtKnotSeerTest extends CardTestPlayerBase { /** * Reported bug From 5cc6ef9d4a1089afddf394668d0e7eb335f3201e Mon Sep 17 00:00:00 2001 From: spjspj Date: Mon, 14 Mar 2016 20:07:14 +1100 Subject: [PATCH 06/44] spjspj - Implement Cryptic Gateway (ONS) --- .../mage/sets/onslaught/CrypticGateway.java | 234 ++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/onslaught/CrypticGateway.java diff --git a/Mage.Sets/src/mage/sets/onslaught/CrypticGateway.java b/Mage.Sets/src/mage/sets/onslaught/CrypticGateway.java new file mode 100644 index 00000000000..79d0a32c81a --- /dev/null +++ b/Mage.Sets/src/mage/sets/onslaught/CrypticGateway.java @@ -0,0 +1,234 @@ +/* + * 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 mage.sets.onslaught; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PutPermanentOnBattlefieldEffect; +import mage.abilities.keyword.ChangelingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author spjspj + */ +public class CrypticGateway extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped creatures you control"); + + static { + filter.add(Predicates.not(new TappedPredicate())); + } + + TargetControlledPermanent target; + + public CrypticGateway(UUID ownerId) { + super(ownerId, 306, "Cryptic Gateway", Rarity.RARE, new CardType[]{CardType.ARTIFACT}, "{5}"); + this.expansionSetCode = "ONS"; + + // Tap two untapped creatures you control: You may put a creature card from your hand that shares a creature type with each creature tapped this way onto the battlefield. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new CrypticGatewayEffect(), new CrypticGatewayCost(new TargetControlledPermanent(filter)))); + } + + public CrypticGateway(final CrypticGateway card) { + super(card); + } + + @Override + public CrypticGateway copy() { + return new CrypticGateway(this); + } +} + +class CrypticGatewayCost extends CostImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("untapped creatures you control"); + private UUID targetCreatureId = null; + private UUID targetCreatureId2 = null; + + TargetControlledPermanent target; + + static { + filter.add(Predicates.not(new TappedPredicate())); + } + + public CrypticGatewayCost(TargetControlledPermanent target) { + this.target = target; + this.text = "Tap two untapped creatures you control"; + } + + public CrypticGatewayCost(final CrypticGatewayCost cost) { + super(cost); + this.target = cost.target.copy(); + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { + int numTargets = 0; + while (numTargets < 2 && target.choose(Outcome.Tap, controllerId, sourceId, game)) { + for (UUID targetId : (List) target.getTargets()) { + Permanent permanent = game.getPermanent(targetId); + if (permanent == null) { + return false; + } + paid |= permanent.tap(game); + if (paid) { + numTargets++; + target.clearChosen(); + } + for (Effect effect : ability.getEffects()) { + if (targetCreatureId == null) { + targetCreatureId = permanent.getId(); + } else if (targetCreatureId2 == null) { + targetCreatureId2 = permanent.getId(); + } + } + } + } + return paid; + } + + @Override + public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { + return target.canChoose(controllerId, game); + } + + @Override + public CrypticGatewayCost copy() { + return new CrypticGatewayCost(this); + } + + public UUID getTargetCreatureId() { + return targetCreatureId; + } + + public UUID getTargetCreatureId2() { + return targetCreatureId2; + } +} + +class CrypticGatewayEffect extends OneShotEffect { + + public CrypticGatewayEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "Put a creature card from your hand that shares a creature type with each creature tapped this way onto the battlefield"; + } + + public CrypticGatewayEffect(final CrypticGatewayEffect effect) { + super(effect); + } + + @Override + public CrypticGatewayEffect copy() { + return new CrypticGatewayEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (source == null || source.getCosts() == null) { + return false; + } + + FilterCard filter = new FilterCreatureCard("creature card from your hand that shares a creature type with each creature tapped this way"); + + for (Cost cost : source.getCosts()) { + if (cost instanceof CrypticGatewayCost) { + UUID id = ((CrypticGatewayCost) cost).getTargetCreatureId(); + UUID id2 = ((CrypticGatewayCost) cost).getTargetCreatureId2(); + Permanent creature = game.getPermanentOrLKIBattlefield(id); + Permanent creature2 = game.getPermanentOrLKIBattlefield(id2); + + if (creature == null || creature2 == null) { + return false; + } + + boolean commonSubType = false; + boolean changeling = false; + boolean changeling2 = false; + if (creature.getAbilities().containsKey(ChangelingAbility.getInstance().getId()) || creature.getSubtype().contains(ChangelingAbility.ALL_CREATURE_TYPE)) { + changeling = true; + } + if (creature2.getAbilities().containsKey(ChangelingAbility.getInstance().getId()) || creature2.getSubtype().contains(ChangelingAbility.ALL_CREATURE_TYPE)) { + changeling2 = true; + } + + ArrayList subtypes = new ArrayList<>(); + + for (String subtype : creature.getSubtype()) { + if (creature2.getSubtype().contains(subtype) || changeling2) { + subtypes.add(new SubtypePredicate(subtype)); + commonSubType = true; + } + } + + for (String subtype : creature2.getSubtype()) { + if (creature.getSubtype().contains(subtype) || changeling) { + subtypes.add(new SubtypePredicate(subtype)); + commonSubType = true; + } + } + + if (changeling && changeling2) { + filter = new FilterCreatureCard("creature card from your hand that shares a creature type with each creature tapped this way"); + } else if (commonSubType) { + filter.add(Predicates.or(subtypes)); + } + + if (commonSubType) { + PutPermanentOnBattlefieldEffect putIntoPlay = new PutPermanentOnBattlefieldEffect(filter); + putIntoPlay.apply(game, source); + } + } + } + + return false; + } +} From 7125d22e7aec65e3a7c1af48040a99d0fb3f8427 Mon Sep 17 00:00:00 2001 From: drmDev Date: Mon, 14 Mar 2016 05:26:45 -0400 Subject: [PATCH 07/44] The remaining Scourge-Dragon enchantment cards implemented --- .../src/mage/sets/scourge/DragonFangs.java | 130 +++++++++++++++++ .../src/mage/sets/scourge/DragonScales.java | 129 +++++++++++++++++ .../src/mage/sets/scourge/DragonShadow.java | 129 +++++++++++++++++ .../src/mage/sets/scourge/DragonWings.java | 131 ++++++++++++++++++ 4 files changed, 519 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/scourge/DragonFangs.java create mode 100644 Mage.Sets/src/mage/sets/scourge/DragonScales.java create mode 100644 Mage.Sets/src/mage/sets/scourge/DragonShadow.java create mode 100644 Mage.Sets/src/mage/sets/scourge/DragonWings.java diff --git a/Mage.Sets/src/mage/sets/scourge/DragonFangs.java b/Mage.Sets/src/mage/sets/scourge/DragonFangs.java new file mode 100644 index 00000000000..c3856acf720 --- /dev/null +++ b/Mage.Sets/src/mage/sets/scourge/DragonFangs.java @@ -0,0 +1,130 @@ +/* + * 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 mage.sets.scourge; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.Filter; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class DragonFangs extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature with converted mana cost 6 or greater"); + static { + filter.add(new ConvertedManaCostPredicate(Filter.ComparisonType.GreaterThan, 5)); + } + + public DragonFangs(UUID ownerId) { + super(ownerId, 117, "Dragon Fangs", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}"); + this.expansionSetCode = "SCG"; + this.subtype.add("Aura"); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.AddAbility)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature gets +1/+1 and has trample. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(1, 1, Duration.WhileOnBattlefield))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAttachedEffect(TrampleAbility.getInstance(), AttachmentType.AURA))); + + // When a creature with converted mana cost 6 or greater enters the battlefield, you may return Dragon Fangs from your graveyard to the battlefield attached to that creature. + this.addAbility(new EntersBattlefieldAllTriggeredAbility(Zone.GRAVEYARD, new DragonFangsEffect(), filter, true, SetTargetPointer.PERMANENT, null)); + } + + public DragonFangs(final DragonFangs card) { + super(card); + } + + @Override + public DragonFangs copy() { + return new DragonFangs(this); + } +} + +class DragonFangsEffect extends OneShotEffect { + + DragonFangsEffect() { + super(Outcome.Benefit); + this.staticText = "return {this} from your graveyard to the battlefield attached to that creature"; + } + + DragonFangsEffect(final DragonFangsEffect effect) { + super(effect); + } + + @Override + public DragonFangsEffect copy() { + return new DragonFangsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card sourceCard = (Card) source.getSourceObjectIfItStillExists(game); + Permanent permanent = game.getPermanent(this.getTargetPointer().getFirst(game, source)); + Player controller = game.getPlayer(source.getControllerId()); + if (sourceCard != null && permanent != null && controller != null) { + game.getState().setValue("attachTo:" + sourceCard.getId(), permanent); + if (controller.moveCards(sourceCard, Zone.BATTLEFIELD, source, game)) { + permanent.addAttachment(sourceCard.getId(), game); + } + return true; + } + return false; + } +} + diff --git a/Mage.Sets/src/mage/sets/scourge/DragonScales.java b/Mage.Sets/src/mage/sets/scourge/DragonScales.java new file mode 100644 index 00000000000..43df4e33a26 --- /dev/null +++ b/Mage.Sets/src/mage/sets/scourge/DragonScales.java @@ -0,0 +1,129 @@ +/* + * 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 mage.sets.scourge; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.Filter; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class DragonScales extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature with converted mana cost 6 or greater"); + static { + filter.add(new ConvertedManaCostPredicate(Filter.ComparisonType.GreaterThan, 5)); + } + + public DragonScales(UUID ownerId) { + super(ownerId, 10, "Dragon Scales", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); + this.expansionSetCode = "SCG"; + this.subtype.add("Aura"); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.AddAbility)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature gets +1/+2 and has vigilance. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(1, 2, Duration.WhileOnBattlefield))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAttachedEffect(VigilanceAbility.getInstance(), AttachmentType.AURA))); + + // When a creature with converted mana cost 6 or greater enters the battlefield, you may return Dragon Scales from your graveyard to the battlefield attached to that creature. + this.addAbility(new EntersBattlefieldAllTriggeredAbility(Zone.GRAVEYARD, new DragonScalesEffect(), filter, true, SetTargetPointer.PERMANENT, null)); + } + + public DragonScales(final DragonScales card) { + super(card); + } + + @Override + public DragonScales copy() { + return new DragonScales(this); + } +} + +class DragonScalesEffect extends OneShotEffect { + + DragonScalesEffect() { + super(Outcome.Benefit); + this.staticText = "return {this} from your graveyard to the battlefield attached to that creature"; + } + + DragonScalesEffect(final DragonScalesEffect effect) { + super(effect); + } + + @Override + public DragonScalesEffect copy() { + return new DragonScalesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card sourceCard = (Card) source.getSourceObjectIfItStillExists(game); + Permanent permanent = game.getPermanent(this.getTargetPointer().getFirst(game, source)); + Player controller = game.getPlayer(source.getControllerId()); + if (sourceCard != null && permanent != null && controller != null) { + game.getState().setValue("attachTo:" + sourceCard.getId(), permanent); + if (controller.moveCards(sourceCard, Zone.BATTLEFIELD, source, game)) { + permanent.addAttachment(sourceCard.getId(), game); + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/scourge/DragonShadow.java b/Mage.Sets/src/mage/sets/scourge/DragonShadow.java new file mode 100644 index 00000000000..3571dc4e1dd --- /dev/null +++ b/Mage.Sets/src/mage/sets/scourge/DragonShadow.java @@ -0,0 +1,129 @@ +/* + * 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 mage.sets.scourge; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FearAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.Filter; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class DragonShadow extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature with converted mana cost 6 or greater"); + static { + filter.add(new ConvertedManaCostPredicate(Filter.ComparisonType.GreaterThan, 5)); + } + + public DragonShadow(UUID ownerId) { + super(ownerId, 65, "Dragon Shadow", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); + this.expansionSetCode = "SCG"; + this.subtype.add("Aura"); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.AddAbility)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature gets +1/+0 and has fear. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(1, 0, Duration.WhileOnBattlefield))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAttachedEffect(FearAbility.getInstance(), AttachmentType.AURA))); + + // When a creature with converted mana cost 6 or greater enters the battlefield, you may return Dragon Breath from your graveyard to the battlefield attached to that creature. + this.addAbility(new EntersBattlefieldAllTriggeredAbility(Zone.GRAVEYARD, new DragonShadowEffect(), filter, true, SetTargetPointer.PERMANENT, null)); + } + + public DragonShadow(final DragonShadow card) { + super(card); + } + + @Override + public DragonShadow copy() { + return new DragonShadow(this); + } +} + +class DragonShadowEffect extends OneShotEffect { + + DragonShadowEffect() { + super(Outcome.Benefit); + this.staticText = "return {this} from your graveyard to the battlefield attached to that creature"; + } + + DragonShadowEffect(final DragonShadowEffect effect) { + super(effect); + } + + @Override + public DragonShadowEffect copy() { + return new DragonShadowEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card sourceCard = (Card) source.getSourceObjectIfItStillExists(game); + Permanent permanent = game.getPermanent(this.getTargetPointer().getFirst(game, source)); + Player controller = game.getPlayer(source.getControllerId()); + if (sourceCard != null && permanent != null && controller != null) { + game.getState().setValue("attachTo:" + sourceCard.getId(), permanent); + if (controller.moveCards(sourceCard, Zone.BATTLEFIELD, source, game)) { + permanent.addAttachment(sourceCard.getId(), game); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/scourge/DragonWings.java b/Mage.Sets/src/mage/sets/scourge/DragonWings.java new file mode 100644 index 00000000000..acbe2a9fe8b --- /dev/null +++ b/Mage.Sets/src/mage/sets/scourge/DragonWings.java @@ -0,0 +1,131 @@ +/* + * 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 mage.sets.scourge; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.CyclingAbility; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.Filter; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class DragonWings extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature with converted mana cost 6 or greater"); + static { + filter.add(new ConvertedManaCostPredicate(Filter.ComparisonType.GreaterThan, 5)); + } + + public DragonWings(UUID ownerId) { + super(ownerId, 34, "Dragon Wings", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + this.expansionSetCode = "SCG"; + this.subtype.add("Aura"); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.AddAbility)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature has flying. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAttachedEffect(FlyingAbility.getInstance(), AttachmentType.AURA))); + // Cycling {1}{U} + this.addAbility(new CyclingAbility(new ManaCostsImpl("{1}{U}"))); + + // When a creature with converted mana cost 6 or greater enters the battlefield, you may return Dragon Breath from your graveyard to the battlefield attached to that creature. + this.addAbility(new EntersBattlefieldAllTriggeredAbility(Zone.GRAVEYARD, new DragonWingsEffect(), filter, true, SetTargetPointer.PERMANENT, null)); + } + + public DragonWings(final DragonWings card) { + super(card); + } + + @Override + public DragonWings copy() { + return new DragonWings(this); + } +} + +class DragonWingsEffect extends OneShotEffect { + + DragonWingsEffect() { + super(Outcome.Benefit); + this.staticText = "return {this} from your graveyard to the battlefield attached to that creature"; + } + + DragonWingsEffect(final DragonWingsEffect effect) { + super(effect); + } + + @Override + public DragonWingsEffect copy() { + return new DragonWingsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card sourceCard = (Card) source.getSourceObjectIfItStillExists(game); + Permanent permanent = game.getPermanent(this.getTargetPointer().getFirst(game, source)); + Player controller = game.getPlayer(source.getControllerId()); + if (sourceCard != null && permanent != null && controller != null) { + game.getState().setValue("attachTo:" + sourceCard.getId(), permanent); + if (controller.moveCards(sourceCard, Zone.BATTLEFIELD, source, game)) { + permanent.addAttachment(sourceCard.getId(), game); + } + return true; + } + return false; + } +} + From ad637a29445128ab1e305e744f7aea069169bfa2 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Mon, 14 Mar 2016 13:41:12 +0100 Subject: [PATCH 08/44] * Fixed CAST_SPELL_LATE event (e.g. used by Reflector Mage) to only fire for cast events. --- .../cards/single/ogw/ThoughtKnotSeerTest.java | 65 ++++++++++--------- .../main/java/mage/abilities/AbilityImpl.java | 3 +- .../LeavesBattlefieldTriggeredAbility.java | 2 +- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTest.java index fa35e6c80ed..4252599d77a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/ThoughtKnotSeerTest.java @@ -13,54 +13,55 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; public class ThoughtKnotSeerTest extends CardTestPlayerBase { - + /** - * Reported bug - * I bounced a Thought-Knot Seer my opponent controlled with enter the battlefield ability of a Reflector Mage. - * I should have drawn a card since the Thought-Knot Seer left the battlefield but I didn't. + * Reported bug I bounced a Thought-Knot Seer my opponent controlled with + * enter the battlefield ability of a Reflector Mage. I should have drawn a + * card since the Thought-Knot Seer left the battlefield but I didn't. */ @Test public void testThoughtKnotSeerBouncedReflectorMage() { - // {1}{W}{U} When Reflector Mage enters the battlefield, return target creature an opponent controls to its owner's hand. + // {1}{W}{U} When Reflector Mage enters the battlefield, return target creature an opponent controls to its owner's hand. // That creature's owner can't cast spells with the same name as that creature until your next turn. - addCard(Zone.HAND, playerA, "Reflector Mage"); // 2/3 - addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); - addCard(Zone.BATTLEFIELD, playerA, "Island", 2); - + addCard(Zone.HAND, playerA, "Reflector Mage"); // 2/3 + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + // {3}{<>} 4/4 // When Thought-Knot Seer enters the battlefield, target opponent reveals his or her hand. You choose a nonland card from it and exile that card. // When Thought-Knot Seer leaves the battlefield, target opponent draws a card. addCard(Zone.BATTLEFIELD, playerB, "Thought-Knot Seer"); addCard(Zone.BATTLEFIELD, playerB, "Wastes", 4); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reflector Mage"); addTarget(playerA, "Thought-Knot Seer"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertHandCount(playerB, "Thought-Knot Seer", 1); - Set hand = playerA.getHand().getCards(currentGame); + assertPermanentCount(playerA, "Reflector Mage", 1); + assertHandCount(playerB, "Thought-Knot Seer", 1); assertHandCount(playerA, 1); // should have drawn a card from Thought-Knot Seer leaving } - + /** - * Simple bounce test on Thought-Knot Seer to differentiate between this and Reflector Mage issue + * Simple bounce test on Thought-Knot Seer to differentiate between this and + * Reflector Mage issue */ @Test public void testThoughtKnotSeerBouncedUnsummon() { // {U} Return target creature to its owner's hand. addCard(Zone.HAND, playerA, "Unsummon"); - addCard(Zone.BATTLEFIELD, playerA, "Island", 2); - + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + // {3}{<>} 4/4 // When Thought-Knot Seer enters the battlefield, target opponent reveals his or her hand. You choose a nonland card from it and exile that card. // When Thought-Knot Seer leaves the battlefield, target opponent draws a card. addCard(Zone.BATTLEFIELD, playerB, "Thought-Knot Seer"); addCard(Zone.BATTLEFIELD, playerB, "Wastes", 4); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Unsummon"); addTarget(playerA, "Thought-Knot Seer"); @@ -68,54 +69,54 @@ public class ThoughtKnotSeerTest extends CardTestPlayerBase { execute(); assertGraveyardCount(playerA, "Unsummon", 1); - assertHandCount(playerB, "Thought-Knot Seer", 1); + assertHandCount(playerB, "Thought-Knot Seer", 1); Set hand = playerA.getHand().getCards(currentGame); assertHandCount(playerA, 1); // should have drawn a card from Thought-Knot Seer leaving } - + /** - * + * */ @Test public void testThoughtKnotSeerDestroyed() { // {1}{B} Destroy target nonblack creature. - addCard(Zone.HAND, playerA, "Doom Blade"); - addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); - + addCard(Zone.HAND, playerA, "Doom Blade"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // {3}{<>} 4/4 // When Thought-Knot Seer enters the battlefield, target opponent reveals his or her hand. You choose a nonland card from it and exile that card. // When Thought-Knot Seer leaves the battlefield, target opponent draws a card. addCard(Zone.BATTLEFIELD, playerB, "Thought-Knot Seer"); addCard(Zone.BATTLEFIELD, playerB, "Wastes", 4); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Doom Blade"); addTarget(playerA, "Thought-Knot Seer"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertGraveyardCount(playerB, "Thought-Knot Seer", 1); + assertGraveyardCount(playerB, "Thought-Knot Seer", 1); Set hand = playerA.getHand().getCards(currentGame); assertHandCount(playerA, 1); // should have drawn a card from Thought-Knot Seer leaving } - + /** - * + * */ @Test public void testThoughtKnotSeerExiled() { // {W} Exile target creature. Its controller may search his or her library for a basic land card, put that card onto the battlefield tapped, then shuffle his or her library. - addCard(Zone.HAND, playerA, "Path to Exile"); - addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); - + addCard(Zone.HAND, playerA, "Path to Exile"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + // {3}{<>} 4/4 // When Thought-Knot Seer enters the battlefield, target opponent reveals his or her hand. You choose a nonland card from it and exile that card. // When Thought-Knot Seer leaves the battlefield, target opponent draws a card. addCard(Zone.BATTLEFIELD, playerB, "Thought-Knot Seer"); addCard(Zone.BATTLEFIELD, playerB, "Wastes", 4); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Path to Exile"); addTarget(playerA, "Thought-Knot Seer"); @@ -127,4 +128,4 @@ public class ThoughtKnotSeerTest extends CardTestPlayerBase { Set hand = playerA.getHand().getCards(currentGame); assertHandCount(playerA, 1); // should have drawn a card from Thought-Knot Seer leaving } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 90344ee1b63..9dd6cc256ee 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -315,7 +315,8 @@ public abstract class AbilityImpl implements Ability { VariableManaCost variableManaCost = handleManaXCosts(game, noMana, controller); String announceString = handleOtherXCosts(game, controller); // For effects from cards like Void Winnower x costs have to be set - if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.CAST_SPELL_LATE, getId(), getSourceId(), getControllerId()), this)) { + if (this.getAbilityType().equals(AbilityType.SPELL) + && game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.CAST_SPELL_LATE, getId(), getSourceId(), getControllerId()), this)) { return false; } for (Mode mode : this.getModes().getSelectedModes()) { diff --git a/Mage/src/main/java/mage/abilities/common/LeavesBattlefieldTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/LeavesBattlefieldTriggeredAbility.java index 23a867af0dd..a333f2346cd 100644 --- a/Mage/src/main/java/mage/abilities/common/LeavesBattlefieldTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/LeavesBattlefieldTriggeredAbility.java @@ -40,7 +40,7 @@ import mage.game.events.ZoneChangeEvent; public class LeavesBattlefieldTriggeredAbility extends ZoneChangeTriggeredAbility { public LeavesBattlefieldTriggeredAbility(Effect effect, boolean optional) { - super(Zone.BATTLEFIELD, null, effect, "When {this} leaves the battlefield, ", optional); + super(Zone.ALL, Zone.BATTLEFIELD, null, effect, "When {this} leaves the battlefield, ", optional); } public LeavesBattlefieldTriggeredAbility(LeavesBattlefieldTriggeredAbility ability) { From 9ab96b03d949d274b0e4e7a8eefee5a923565344 Mon Sep 17 00:00:00 2001 From: spjspj Date: Tue, 15 Mar 2016 01:17:41 +1100 Subject: [PATCH 09/44] spjspj - Implement Screams From Within (Darksteel) --- .../sets/darksteel/ScreamsFromWithin.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/darksteel/ScreamsFromWithin.java diff --git a/Mage.Sets/src/mage/sets/darksteel/ScreamsFromWithin.java b/Mage.Sets/src/mage/sets/darksteel/ScreamsFromWithin.java new file mode 100644 index 00000000000..7533efcdbeb --- /dev/null +++ b/Mage.Sets/src/mage/sets/darksteel/ScreamsFromWithin.java @@ -0,0 +1,110 @@ +/* + * 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 mage.sets.darksteel; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.DiesAttachedTriggeredAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author spjspj + */ +public class ScreamsFromWithin extends CardImpl { + + public ScreamsFromWithin(UUID ownerId) { + super(ownerId, 52, "Screams from Within", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}{B}"); + this.expansionSetCode = "DST"; + this.subtype.add("Aura"); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Detriment)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Enchanted creature gets -1/-1. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(-1, -1, Duration.WhileOnBattlefield))); + + // When enchanted creature dies, return Screams from Within from your graveyard to the battlefield. + this.addAbility(new DiesAttachedTriggeredAbility(new ScreamsFromWithinEffect(), "enchanted creature")); + } + + public ScreamsFromWithin(final ScreamsFromWithin card) { + super(card); + } + + @Override + public ScreamsFromWithin copy() { + return new ScreamsFromWithin(this); + } +} + +class ScreamsFromWithinEffect extends OneShotEffect { + + public ScreamsFromWithinEffect() { + super(Outcome.PutCardInPlay); + staticText = "return Screams from Within from your graveyard to the battlefield"; + } + + public ScreamsFromWithinEffect(final ScreamsFromWithinEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Card sourceEnchantmentCard = game.getCard(source.getSourceId()); + Player player = game.getPlayer(source.getControllerId()); + if (sourceEnchantmentCard != null && player != null) { + return player.moveCards(sourceEnchantmentCard, Zone.BATTLEFIELD, source, game); + } + return false; + } + + @Override + public ScreamsFromWithinEffect copy() { + return new ScreamsFromWithinEffect(this); + } +} \ No newline at end of file From c85cc9cb987d1ca13a0f767d3d1c695d0959cbdf Mon Sep 17 00:00:00 2001 From: fireshoes Date: Mon, 14 Mar 2016 11:24:45 -0500 Subject: [PATCH 10/44] [SOI] Added 3/14 spoilers to mtg-cards-data.txt --- Utils/mtg-cards-data.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 80d1826d604..75d0f3254f1 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -34636,6 +34636,7 @@ Stasis Snare|Game Day|47|U|{1}{W}{W}|Enchantment|||Flash$When Stasis Snare enter Radiant Flames|Game Day|48|R|{2}{R}|Sorcery||Converge - Radiant Flames deals X damage to each creature, where X is the number of colors of mana spent to cast Radiant Flames.| Immolating Glare|Game Day|49|U|{1}{W}|Instant|||Destroy target attacking creature.| Jori En, Ruin Diver|Game Day|50|R|{1}{U}{R}|Legendary Creature - Merfolk Wizard|2|3|Whenever you cast your second spell each turn, draw a card.| +Anguished Unmaking|Game Day|52|R|{1}{W}{B}|Instant|||Exile target nonland permanent. You lose 3 life.| Aerial Maneuver|Gatecrash|1|C|{1}{W}|Instant|||Target creature gets +1/+1 and gains flying and first strike until end of turn.| Debtor's Pulpit|Gatecrash|10|U|{4}{W}|Enchantment - Aura|||Enchant land$Enchanted land has "{tap}: Tap target creature."| Massive Raid|Gatecrash|100|C|{1}{R}{R}|Instant|||Massive Raid deals damage to target creature or player equal to the number of creatures you control.| @@ -56936,12 +56937,15 @@ Unknown Shores|Oath of the Gatewatch|181|C||Land|||{T}: Add {C} to your mana poo Wandering Fumarole|Oath of the Gatewatch|182|R||Land|||Wandering Fumarole enters the battlefield tapped.${T}: Add {U} or {R} to your mana pool.${2}{U}{R}: Until end of turn, Wandering Fumarole becomes a 1/4 blue and red Elemental creature with "0: Switch this creature's power and toughness until end of turn." It's still a land.| Wastes|Oath of the Gatewatch|183|C||Basic Land|||{T}: Add {C} to your mana pool.| Wastes|Oath of the Gatewatch|184|C||Basic Land|||{T}: Add {C} to your mana pool.| +Angel of Deliverance|Shadows over Innistrad|2|R|{6}{W}{W}|Creature - Angel|6|6|Flying$Delirium — Whenever Angel of Deliverance deals damage, if there are four or more card types among cards in your graveyard, exile target creature an opponent controls.| Archangel Avacyn|Shadows over Innistrad|5a|M|{3}{W}{W}|Legendary Creature - Angel|4|4|Flash$Flying, vigilance$When Archangel Avacyn enters the battlefield, creatures you control gain indestructible until end of turn.$When a non-Angel creature you control dies, transform Archangel Avacyn at the beginning of the next upkeep.| Avacyn, the Purifier|Shadows over Innistrad|5b|M||Legendary Creature - Angel|6|5|Flying$When this creature transforms into Avacyn, the Purifier, it deals 3 damage to each other creature and each opponent.| Avacynian Missionaries|Shadows over Innistrad|6a|U|{3}{W}|Creature - Human Cleric|3|3|At the beginning of your end step, if Avacynian Missionaries is equipped, transform it.| Lunarch Inquisitors|Shadows over Innistrad|6b|U||Creature - Human Cleric|4|4|When this creature transforms into Lunarch Inquisitors, you may exile another target creature until Lunarch Inquisitors leaves the battlefield.| Bygone Bishop|Shadows over Innistrad|8|R|{2}{W}|Creature - Spirit Cleric|2|3|Flying$Whenever you cast a creature spell with converted mana cost 3 or less, investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| Declaration in Stone|Shadows over Innistrad|12|R|{1}{W}|Sorcery|||Exile target creature and all other creatures its controller controls with the same name as that creature. That player investigates for each nontoken creature exiled this way.| +Descend upon the Sinful|Shadows over Innistrad|13|M|{4}{W}{W}|Sorcery|||Exile all creatures$Delirium — Put a 4/4 white Angel creature token with flying onto the battlefield if there are four or more card types among cards in your graveyard.| +Drogskol Cavalry|Shadows over Innistrad|15|R|{5}{W}{W}|Creature - Spirit Knight|4|4|Flying$Whenever another Spirit enters the battlefield under your control, you gain 2 life.${3}{W}: Put a 1/1 white Spirit creature token with flying onto the battlefield.| Eerie Interlude|Shadows over Innistrad|16|R|{2}{W}|Instant|||Exile any number of target creatures you control. Return those cards to the battlefield under their owner's control at the beginning of the next end step.| Expose Evil|Shadows over Innistrad|19|C|{1}{W}|Instant|||Tap up to two target creatures.$Investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| Pious Evangel|Shadows over Innistrad|34a|U|{2}{W}|Creature - Human Cleric|2|2|Whenever Pious Evangel or another creature enters the battlefield under your control, you gain 1 life.${2}, {T}, Sacrifice another permanent: Transform Pious Evangel.| @@ -56957,9 +56961,12 @@ Geralf's Masterpiece|Shadows over Innistrad|65|M|{3}{U}{U}|Creature - Zombie Hor Invasive Surgery|Shadows over Innistrad|68|U|{U}|Instant|||Counter target sorcery spell.$Delirium — If there are four or more card types among cards in your graveyard, search the graveyard, hand, and library of that spell's controller for any number of cards with the same name as that spell, exile those cards, then that player shuffles his or her library.| Just the Wind|Shadows over Innistrad|71|C|{1}{U}|Instant|||Return target creature to its owner's hand.$Madness {U} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| Lamplighter of Selhoff|Shadows over Innistrad|72|C|{4}{U}|Creature - Zombie Horror|3|5|When Lamplighter of Selhoff enters the battlefield, if you control another Zombie, you may draw a card. If you do, discard a card.| +Nephalia Moondrakes|Shadows over Innistrad|75|R|{5}{U}{U}|Creature - Drake|5|5|Flying$When Nephalia Moondrakes enters the battlefield, target creature gains flying until end of turn.${4}{U}{U}, Exile Nephalia Moondrakes from your graveyard: Creatures you control gain flying until end of turn.| Niblis of Dusk|Shadows over Innistrad|76|C|{2}{U}|Creature - Spirit|2|1|Flying$Prowess| Pieces of the Puzzle|Shadows over Innistrad|78|C|{2}{U}|Sorcery|||Reveal the top five cards of your library. Put up to two instant and/or sorcery cards from among them into your hand and the rest into your graveyard.| Pore Over the Pages|Shadows over Innistrad|79|U|{3}{U}{U}|Sorcery|||Draw three cards, untap up to two lands, then discard a card.| +Startled Awake|Shadows over Innistrad|88a|{2}{U}{U}|Sorcery|||Target opponent puts the top thirteen cards of his or her library into his or her graveyard.${3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery.| +Persistent Nightmare|Shadows over Innistrad|88b||Creature - Nightmare|1|1|Skulk (This creature can't be blocked by creatures with greater power.)$When Persistent Nightmare deals combat damage to a player, return it to its owner's hand.| Stitched Mangler|Shadows over Innistrad|89|C|{2}{U}|Creature - Zombie Horror|2|3|Stitched Mangler enters the battlefield tapped.$When Stitched Mangler enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.| Thing in the Ice|Shadows over Innistrad|92a|R|{1}{U}|Creature - Horror|0|4|Defender$Thing in the Ice enters the battlefield with four ice counters on it.$Whenever you cast an instant or sorcery spell, remove an ice counter from Thing in the Ice. Then if it has no ice counters on it, transform it.| Awoken Horror|Shadows over Innistrad|92b|R||Creature - Kraken Horror|7|8|When this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands.| @@ -56976,6 +56983,7 @@ Mindwrack Demon|Shadows over Innistrad|124|M|{2}{B}{B}|Creature - Demon|4|5|Flyi Pick the Brain|Shadows over Innistrad|129|U|{2}{B}|Sorcery|||Target opponent reveals his or her hand. You choose a nonland card from it and exile that card.$Delirium — If there are four or more card types among cards in your graveyard, search that player's graveyard, hand, and library for any number of cards with the same name as the exiled card, exile those cards, then that player shuffles his or her library.| Relentless Dead|Shadows over Innistrad|131|M|{B}{B}|Creature - Zombie|2|2|Menace (This creature can't be blocked except by two or more creatures.)$When Relentless Dead dies, you may pay {B}. If you do, return it to its owner's hand.$When Relentless Dead dies, you may pay {X}. If you do, return another target Zombie creature card with converted mana cost X from your graveyard to the battlefield.| Shamble Back|Shadows over Innistrad|134|C|{B}|Sorcery|||Exile target creature card from a graveyard. Put a 2/2 black Zombie creature token onto the battlefield. You gain 2 life.| +Sinister Concoction|Shadows over Innistrad|135|U|{B}|Enchantment|||{B}, Pay 1 life, Put the top card of your library into your graveyard, Discard a card, Sacrifice Sinister Concoction: Destroy target creature.| Tooth Collector|Shadows over Innistrad|140|U|{2}{B}|Creature - Human Rogue|3|2|When Tooth Collector enters the battlefield, target creature an opponent controls gets -1/-1 until end of turn.${Delirium — At the beginning of each opponent's upkeep, if there are four or more card types among cards in your graveyard, target creature that player controls gets -1/-1 until end of turn.| Dance with Devils|Shadows over Innistrad|150|U|{3}{R}|Instant|||Put two 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player."| Devil's Playground|Shadows over Innistrad|151|R|{4}{R}{R}|Sorcery|||Put four 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player."| @@ -56999,6 +57007,8 @@ Quilled Wolf|Shadows over Innistrad|222|C|{1}{G}|Creature - Wolf|2|2|{5}{G}: Qui Soul Swallower|Shadows over Innistrad|230|R|{2}{G}{G}|Creature - Wurm|3|3|Trample$Delirium — At the beginning of your upkeep, if there are four or more card types among cards in your graveyard, put three +1/+1 counters on Soul Swallower.| Stoic Builder|Shadows over Innistrad|231|C|{2}{G}|Creature - Human|2|3|When Stoic Builder enters the battlefield, you may return target land card from your graveyard to your hand.| Watcher in the Web|Shadows over Innistrad|239|C|{4}{G}|Creature - Spider|2|5|Reach (This creature can block creature with flying.)$Watcher in the Web can block an additional seven creatures each combat.| +Anguished Unmaking|Shadows over Innistrad|242|R|{1}{W}{B}|Instant|||Exile target nonland permanent. You lose 3 life.| +Nahiri, the Harbinger|Shadows over Innistrad|247|M|{2}{R}{W}|Planeswalker - Nahiri|||+2: You may discard a card. If you do, draw a card.$-2: Exile target enchantment, tapped artifact, or tapped creature.$-8 Search your library for an artifact or creature card, put it onto the battlefield, then shuffle your library. It gains haste. Return it to your hand at the beginning of the next end step.| Brain in a Jar|Shadows over Innistrad|252|R|{2}|Artifact|||{1}, {T}: Put a charge counter on Brain in a Jar, then you may cast an instant or sorcery card with converted mana costs equal to the number of charge counters on Brain in a Jar from your hand without paying its mana cost.${3}< {T}, Remove X charge counters from Brain in a Jar: Scry X.| Explosive Apparatus|Shadows over Innistrad|255|C|{1}|Artifact|||{3}, {T}, Sacrifice Explosive Apparatus: Explosive Apparatus deals 2 damage to target creature or player.| Magnifying Glass|Shadows over Innistrad|258|U|{3}|Artifact|||{T}: Add {C} to your mana pool.${4}, {T}: Investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| From 272f68b530b59d42a751089be4f7928af994e178 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Mon, 14 Mar 2016 18:15:57 +0100 Subject: [PATCH 11/44] Fixed handling of DontUntapInControllersNextUntapStepTargetEffect (fixes Sleep bug). --- .../battleforzendikar/GuardianOfTazeem.java | 10 ++- .../mage/sets/commander/PollenLullaby.java | 17 +++-- .../commander2014/BreachingLeviathan.java | 12 +++- .../sets/darkascension/ClingingMists.java | 31 ++++---- Mage.Sets/src/mage/sets/invasion/Tangle.java | 17 +++-- Mage.Sets/src/mage/sets/magic2010/Sleep.java | 34 +++++---- .../sets/portalthreekingdoms/Exhaustion.java | 27 ++++--- .../src/mage/sets/theros/TritonTactics.java | 21 +++--- .../sets/zendikar/LorthosTheTidemaker.java | 70 ++++--------------- .../combat/AttackBlockRestrictionsTest.java | 32 ++++----- ...nControllersNextUntapStepTargetEffect.java | 69 ++++++++++-------- 11 files changed, 169 insertions(+), 171 deletions(-) diff --git a/Mage.Sets/src/mage/sets/battleforzendikar/GuardianOfTazeem.java b/Mage.Sets/src/mage/sets/battleforzendikar/GuardianOfTazeem.java index d82f72d6ad5..3df407ef198 100644 --- a/Mage.Sets/src/mage/sets/battleforzendikar/GuardianOfTazeem.java +++ b/Mage.Sets/src/mage/sets/battleforzendikar/GuardianOfTazeem.java @@ -152,12 +152,10 @@ class GuardianOfTazeemEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Permanent land = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); Permanent targetCreature = game.getPermanent(source.getFirstTarget()); - if (land != null && targetCreature != null) { - if (land.hasSubtype("Island")) { - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("that creature"); - effect.setTargetPointer(new FixedTarget(targetCreature, game)); - game.addEffect(effect, source); - } + if (land != null && targetCreature != null && land.hasSubtype("Island")) { + ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("that creature"); + effect.setTargetPointer(new FixedTarget(targetCreature, game)); + game.addEffect(effect, source); } return true; } diff --git a/Mage.Sets/src/mage/sets/commander/PollenLullaby.java b/Mage.Sets/src/mage/sets/commander/PollenLullaby.java index 96c1733994e..8a02781ca68 100644 --- a/Mage.Sets/src/mage/sets/commander/PollenLullaby.java +++ b/Mage.Sets/src/mage/sets/commander/PollenLullaby.java @@ -27,13 +27,15 @@ */ package mage.sets.commander; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DoIfClashWonEffect; -import mage.abilities.effects.common.PreventAllDamageByAllEffect; import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.PreventAllDamageByAllEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; @@ -43,7 +45,7 @@ import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; /** * @@ -55,7 +57,6 @@ public class PollenLullaby extends CardImpl { super(ownerId, 26, "Pollen Lullaby", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{1}{W}"); this.expansionSetCode = "CMD"; - // Prevent all combat damage that would be dealt this turn. Clash with an opponent. If you win, creatures that player controls don't untap during the player's next untap step. this.getSpellAbility().addEffect(new PreventAllDamageByAllEffect(Duration.EndOfTurn, true)); this.getSpellAbility().addEffect(new DoIfClashWonEffect(new PollenLullabyEffect(), true, null)); @@ -86,10 +87,13 @@ class PollenLullabyEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { - for (Permanent creature: game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game)) { - creature.tap(game); + List doNotUntapNextUntapStep = new ArrayList<>(); + for (Permanent creature : game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game)) { + doNotUntapNextUntapStep.add(creature); + } + if (!doNotUntapNextUntapStep.isEmpty()) { ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("This creature"); - effect.setTargetPointer(new FixedTarget(creature.getId())); + effect.setTargetPointer(new FixedTargets(doNotUntapNextUntapStep, game)); game.addEffect(effect, source); } return true; @@ -103,4 +107,3 @@ class PollenLullabyEffect extends OneShotEffect { } } - diff --git a/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java b/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java index 4bc14d7bfca..5a6bea5fcec 100644 --- a/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java +++ b/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java @@ -27,6 +27,8 @@ */ package mage.sets.commander2014; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import mage.MageInt; import mage.ObjectColor; @@ -46,7 +48,7 @@ import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; import mage.watchers.common.CastFromHandWatcher; /** @@ -105,10 +107,14 @@ class BreachingLeviathanEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { + List doNotUntapNextUntapStep = new ArrayList<>(); for (Permanent creature : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { creature.tap(game); - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); - effect.setTargetPointer(new FixedTarget(creature.getId())); + doNotUntapNextUntapStep.add(creature); + } + if (!doNotUntapNextUntapStep.isEmpty()) { + ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("This creature"); + effect.setTargetPointer(new FixedTargets(doNotUntapNextUntapStep, game)); game.addEffect(effect, source); } return true; diff --git a/Mage.Sets/src/mage/sets/darkascension/ClingingMists.java b/Mage.Sets/src/mage/sets/darkascension/ClingingMists.java index 9a3c5f17b56..dc58486637d 100644 --- a/Mage.Sets/src/mage/sets/darkascension/ClingingMists.java +++ b/Mage.Sets/src/mage/sets/darkascension/ClingingMists.java @@ -27,27 +27,25 @@ */ package mage.sets.darkascension; -import mage.constants.CardType; -import mage.constants.Rarity; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import mage.abilities.Ability; import mage.abilities.condition.common.FatefulHourCondition; import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; import mage.abilities.effects.common.PreventAllDamageByAllEffect; import mage.cards.CardImpl; +import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; -import mage.constants.PhaseStep; +import mage.constants.Rarity; import mage.filter.common.FilterAttackingCreature; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; - -import java.util.UUID; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; /** * @@ -59,7 +57,6 @@ public class ClingingMists extends CardImpl { super(ownerId, 109, "Clinging Mists", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{2}{G}"); this.expansionSetCode = "DKA"; - // Prevent all combat damage that would be dealt this turn. this.getSpellAbility().addEffect(new PreventAllDamageByAllEffect(null, Duration.EndOfTurn, true)); @@ -94,11 +91,15 @@ class ClingingMistsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - for (Permanent creature: game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { + List doNotUntapNextUntapStep = new ArrayList<>(); + for (Permanent creature : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { creature.tap(game); - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); - effect.setTargetPointer(new FixedTarget(creature.getId())); - game.addEffect(effect, source); + doNotUntapNextUntapStep.add(creature); + } + if (!doNotUntapNextUntapStep.isEmpty()) { + ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("This creature"); + effect.setTargetPointer(new FixedTargets(doNotUntapNextUntapStep, game)); + game.addEffect(effect, source); } return true; } diff --git a/Mage.Sets/src/mage/sets/invasion/Tangle.java b/Mage.Sets/src/mage/sets/invasion/Tangle.java index 112e525cc3b..ddcf3d3dd26 100644 --- a/Mage.Sets/src/mage/sets/invasion/Tangle.java +++ b/Mage.Sets/src/mage/sets/invasion/Tangle.java @@ -27,12 +27,14 @@ */ package mage.sets.invasion; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.PreventAllDamageByAllEffect; import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.PreventAllDamageByAllEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; @@ -43,7 +45,7 @@ import mage.filter.predicate.permanent.AttackingPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; /** * @@ -55,7 +57,6 @@ public class Tangle extends CardImpl { super(ownerId, 213, "Tangle", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{1}{G}"); this.expansionSetCode = "INV"; - // Prevent all combat damage that would be dealt this turn. this.getSpellAbility().addEffect(new PreventAllDamageByAllEffect(Duration.EndOfTurn, true)); // Each attacking creature doesn't untap during its controller's next untap step. @@ -99,9 +100,13 @@ class TangleEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - for (Permanent permanent :game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); - effect.setTargetPointer(new FixedTarget(permanent.getId())); + List doNotUntapNextUntapStep = new ArrayList<>(); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { + doNotUntapNextUntapStep.add(permanent); + } + if (!doNotUntapNextUntapStep.isEmpty()) { + ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("This creature"); + effect.setTargetPointer(new FixedTargets(doNotUntapNextUntapStep, game)); game.addEffect(effect, source); } return true; diff --git a/Mage.Sets/src/mage/sets/magic2010/Sleep.java b/Mage.Sets/src/mage/sets/magic2010/Sleep.java index 9b74bff5919..76ec7fb0a41 100644 --- a/Mage.Sets/src/mage/sets/magic2010/Sleep.java +++ b/Mage.Sets/src/mage/sets/magic2010/Sleep.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,29 +20,30 @@ * 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 mage.sets.magic2010; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPlayer; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; /** * @@ -83,11 +84,16 @@ class SleepEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getFirstTarget()); if (player != null) { - for (Permanent creature: game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game)) { + List doNotUntapNextUntapStep = new ArrayList<>(); + for (Permanent creature : game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game)) { creature.tap(game); - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); - effect.setTargetPointer(new FixedTarget(creature.getId())); - game.addEffect(effect, source); + doNotUntapNextUntapStep.add(creature); + } + if (!doNotUntapNextUntapStep.isEmpty()) { + ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("", player.getId()); + effect.setText("those creatures don't untap during that player's next untap step"); + effect.setTargetPointer(new FixedTargets(doNotUntapNextUntapStep, game)); + game.addEffect(effect, source); } return true; } diff --git a/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java b/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java index 12ca4274395..6156526574e 100644 --- a/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java +++ b/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java @@ -27,6 +27,8 @@ */ package mage.sets.portalthreekingdoms; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; @@ -43,7 +45,7 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetOpponent; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; /** * @@ -55,7 +57,6 @@ public class Exhaustion extends CardImpl { super(ownerId, 42, "Exhaustion", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{2}{U}"); this.expansionSetCode = "PTK"; - // Creatures and lands target opponent controls don't untap during his or her next untap step. this.getSpellAbility().addEffect(new ExhaustionEffect()); this.getSpellAbility().addTarget(new TargetOpponent()); @@ -72,35 +73,39 @@ public class Exhaustion extends CardImpl { } class ExhaustionEffect extends OneShotEffect { - + private static final FilterPermanent filter = new FilterPermanent(); - + static { filter.add(Predicates.or(new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.LAND))); } - + ExhaustionEffect() { super(Outcome.Detriment); this.staticText = "Creatures and lands target opponent controls don't untap during his or her next untap step."; } - + ExhaustionEffect(final ExhaustionEffect effect) { super(effect); } - + @Override public ExhaustionEffect copy() { return new ExhaustionEffect(this); } - + @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getFirstTarget()); if (player != null) { + List doNotUntapNextUntapStep = new ArrayList<>(); for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, player.getId(), game)) { - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); - effect.setTargetPointer(new FixedTarget(permanent.getId())); - game.addEffect(effect, source); + doNotUntapNextUntapStep.add(permanent); + } + if (!doNotUntapNextUntapStep.isEmpty()) { + ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("This creature"); + effect.setTargetPointer(new FixedTargets(doNotUntapNextUntapStep, game)); + game.addEffect(effect, source); } return true; } diff --git a/Mage.Sets/src/mage/sets/theros/TritonTactics.java b/Mage.Sets/src/mage/sets/theros/TritonTactics.java index f58b2966609..2f97bf4a9de 100644 --- a/Mage.Sets/src/mage/sets/theros/TritonTactics.java +++ b/Mage.Sets/src/mage/sets/theros/TritonTactics.java @@ -27,8 +27,10 @@ */ package mage.sets.theros; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -52,7 +54,7 @@ import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; import mage.watchers.Watcher; @@ -69,7 +71,7 @@ public class TritonTactics extends CardImpl { // Up to two target creatures each get +0/+3 until end of turn. Untap those creatures. // At this turn's next end of combat, tap each creature that was blocked by one of those // creatures this turn and it doesn't untap during its controller's next untap step. - Effect effect = new BoostTargetEffect(0,3, Duration.EndOfTurn); + Effect effect = new BoostTargetEffect(0, 3, Duration.EndOfTurn); effect.setText("Up to two target creatures each get +0/+3 until end of turn"); this.getSpellAbility().addEffect(effect); this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2)); @@ -78,7 +80,6 @@ public class TritonTactics extends CardImpl { this.getSpellAbility().addWatcher(new BlockedCreaturesWatcher()); - } public TritonTactics(final TritonTactics card) { @@ -110,7 +111,7 @@ class TritonTacticsUntapTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Set targets = new HashSet<>(); - for (UUID target: targetPointer.getTargets(game, source)) { + for (UUID target : targetPointer.getTargets(game, source)) { Permanent permanent = game.getPermanent(target); if (permanent != null) { permanent.untap(game); @@ -189,17 +190,21 @@ class TritonTacticsEndOfCombatEffect extends OneShotEffect { Object object = game.getState().getValue("blockedAttackers" + source.getSourceId()); if (object != null && object instanceof Map) { attackerMap = (Map>) object; - for (Set attackerSet :attackerMap.values()) { + for (Set attackerSet : attackerMap.values()) { + List doNotUntapNextUntapStep = new ArrayList<>(); for (Permanent creature : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), game)) { if (attackerSet.contains(CardUtil.getCardZoneString(null, creature.getId(), game))) { // tap creature and add the not untap effect creature.tap(game); - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); - effect.setTargetPointer(new FixedTarget(creature.getId())); - game.addEffect(effect, source); + doNotUntapNextUntapStep.add(creature); game.informPlayers(new StringBuilder("Triton Tactics: ").append(creature.getName()).append(" doesn't untap during its controller's next untap step").toString()); } } + if (!doNotUntapNextUntapStep.isEmpty()) { + ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("This creature"); + effect.setTargetPointer(new FixedTargets(doNotUntapNextUntapStep, game)); + game.addEffect(effect, source); + } } } if (attackerMap != null) { diff --git a/Mage.Sets/src/mage/sets/zendikar/LorthosTheTidemaker.java b/Mage.Sets/src/mage/sets/zendikar/LorthosTheTidemaker.java index 098a382196b..8f0de3a5745 100644 --- a/Mage.Sets/src/mage/sets/zendikar/LorthosTheTidemaker.java +++ b/Mage.Sets/src/mage/sets/zendikar/LorthosTheTidemaker.java @@ -28,25 +28,18 @@ package mage.sets.zendikar; import java.util.UUID; - +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; -import mage.cards.CardImpl; -import mage.constants.Outcome; import mage.filter.FilterPermanent; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPermanent; -import mage.target.targetpointer.FixedTarget; /** * @@ -66,7 +59,11 @@ public class LorthosTheTidemaker extends CardImpl { this.toughness = new MageInt(8); // Whenever Lorthos, the Tidemaker attacks, you may pay {8}. If you do, tap up to eight target permanents. Those permanents don't untap during their controllers' next untap steps. - AttacksTriggeredAbility ability = new AttacksTriggeredAbility(new LorthosTheTideMakerEffect(), true); + DoIfCostPaid effect = new DoIfCostPaid(new TapTargetEffect(), new GenericManaCost(8), "Pay {8} to tap up to 8 target permanents? (They don't untap during their controllers' next untap steps)"); + AttacksTriggeredAbility ability = new AttacksTriggeredAbility(effect, false); + Effect effect2 = new DontUntapInControllersNextUntapStepTargetEffect(); + effect2.setText("Those permanents don't untap during their controllers' next untap steps"); + effect.addEffect(effect2); ability.addTarget(new TargetPermanent(0, 8, filter, false)); this.addAbility(ability); } @@ -80,44 +77,3 @@ public class LorthosTheTidemaker extends CardImpl { return new LorthosTheTidemaker(this); } } - -class LorthosTheTideMakerEffect extends OneShotEffect { - - public LorthosTheTideMakerEffect() { - super(Outcome.Tap); - this.staticText = "you may pay {8}. If you do, tap up to eight target permanents. Those permanents don't untap during their controllers' next untap steps"; - } - - public LorthosTheTideMakerEffect(final LorthosTheTideMakerEffect effect) { - super(effect); - } - - @Override - public LorthosTheTideMakerEffect copy() { - return new LorthosTheTideMakerEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - Cost cost = new ManaCostsImpl("{8}"); - if (player.chooseUse(Outcome.Tap, "Pay " + cost.getText() + " and " + staticText, source, game)) { - cost.clearPaid(); - if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) { - for (UUID target : this.targetPointer.getTargets(game, source)) { - Permanent permanent = game.getPermanent(target); - if (permanent != null) { - permanent.tap(game); - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); - effect.setTargetPointer(new FixedTarget(permanent.getId())); - game.addEffect(effect, source); - } - } - return false; - } - } - } - return false; - } -} diff --git a/Mage.Tests/src/test/java/org/mage/test/combat/AttackBlockRestrictionsTest.java b/Mage.Tests/src/test/java/org/mage/test/combat/AttackBlockRestrictionsTest.java index 191d4f0c89b..86542b6609c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/combat/AttackBlockRestrictionsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/combat/AttackBlockRestrictionsTest.java @@ -1,6 +1,5 @@ package org.mage.test.combat; - import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; @@ -35,11 +34,13 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { } /** - * Tests attacking creature doesn't untap after being blocked by Wall of Frost + * Tests attacking creature doesn't untap after being blocked by Wall of + * Frost */ @Test public void testWallofWrost() { - addCard(Zone.BATTLEFIELD, playerA, "Wall of Frost"); + // Whenever Wall of Frost blocks a creature, that creature doesn't untap during its controller's next untap step. + addCard(Zone.BATTLEFIELD, playerA, "Wall of Frost"); // 0/7 addCard(Zone.BATTLEFIELD, playerB, "Craw Wurm"); attack(2, playerB, "Craw Wurm"); @@ -56,9 +57,8 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { } /** - * Tests card that says that it can't block specific cards - * Hunted Ghoul: - * Hunted Ghoul can't block Humans. + * Tests card that says that it can't block specific cards Hunted Ghoul: + * Hunted Ghoul can't block Humans. */ @Test public void testFilteredBlocking() { @@ -76,9 +76,8 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { } /** - * Tests card that says that it can't block specific cards still can block others - * Hunted Ghoul: - * Hunted Ghoul can't block Humans. + * Tests card that says that it can't block specific cards still can block + * others Hunted Ghoul: Hunted Ghoul can't block Humans. */ @Test public void testFilteredBlocking2() { @@ -176,7 +175,8 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { } /** - * Tests "Creatures with power less than Champion of Lambholt's power can't block creatures you control." + * Tests "Creatures with power less than Champion of Lambholt's power can't + * block creatures you control." */ @Test public void testChampionOfLambholt() { @@ -244,22 +244,22 @@ public class AttackBlockRestrictionsTest extends CardTestPlayerBase { assertCounterCount(playerA, CounterType.POISON, 1); } - + @Test public void testCantBeBlockedTormentedSoul() { addCard(Zone.BATTLEFIELD, playerB, "Tormented Soul"); - + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); - + attack(2, playerB, "Tormented Soul"); block(2, playerA, "Tormented Soul", "Memnite"); - + setStopAt(2, PhaseStep.END_TURN); execute(); - + assertPermanentCount(playerA, "Memnite", 1); assertPermanentCount(playerB, "Tormented Soul", 1); - + assertLife(playerA, 19); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java index e679a45d44b..7b2001e7a11 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInControllersNextUntapStepTargetEffect.java @@ -27,6 +27,8 @@ */ package mage.abilities.effects.common; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; @@ -45,8 +47,10 @@ import mage.game.permanent.Permanent; */ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousRuleModifyingEffectImpl { - private int validForTurnNum; + private UUID onlyIfControlledByPlayer; private String targetName; + // holds the info what target was already handled in Untap of its controller + private final Map handledTargetsDuringTurn = new HashMap<>(); /** * Attention: This effect won't work with targets controlled by different @@ -55,19 +59,30 @@ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousR * */ public DontUntapInControllersNextUntapStepTargetEffect() { - super(Duration.Custom, Outcome.Detriment, false, true); + this(""); } public DontUntapInControllersNextUntapStepTargetEffect(String targetName) { - this(); + this(targetName, null); + } + + /** + * + * @param targetName used as target text for the generated rule text + * @param onlyIfControlledByPlayer the effect only works if the permanent is + * controlled by that controller, null = it works for all players + */ + public DontUntapInControllersNextUntapStepTargetEffect(String targetName, UUID onlyIfControlledByPlayer) { + super(Duration.Custom, Outcome.Detriment, false, true); this.targetName = targetName; + this.onlyIfControlledByPlayer = onlyIfControlledByPlayer; } public DontUntapInControllersNextUntapStepTargetEffect(final DontUntapInControllersNextUntapStepTargetEffect effect) { super(effect); - this.validForTurnNum = effect.validForTurnNum; this.targetName = effect.targetName; - + this.handledTargetsDuringTurn.putAll(effect.handledTargetsDuringTurn); + this.onlyIfControlledByPlayer = effect.onlyIfControlledByPlayer; } @Override @@ -97,42 +112,40 @@ public class DontUntapInControllersNextUntapStepTargetEffect extends ContinuousR @Override public boolean applies(GameEvent event, Ability source, Game game) { - // the check for turn number is needed if multiple effects are added to prevent untap in next untap step of controller - // if we don't check for turn number, every untap step of a turn only one effect would be used instead of correctly only one time - // to skip the untap effect. - - // Discard effect if it's related to a previous turn - if (validForTurnNum > 0 && validForTurnNum < game.getTurnNum()) { - discard(); - return false; - } - // remember the turn of the untap step the effect has to be applied + // the check if a permanent untap pahse is already handled is needed if multiple effects are added to prevent untap in next untap step of controller + // if we don't check it for every untap step of a turn only one effect would be consumed instead of all be valid for the next untap step if (GameEvent.EventType.UNTAP_STEP.equals(event.getType())) { - UUID controllerId = null; + boolean allHandled = true; for (UUID targetId : getTargetPointer().getTargets(game, source)) { Permanent permanent = game.getPermanent(targetId); if (permanent != null) { - controllerId = permanent.getControllerId(); + if (game.getActivePlayerId().equals(permanent.getControllerId()) + || (game.getActivePlayerId().equals(onlyIfControlledByPlayer))) { // if effect works only for specific player, all permanents have to be set to handled in that players untap step + if (!handledTargetsDuringTurn.containsKey(targetId)) { + // it's the untep step of the current controller and the effect was not handled for this target yet, so do it now + handledTargetsDuringTurn.put(targetId, false); + allHandled = false; + } else if (!handledTargetsDuringTurn.get(targetId)) { + // if it was already ready to be handled on an previous Untap step set it to done if not already so + handledTargetsDuringTurn.put(targetId, true); + } + } else { + allHandled = false; + } } } - if (controllerId == null) { // no more targets on the battlefield, effect can be discarded + if (allHandled) { discard(); - return false; - } - - if (game.getActivePlayerId().equals(controllerId)) { - if (validForTurnNum == game.getTurnNum()) { // the turn has a second untap step but the effect is already related to the first untap step - discard(); - return false; - } - validForTurnNum = game.getTurnNum(); } } if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getType() == EventType.UNTAP) { - if (targetPointer.getTargets(game, source).contains(event.getTargetId())) { + if (handledTargetsDuringTurn.containsKey(event.getTargetId()) + && !handledTargetsDuringTurn.get(event.getTargetId()) + && getTargetPointer().getTargets(game, source).contains(event.getTargetId())) { Permanent permanent = game.getPermanent(event.getTargetId()); if (permanent != null && game.getActivePlayerId().equals(permanent.getControllerId())) { + handledTargetsDuringTurn.put(event.getTargetId(), true); return true; } } From 0e2c92802665f080bfb5f18c7f244714248e1152 Mon Sep 17 00:00:00 2001 From: drmDev Date: Mon, 14 Mar 2016 14:47:43 -0400 Subject: [PATCH 12/44] Card Implementations for 5 Ice Age Talisman cycle --- .../mage/sets/iceage/HematiteTalisman.java | 74 +++++++++++++++++++ .../mage/sets/iceage/LapisLazuliTalisman.java | 74 +++++++++++++++++++ .../mage/sets/iceage/MalachiteTalisman.java | 74 +++++++++++++++++++ .../src/mage/sets/iceage/NacreTalisman.java | 74 +++++++++++++++++++ .../src/mage/sets/iceage/OnyxTalisman.java | 74 +++++++++++++++++++ 5 files changed, 370 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/iceage/HematiteTalisman.java create mode 100644 Mage.Sets/src/mage/sets/iceage/LapisLazuliTalisman.java create mode 100644 Mage.Sets/src/mage/sets/iceage/MalachiteTalisman.java create mode 100644 Mage.Sets/src/mage/sets/iceage/NacreTalisman.java create mode 100644 Mage.Sets/src/mage/sets/iceage/OnyxTalisman.java diff --git a/Mage.Sets/src/mage/sets/iceage/HematiteTalisman.java b/Mage.Sets/src/mage/sets/iceage/HematiteTalisman.java new file mode 100644 index 00000000000..f150d6758a2 --- /dev/null +++ b/Mage.Sets/src/mage/sets/iceage/HematiteTalisman.java @@ -0,0 +1,74 @@ +/* + * 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 mage.sets.iceage; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastAllTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.target.TargetPermanent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class HematiteTalisman extends CardImpl { + + private final static FilterSpell filter = new FilterSpell("a red spell"); + + static { + filter.add(new ColorPredicate(ObjectColor.RED)); + } + + public HematiteTalisman(UUID ownerId) { + super(ownerId, 295, "Hematite Talisman", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{2}"); + this.expansionSetCode = "ICE"; + + // Whenever a player casts a red spell, you may pay {3}. If you do, untap target permanent. + Ability ability = new SpellCastAllTriggeredAbility(new DoIfCostPaid(new UntapTargetEffect(), new ManaCostsImpl("{3}")), filter, true); + ability.addTarget(new TargetPermanent()); + this.addAbility(ability); + } + + public HematiteTalisman(final HematiteTalisman card) { + super(card); + } + + @Override + public HematiteTalisman copy() { + return new HematiteTalisman(this); + } +} diff --git a/Mage.Sets/src/mage/sets/iceage/LapisLazuliTalisman.java b/Mage.Sets/src/mage/sets/iceage/LapisLazuliTalisman.java new file mode 100644 index 00000000000..904158d71c6 --- /dev/null +++ b/Mage.Sets/src/mage/sets/iceage/LapisLazuliTalisman.java @@ -0,0 +1,74 @@ +/* + * 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 mage.sets.iceage; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastAllTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.target.TargetPermanent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class LapisLazuliTalisman extends CardImpl { + + private final static FilterSpell filter = new FilterSpell("a blue spell"); + + static { + filter.add(new ColorPredicate(ObjectColor.BLUE)); + } + + public LapisLazuliTalisman(UUID ownerId) { + super(ownerId, 302, "Lapis Lazuli Talisman", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{2}"); + this.expansionSetCode = "ICE"; + + // Whenever a player casts a blue spell, you may pay {3}. If you do, untap target permanent. + Ability ability = new SpellCastAllTriggeredAbility(new DoIfCostPaid(new UntapTargetEffect(), new ManaCostsImpl("{3}")), filter, true); + ability.addTarget(new TargetPermanent()); + this.addAbility(ability); + } + + public LapisLazuliTalisman(final LapisLazuliTalisman card) { + super(card); + } + + @Override + public LapisLazuliTalisman copy() { + return new LapisLazuliTalisman(this); + } +} diff --git a/Mage.Sets/src/mage/sets/iceage/MalachiteTalisman.java b/Mage.Sets/src/mage/sets/iceage/MalachiteTalisman.java new file mode 100644 index 00000000000..4e904064059 --- /dev/null +++ b/Mage.Sets/src/mage/sets/iceage/MalachiteTalisman.java @@ -0,0 +1,74 @@ +/* + * 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 mage.sets.iceage; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastAllTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.target.TargetPermanent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class MalachiteTalisman extends CardImpl { + + private final static FilterSpell filter = new FilterSpell("a green spell"); + + static { + filter.add(new ColorPredicate(ObjectColor.GREEN)); + } + + public MalachiteTalisman(UUID ownerId) { + super(ownerId, 303, "Malachite Talisman", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{2}"); + this.expansionSetCode = "ICE"; + + // Whenever a player casts a green spell, you may pay {3}. If you do, untap target permanent. + Ability ability = new SpellCastAllTriggeredAbility(new DoIfCostPaid(new UntapTargetEffect(), new ManaCostsImpl("{3}")), filter, true); + ability.addTarget(new TargetPermanent()); + this.addAbility(ability); + } + + public MalachiteTalisman(final MalachiteTalisman card) { + super(card); + } + + @Override + public MalachiteTalisman copy() { + return new MalachiteTalisman(this); + } +} diff --git a/Mage.Sets/src/mage/sets/iceage/NacreTalisman.java b/Mage.Sets/src/mage/sets/iceage/NacreTalisman.java new file mode 100644 index 00000000000..3fb5ba99147 --- /dev/null +++ b/Mage.Sets/src/mage/sets/iceage/NacreTalisman.java @@ -0,0 +1,74 @@ +/* + * 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 mage.sets.iceage; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastAllTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.target.TargetPermanent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class NacreTalisman extends CardImpl { + + private final static FilterSpell filter = new FilterSpell("a white spell"); + + static { + filter.add(new ColorPredicate(ObjectColor.WHITE)); + } + + public NacreTalisman(UUID ownerId) { + super(ownerId, 304, "Nacre Talisman", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{2}"); + this.expansionSetCode = "ICE"; + + // Whenever a player casts a white spell, you may pay {3}. If you do, untap target permanent. + Ability ability = new SpellCastAllTriggeredAbility(new DoIfCostPaid(new UntapTargetEffect(), new ManaCostsImpl("{3}")), filter, true); + ability.addTarget(new TargetPermanent()); + this.addAbility(ability); + } + + public NacreTalisman(final NacreTalisman card) { + super(card); + } + + @Override + public NacreTalisman copy() { + return new NacreTalisman(this); + } +} diff --git a/Mage.Sets/src/mage/sets/iceage/OnyxTalisman.java b/Mage.Sets/src/mage/sets/iceage/OnyxTalisman.java new file mode 100644 index 00000000000..4cd051eea05 --- /dev/null +++ b/Mage.Sets/src/mage/sets/iceage/OnyxTalisman.java @@ -0,0 +1,74 @@ +/* + * 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 mage.sets.iceage; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastAllTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.UntapTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.target.TargetPermanent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class OnyxTalisman extends CardImpl { + + private final static FilterSpell filter = new FilterSpell("a black spell"); + + static { + filter.add(new ColorPredicate(ObjectColor.BLACK)); + } + + public OnyxTalisman(UUID ownerId) { + super(ownerId, 306, "Onyx Talisman", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{2}"); + this.expansionSetCode = "ICE"; + + // Whenever a player casts a black spell, you may pay {3}. If you do, untap target permanent. + Ability ability = new SpellCastAllTriggeredAbility(new DoIfCostPaid(new UntapTargetEffect(), new ManaCostsImpl("{3}")), filter, true); + ability.addTarget(new TargetPermanent()); + this.addAbility(ability); + } + + public OnyxTalisman(final OnyxTalisman card) { + super(card); + } + + @Override + public OnyxTalisman copy() { + return new OnyxTalisman(this); + } +} From 169446d1e573c293effbd223ab00f3f7541d4917 Mon Sep 17 00:00:00 2001 From: DjB Date: Mon, 14 Mar 2016 18:51:57 -0500 Subject: [PATCH 13/44] Update Misstep.java --- .../mage/sets/mercadianmasques/Misstep.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/Mage.Sets/src/mage/sets/mercadianmasques/Misstep.java b/Mage.Sets/src/mage/sets/mercadianmasques/Misstep.java index c11c5f566ca..ab772c40044 100644 --- a/Mage.Sets/src/mage/sets/mercadianmasques/Misstep.java +++ b/Mage.Sets/src/mage/sets/mercadianmasques/Misstep.java @@ -27,21 +27,23 @@ */ package mage.sets.mercadianmasques; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Rarity; import mage.filter.common.FilterCreaturePermanent; +import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPlayer; -import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; /** * @@ -86,15 +88,20 @@ class MisstepEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getFirstTarget()); - if (player != null) { - for (Permanent creature: game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game)) { - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); - effect.setTargetPointer(new FixedTarget(creature.getId())); - game.addEffect(effect, source); - } - return true; + Player player = game.getPlayer(source.getFirstTarget()); + if (player != null) { + List doNotUntapNextUntapStep = new ArrayList<>(); + for (Permanent creature : game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), player.getId(), game)) { + doNotUntapNextUntapStep.add(creature); } + if (!doNotUntapNextUntapStep.isEmpty()) { + ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("", player.getId()); + effect.setText("those creatures don't untap during that player's next untap step"); + effect.setTargetPointer(new FixedTargets(doNotUntapNextUntapStep, game)); + game.addEffect(effect, source); + } + return true; + } return false; } } From 8e4731e850053334787a4b4f0911aaff24928798 Mon Sep 17 00:00:00 2001 From: Jerrad Bieno Date: Mon, 14 Mar 2016 21:33:31 -0500 Subject: [PATCH 14/44] Simplified predicate lines and changed filter text. Made the predicates into separate lines and changed the filter text from "came into play" to "entered the battlefield". --- Mage.Sets/src/mage/sets/planarchaos/CradleToGrave.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/sets/planarchaos/CradleToGrave.java b/Mage.Sets/src/mage/sets/planarchaos/CradleToGrave.java index e82e4c61be2..0a0bedf0877 100644 --- a/Mage.Sets/src/mage/sets/planarchaos/CradleToGrave.java +++ b/Mage.Sets/src/mage/sets/planarchaos/CradleToGrave.java @@ -45,10 +45,11 @@ import mage.target.common.TargetCreaturePermanent; */ public class CradleToGrave extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nonblack creature that came into play this turn"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nonblack creature that entered the battlefield this turn"); static { - filter.add(Predicates.and(Predicates.not(new ColorPredicate(ObjectColor.BLACK)), new EnteredThisTurnPredicate())); + filter.add(Predicates.not(new ColorPredicate(ObjectColor.BLACK))); + filter.add(new EnteredThisTurnPredicate()); } public CradleToGrave(UUID ownerId) { From 03cec66bae756a8a2df2ecc09863752eb5058d62 Mon Sep 17 00:00:00 2001 From: okuRaku Date: Mon, 14 Mar 2016 23:23:01 -0500 Subject: [PATCH 15/44] fix for Exhaustion on things that entered after resolution --- .../sets/portalthreekingdoms/Exhaustion.java | 12 +- ...ntapInOpponentsNextUntapStepAllEffect.java | 143 ++++++++++++++++++ 2 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java diff --git a/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java b/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java index 12ca4274395..1581768ffdf 100644 --- a/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java +++ b/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.DontUntapInOpponentsNextUntapStepAllEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Outcome; @@ -40,7 +40,6 @@ import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetOpponent; import mage.target.targetpointer.FixedTarget; @@ -96,12 +95,11 @@ class ExhaustionEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getFirstTarget()); + if (player != null) { - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, player.getId(), game)) { - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); - effect.setTargetPointer(new FixedTarget(permanent.getId())); - game.addEffect(effect, source); - } + ContinuousEffect effect = new DontUntapInOpponentsNextUntapStepAllEffect(filter); + effect.setTargetPointer(new FixedTarget(player.getId())); + game.addEffect(effect, source); return true; } return false; diff --git a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java new file mode 100644 index 00000000000..be645f86abf --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java @@ -0,0 +1,143 @@ +/* + * 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 mage.abilities.effects.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.PhaseStep; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * @author okuRaku + */ +public class DontUntapInOpponentsNextUntapStepAllEffect extends ContinuousRuleModifyingEffectImpl { + + private int validForTurnNum; + //private String targetName; + TargetController targetController; + FilterPermanent filter; + + /** + * Attention: This effect won't work with targets controlled by different + * controllers If this is needed, the validForTurnNum has to be saved per + * controller. + */ + public DontUntapInOpponentsNextUntapStepAllEffect(FilterPermanent filter) { + super(Duration.Custom, Outcome.Detriment, false, true); + this.filter = filter; + } + + public DontUntapInOpponentsNextUntapStepAllEffect(final DontUntapInOpponentsNextUntapStepAllEffect effect) { + super(effect); + this.validForTurnNum = effect.validForTurnNum; + this.targetController = effect.targetController; + this.filter = effect.filter; + + } + + @Override + public DontUntapInOpponentsNextUntapStepAllEffect copy() { + return new DontUntapInOpponentsNextUntapStepAllEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public String getInfoMessage(Ability source, GameEvent event, Game game) { + MageObject mageObject = game.getObject(source.getSourceId()); + Permanent permanentToUntap = game.getPermanent((event.getTargetId())); + if (permanentToUntap != null && mageObject != null) { + return permanentToUntap.getLogName() + " doesn't untap (" + mageObject.getLogName() + ")"; + } + return null; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == EventType.UNTAP_STEP || event.getType() == EventType.UNTAP; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + // the check for turn number is needed if multiple effects are added to prevent untap in next untap step of controller + // if we don't check for turn number, every untap step of a turn only one effect would be used instead of correctly only one time + // to skip the untap effect. + + // Discard effect if it's related to a previous turn + if (validForTurnNum > 0 && validForTurnNum < game.getTurnNum()) { + discard(); + return false; + } + // remember the turn of the untap step the effect has to be applied + if (GameEvent.EventType.UNTAP_STEP.equals(event.getType())) { + if (game.getActivePlayerId().equals(getTargetPointer().getFirst(game, source))) { + if (validForTurnNum == game.getTurnNum()) { // the turn has a second untap step but the effect is already related to the first untap step + discard(); + return false; + } + validForTurnNum = game.getTurnNum(); + } + } + + if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getType() == EventType.UNTAP) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null && !game.isOpponent(controller, permanent.getControllerId())) { + return false; + } + if (game.getActivePlayerId().equals(permanent.getControllerId()) && // controller's untap step + filter.match(permanent, source.getSourceId(), source.getControllerId(), game)) { + return true; + } + } + } + return false; + } + + @Override + public String getText(Mode mode) { + if (!staticText.isEmpty()) { + return staticText; + } + return filter.getMessage() + " target opponent controls don't untap during his or her next untap step."; + } +} From 853456377a96cda21d984bdfc023bf1b9df4a41c Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 15 Mar 2016 11:53:23 +0100 Subject: [PATCH 16/44] Avacyn, the Purifier - Added missing colro for the night side card. --- .../mage/sets/shadowsoverinnistrad/AvacynThePurifier.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynThePurifier.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynThePurifier.java index 8fd42bde7a2..c09542d4732 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynThePurifier.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AvacynThePurifier.java @@ -42,8 +42,8 @@ import mage.constants.Zone; * @author fireshoes */ public class AvacynThePurifier extends CardImpl { - - private static final String rule = "Whenever this creature transforms into Avacyn, the Purifier, it deals 3 damage to each other creature and each opponent"; + + private static final String rule = "Whenever this creature transforms into {this}, it deals 3 damage to each other creature and each opponent"; public AvacynThePurifier(UUID ownerId) { super(ownerId, 5, "Avacyn, the Purifier", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, ""); @@ -52,6 +52,7 @@ public class AvacynThePurifier extends CardImpl { this.subtype.add("Angel"); this.power = new MageInt(6); this.toughness = new MageInt(5); + this.color.setRed(true); // this card is the second face of double-faced card this.nightCard = true; From 6c141a1f3ee7eb2799cb95ff065e637c6dd7e6fd Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 15 Mar 2016 11:54:11 +0100 Subject: [PATCH 17/44] Fixed color identity evaluation not checking second card face. --- .../commander/CommanderColorIdentityTest.java | 102 ++++++++++++++++++ .../src/main/java/mage/filter/FilterMana.java | 10 ++ Mage/src/main/java/mage/util/CardUtil.java | 39 ++++++- 3 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/CommanderColorIdentityTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/CommanderColorIdentityTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/CommanderColorIdentityTest.java new file mode 100644 index 00000000000..49c9aeb89c0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/CommanderColorIdentityTest.java @@ -0,0 +1,102 @@ +/* + * 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.commander; + +import mage.cards.Card; +import mage.cards.repository.CardInfo; +import mage.cards.repository.CardRepository; +import mage.filter.FilterMana; +import mage.util.CardUtil; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander3PlayersFFA; + +/** + * + * @author LevelX2 + */ +public class CommanderColorIdentityTest extends CardTestCommander3PlayersFFA { + + @Test + public void TestColors() { + // Basic Colors + Assert.assertEquals("{B}", getColorIdentityString("Sengir Vampire")); + Assert.assertEquals("{G}", getColorIdentityString("Elvish Visionary")); + Assert.assertEquals("{R}", getColorIdentityString("Demolish")); + Assert.assertEquals("{U}", getColorIdentityString("Negate")); + Assert.assertEquals("{W}", getColorIdentityString("Silvercoat Lion")); + + // Multicolor + Assert.assertEquals("{G}{W}", getColorIdentityString("Veteran Warleader")); + Assert.assertEquals("{B}{R}", getColorIdentityString("Forerunner of Slaughter")); + Assert.assertEquals("{R}{U}", getColorIdentityString("Brutal Expulsion")); + Assert.assertEquals("{B}{G}", getColorIdentityString("Catacomb Sifter")); + Assert.assertEquals("{B}{U}", getColorIdentityString("Fathom Feeder")); + + Assert.assertEquals("{R}{W}", getColorIdentityString("Angelic Captain")); + Assert.assertEquals("{G}{U}", getColorIdentityString("Bring to Light")); + Assert.assertEquals("{B}{W}", getColorIdentityString("Drana's Emissary")); + Assert.assertEquals("{G}{R}", getColorIdentityString("Grove Rumbler")); + Assert.assertEquals("{U}{W}", getColorIdentityString("Roil Spout")); + + // Cards with colors in the rule text + Assert.assertEquals("{B}{R}", getColorIdentityString("Fires of Undeath")); + + // Cards without casting costs + Assert.assertEquals("{B}", getColorIdentityString("Living End")); + Assert.assertEquals("{G}", getColorIdentityString("Hypergenesis")); + + // Phyrexian Mana + Assert.assertEquals("{B}", getColorIdentityString("Dismember")); + + // Hybrid mana + Assert.assertEquals("{G}{W}", getColorIdentityString("Kitchen Finks")); + + // Lands with colored activation costs + Assert.assertEquals("{G}", getColorIdentityString("Treetop Village")); + + // Cards with extort don't use extort to determine color identity + Assert.assertEquals("{W}", getColorIdentityString("Knight of Obligation")); + + // Two face cards + Assert.assertEquals("{G}{R}", getColorIdentityString("Daybreak Ranger")); + Assert.assertEquals("{R}{W}", getColorIdentityString("Archangel Avacyn")); + Assert.assertEquals("{R}{U}", getColorIdentityString("Civilized Scholar")); + + } + + private String getColorIdentityString(String cardName) { + CardInfo cardInfo = CardRepository.instance.findCard(cardName); + if (cardInfo == null) { + throw new IllegalArgumentException("Couldn't find the card " + cardName + " in the DB."); + } + Card card = cardInfo.getCard(); + FilterMana filterMana = CardUtil.getColorIdentity(card); + return filterMana.toString(); + } +} diff --git a/Mage/src/main/java/mage/filter/FilterMana.java b/Mage/src/main/java/mage/filter/FilterMana.java index 913f41ecabe..2e7542c5b85 100644 --- a/Mage/src/main/java/mage/filter/FilterMana.java +++ b/Mage/src/main/java/mage/filter/FilterMana.java @@ -114,4 +114,14 @@ public class FilterMana implements Serializable { public FilterMana copy() { return new FilterMana(this); } + + @Override + public String toString() { + return (black ? "{B}" : "") + + (green ? "{G}" : "") + + (red ? "{R}" : "") + + (blue ? "{U}" : "") + + (white ? "{W}" : ""); + } + } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index f72da7e32ee..0148c99eb7e 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -33,6 +33,7 @@ import java.util.Set; import java.util.UUID; import mage.MageObject; import mage.Mana; +import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; import mage.abilities.SpellAbility; @@ -623,22 +624,50 @@ public class CardUtil { for (String rule : card.getRules()) { rule = rule.replaceAll("(?i)", ""); // Ignoring reminder text in italic - if (rule.matches(regexBlack)) { + if (!mana.isBlack() && rule.matches(regexBlack)) { mana.setBlack(true); } - if (rule.matches(regexBlue)) { + if (!mana.isBlue() && rule.matches(regexBlue)) { mana.setBlue(true); } - if (rule.matches(regexGreen)) { + if (!mana.isGreen() && rule.matches(regexGreen)) { mana.setGreen(true); } - if (rule.matches(regexRed)) { + if (!mana.isRed() && rule.matches(regexRed)) { mana.setRed(true); } - if (rule.matches(regexWhite)) { + if (!mana.isWhite() && rule.matches(regexWhite)) { mana.setWhite(true); } } + if (card.canTransform()) { + Card secondCard = card.getSecondCardFace(); + ObjectColor color = secondCard.getColor(null); + mana.setBlack(mana.isBlack() || color.isBlack()); + mana.setGreen(mana.isGreen() || color.isGreen()); + mana.setRed(mana.isRed() || color.isRed()); + mana.setBlue(mana.isBlue() || color.isBlue()); + mana.setWhite(mana.isWhite() || color.isWhite()); + for (String rule : secondCard.getRules()) { + rule = rule.replaceAll("(?i)", ""); // Ignoring reminder text in italic + if (!mana.isBlack() && rule.matches(regexBlack)) { + mana.setBlack(true); + } + if (!mana.isBlue() && rule.matches(regexBlue)) { + mana.setBlue(true); + } + if (!mana.isGreen() && rule.matches(regexGreen)) { + mana.setGreen(true); + } + if (!mana.isRed() && rule.matches(regexRed)) { + mana.setRed(true); + } + if (!mana.isWhite() && rule.matches(regexWhite)) { + mana.setWhite(true); + } + } + } + return mana; } From 383b05534cf0f6942a98365531dd7b579d9eaa5d Mon Sep 17 00:00:00 2001 From: spjspj Date: Wed, 16 Mar 2016 01:05:24 +1100 Subject: [PATCH 18/44] spjspj - Change to '{this}'. Also add checks for if in graveyard of owner and whether it can return to the battlefield --- .../sets/darksteel/ScreamsFromWithin.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/sets/darksteel/ScreamsFromWithin.java b/Mage.Sets/src/mage/sets/darksteel/ScreamsFromWithin.java index 7533efcdbeb..bd126e45a82 100644 --- a/Mage.Sets/src/mage/sets/darksteel/ScreamsFromWithin.java +++ b/Mage.Sets/src/mage/sets/darksteel/ScreamsFromWithin.java @@ -42,8 +42,11 @@ import mage.cards.Card; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.Target; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; @@ -86,7 +89,7 @@ class ScreamsFromWithinEffect extends OneShotEffect { public ScreamsFromWithinEffect() { super(Outcome.PutCardInPlay); - staticText = "return Screams from Within from your graveyard to the battlefield"; + staticText = "return {this} from your graveyard to the battlefield"; } public ScreamsFromWithinEffect(final ScreamsFromWithinEffect effect) { @@ -95,10 +98,16 @@ class ScreamsFromWithinEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Card sourceEnchantmentCard = game.getCard(source.getSourceId()); + Card aura = game.getCard(source.getSourceId()); Player player = game.getPlayer(source.getControllerId()); - if (sourceEnchantmentCard != null && player != null) { - return player.moveCards(sourceEnchantmentCard, Zone.BATTLEFIELD, source, game); + if (aura != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD && player != null && player.getGraveyard().contains(source.getSourceId())) { + for (Permanent creaturePermanent : game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), game)) { + for (Target target : aura.getSpellAbility().getTargets()) { + if (target.canTarget(creaturePermanent.getId(), game)) { + return player.moveCards(aura, Zone.BATTLEFIELD, source, game); + } + } + } } return false; } @@ -107,4 +116,4 @@ class ScreamsFromWithinEffect extends OneShotEffect { public ScreamsFromWithinEffect copy() { return new ScreamsFromWithinEffect(this); } -} \ No newline at end of file +} From 4c8ca577bd9e016cd5d1b2c40a26aaecddafd86a Mon Sep 17 00:00:00 2001 From: rkfg Date: Tue, 15 Mar 2016 18:37:32 +0300 Subject: [PATCH 19/44] Fix transformable card being shown non-transformed on the battlefield. --- Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index 8bf5bd192cc..b310a074f37 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -880,7 +880,7 @@ public class CardPanel extends MagePermanent implements MouseListener, MouseMoti updateImage(); if (card.canTransform()) { BufferedImage transformIcon; - if (transformed) { + if (transformed || card.isTransformed()) { transformIcon = ImageManagerImpl.getInstance().getNightImage(); } else { transformIcon = ImageManagerImpl.getInstance().getDayImage(); From 8834965979634b7bc11f81449f9096dba5dbd09a Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 15 Mar 2016 17:18:59 +0100 Subject: [PATCH 20/44] [SOI] Flameblade Angel and Relentless Dead. --- .../shadowsoverinnistrad/FlamebladeAngel.java | 123 +++++++++++++++++ .../shadowsoverinnistrad/RelentlessDead.java | 129 ++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/FlamebladeAngel.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/RelentlessDead.java diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/FlamebladeAngel.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/FlamebladeAngel.java new file mode 100644 index 00000000000..7c03974f59c --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/FlamebladeAngel.java @@ -0,0 +1,123 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.DamageEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author LevelX2 + */ +public class FlamebladeAngel extends CardImpl { + + public FlamebladeAngel(UUID ownerId) { + super(ownerId, 157, "Flameblade Angel", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{4}{R}{R}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Angel"); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + // Whenever a source an opponent controls deals damage to you or a permanent you control, you may have Flameblade Angel deal 1 damage to that source's controller. + this.addAbility(new FlamebladeAngelTriggeredAbility()); + + } + + public FlamebladeAngel(final FlamebladeAngel card) { + super(card); + } + + @Override + public FlamebladeAngel copy() { + return new FlamebladeAngel(this); + } +} + +class FlamebladeAngelTriggeredAbility extends TriggeredAbilityImpl { + + public FlamebladeAngelTriggeredAbility() { + super(Zone.BATTLEFIELD, new DamageTargetEffect(1), true); + } + + public FlamebladeAngelTriggeredAbility(final FlamebladeAngelTriggeredAbility ability) { + super(ability); + } + + @java.lang.Override + public FlamebladeAngelTriggeredAbility copy() { + return new FlamebladeAngelTriggeredAbility(this); + } + + @java.lang.Override + public boolean checkEventType(GameEvent event, Game game) { + return event instanceof DamageEvent; + } + + @java.lang.Override + public boolean checkTrigger(GameEvent event, Game game) { + boolean result = false; + UUID sourceControllerId = game.getControllerId(event.getSourceId()); + if (sourceControllerId != null && game.getOpponents(getControllerId()).contains(sourceControllerId)) { + + if (event.getTargetId().equals(getControllerId())) { + result = true; + } else { + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent != null && getControllerId().equals(permanent.getControllerId())) { + result = true; + } + } + if (result) { + for (Effect effect : getEffects()) { + effect.setTargetPointer(new FixedTarget(sourceControllerId)); + } + } + } + return result; + } + + @java.lang.Override + public String getRule() { + return "Whenever a source an opponent controls deals damage to you or a permanent you control, you may have {this} deal 1 damage to that source's controller."; + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/RelentlessDead.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/RelentlessDead.java new file mode 100644 index 00000000000..f768d83ec15 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/RelentlessDead.java @@ -0,0 +1,129 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DiesTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ReturnToHandSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.Filter; +import mage.filter.FilterCard; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author LevelX2 + */ +public class RelentlessDead extends CardImpl { + + public RelentlessDead(UUID ownerId) { + super(ownerId, 131, "Relentless Dead", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, "{B}{B}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Zombie"); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Menace + this.addAbility(new MenaceAbility()); + + // When Relentless Dead dies, you may pay {B}. If you do, return it to its owner's hand. + this.addAbility(new DiesTriggeredAbility(new DoIfCostPaid(new ReturnToHandSourceEffect(), new ManaCostsImpl("{B}")))); + + // When Relentless Dead dies, you may pay {X}. If you do, return another target Zombie creature card with converted mana cost X from your graveyard to the battlefield. + this.addAbility(new DiesTriggeredAbility(new RelentlessDeadEffect())); + } + + public RelentlessDead(final RelentlessDead card) { + super(card); + } + + @Override + public RelentlessDead copy() { + return new RelentlessDead(this); + } +} + +class RelentlessDeadEffect extends OneShotEffect { + + public RelentlessDeadEffect() { + super(Outcome.PutCardInPlay); + this.staticText = "you may pay {X}. If you do, return another target Zombie creature card with converted mana cost X from your graveyard to the battlefield"; + } + + public RelentlessDeadEffect(final RelentlessDeadEffect effect) { + super(effect); + } + + @Override + public RelentlessDeadEffect copy() { + return new RelentlessDeadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + if (controller.chooseUse(Outcome.BoostCreature, "Do you want to to pay {X}?", source, game)) { + int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); + Cost cost = new GenericManaCost(costX); + if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) { + FilterCard filter = new FilterCard("Zombie card with converted mana cost " + costX); + filter.add(new SubtypePredicate("Zombie")); + filter.add(new ConvertedManaCostPredicate(Filter.ComparisonType.Equal, costX)); + TargetCardInYourGraveyard target = new TargetCardInYourGraveyard(filter); + if (controller.chooseTarget(outcome, target, source, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + controller.moveCards(card, Zone.BATTLEFIELD, source, game); + } + } + } + } + return true; + } + return false; + + } +} From dc37ff079318a13bf9d2a81122e2129f7f5febb6 Mon Sep 17 00:00:00 2001 From: drmDev Date: Tue, 15 Mar 2016 13:32:46 -0400 Subject: [PATCH 21/44] Card implementation Clergy of the Holy Nimbus --- .../sets/legends/ClergyOfTheHolyNimbus.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/legends/ClergyOfTheHolyNimbus.java diff --git a/Mage.Sets/src/mage/sets/legends/ClergyOfTheHolyNimbus.java b/Mage.Sets/src/mage/sets/legends/ClergyOfTheHolyNimbus.java new file mode 100644 index 00000000000..dfdc854f41d --- /dev/null +++ b/Mage.Sets/src/mage/sets/legends/ClergyOfTheHolyNimbus.java @@ -0,0 +1,119 @@ +/* + * 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 mage.sets.legends; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateOnlyByOpponentActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.CantBeRegeneratedSourceEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class ClergyOfTheHolyNimbus extends CardImpl { + + public ClergyOfTheHolyNimbus(UUID ownerId) { + super(ownerId, 175, "Clergy of the Holy Nimbus", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{W}"); + this.expansionSetCode = "LEG"; + this.subtype.add("Human"); + this.subtype.add("Cleric"); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // If Clergy of the Holy Nimbus would be destroyed, regenerate it. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new ClergyOfTheHolyNimbusReplacementEffect())); + + // {1}: Clergy of the Holy Nimbus can't be regenerated this turn. Only any opponent may activate this ability. + this.addAbility(new ActivateOnlyByOpponentActivatedAbility(Zone.BATTLEFIELD, new CantBeRegeneratedSourceEffect(Duration.EndOfTurn), new ManaCostsImpl("{1}"))); + } + + public ClergyOfTheHolyNimbus(final ClergyOfTheHolyNimbus card) { + super(card); + } + + @Override + public ClergyOfTheHolyNimbus copy() { + return new ClergyOfTheHolyNimbus(this); + } +} + +class ClergyOfTheHolyNimbusReplacementEffect extends ReplacementEffectImpl { + + ClergyOfTheHolyNimbusReplacementEffect() { + super(Duration.Custom, Outcome.Regenerate); + staticText = "If {this} would be destroyed, regenerate it"; + } + + ClergyOfTheHolyNimbusReplacementEffect(ClergyOfTheHolyNimbusReplacementEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent ClergyOfTheHolyNimbus = game.getPermanent(event.getTargetId()); + if (ClergyOfTheHolyNimbus != null + && event.getAmount() == 0) { // 1=noRegen + if (ClergyOfTheHolyNimbus.regenerate(source.getSourceId(), game)) { + game.informPlayers(source.getSourceObject(game).getName() + " has been regenerated."); + return true; + } + } + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DESTROY_PERMANENT; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return event.getTargetId() != null + && event.getTargetId().equals(source.getSourceId()); + } + + @Override + public ClergyOfTheHolyNimbusReplacementEffect copy() { + return new ClergyOfTheHolyNimbusReplacementEffect(this); + } + +} \ No newline at end of file From 0a38430d497e95d968dc170f7ca34d46f4dbe4b0 Mon Sep 17 00:00:00 2001 From: fireshoes Date: Tue, 15 Mar 2016 17:47:21 -0500 Subject: [PATCH 22/44] [SOI] Added 3/14-15 spoilers to mtg-cards-data.txt Added Angel of Deliverance, Anguished Unmaking, Descend upon the Sinful, Drogskol Cavalry, Nephalia Moondrakes, and Sinister Concoction. --- .../java/org/mage/card/arcane/CardPanel.java | 2536 ++++++++--------- .../betrayersofkamigawa/FinalJudgment.java | 157 +- .../mage/sets/gameday/AnguishedUnmaking.java | 52 + .../AngelOfDeliverance.java | 126 + .../AnguishedUnmaking.java | 62 + .../DescendUponTheSinful.java | 69 + .../shadowsoverinnistrad/DrogskolCavalry.java | 86 + .../NephaliaMoondrakes.java | 85 + .../shadowsoverinnistrad/PiousEvangel.java | 3 - .../SinisterConcoction.java | 73 + .../mage/game/permanent/token/AngelToken.java | 68 +- .../permanent/token/SpiritWhiteToken.java | 176 +- Utils/mtg-cards-data.txt | 22 +- 13 files changed, 2019 insertions(+), 1496 deletions(-) create mode 100644 Mage.Sets/src/mage/sets/gameday/AnguishedUnmaking.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/AngelOfDeliverance.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/AnguishedUnmaking.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/DescendUponTheSinful.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrogskolCavalry.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/NephaliaMoondrakes.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/SinisterConcoction.java diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index 8bf5bd192cc..480249f9bf6 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -1,1268 +1,1268 @@ -package org.mage.card.arcane; - -import java.awt.AlphaComposite; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.StringTokenizer; -import java.util.UUID; -import javax.swing.BorderFactory; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import mage.cards.MagePermanent; -import mage.cards.TextPopup; -import mage.cards.action.ActionCallback; -import mage.cards.action.TransferData; -import mage.client.dialog.PreferencesDialog; -import mage.client.plugins.adapters.MageActionCallback; -import mage.client.plugins.impl.Plugins; -import mage.client.util.ImageHelper; -import mage.client.util.audio.AudioManager; -import mage.components.ImagePanel; -import mage.constants.AbilityType; -import mage.constants.CardType; -import mage.constants.EnlargeMode; -import mage.utils.CardUtil; -import mage.view.AbilityView; -import mage.view.CardView; -import mage.view.CounterView; -import mage.view.PermanentView; -import mage.view.StackAbilityView; -import net.java.truevfs.access.TFile; -import org.apache.log4j.Logger; -import org.mage.card.arcane.ScaledImagePanel.MultipassType; -import org.mage.card.arcane.ScaledImagePanel.ScalingType; -import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL; -import org.mage.plugins.card.dl.sources.DirectLinksForDownload; -import org.mage.plugins.card.images.ImageCache; -import org.mage.plugins.card.utils.impl.ImageManagerImpl; - -/** - * Main class for drawing Mage card object. - * - * @author arcane, nantuko, noxx - */ -@SuppressWarnings({"unchecked", "rawtypes"}) -public class CardPanel extends MagePermanent implements MouseListener, MouseMotionListener, MouseWheelListener, ComponentListener { - - private static final long serialVersionUID = -3272134219262184410L; - - private static final Logger LOGGER = Logger.getLogger(CardPanel.class); - - private static final int WIDTH_LIMIT = 90; // card width limit to create smaller counter - public static final double TAPPED_ANGLE = Math.PI / 2; - public static final double FLIPPED_ANGLE = Math.PI; - public static final float ASPECT_RATIO = 3.5f / 2.5f; - public static final int POPUP_X_GAP = 1; // prevent tooltip window from blinking - - public static CardPanel dragAnimationPanel; - - public static final Rectangle CARD_SIZE_FULL = new Rectangle(101, 149); - - private static final float ROUNDED_CORNER_SIZE = 0.1f; - private static final float BLACK_BORDER_SIZE = 0.03f; - private static final int TEXT_GLOW_SIZE = 6; - private static final float TEXT_GLOW_INTENSITY = 3f; - private static final float ROT_CENTER_TO_TOP_CORNER = 1.0295630140987000315797369464196f; - private static final float ROT_CENTER_TO_BOTTOM_CORNER = 0.7071067811865475244008443621048f; - - public CardView gameCard; - public CardView updateCard; - - // for two faced cards - public CardView temporary; - private List links = new ArrayList<>(); - - public double tappedAngle = 0; - public double flippedAngle = 0; - public final ScaledImagePanel imagePanel; - public ImagePanel overlayPanel; - - public JPanel buttonPanel; - private JButton dayNightButton; - - public JPanel copyIconPanel; - private JButton showCopySourceButton; - - public JPanel iconPanel; - private JButton typeButton; - - public JPanel counterPanel; - private JLabel loyaltyCounterLabel; - private JLabel plusCounterLabel; - private JLabel otherCounterLabel; - private JLabel minusCounterLabel; - private int loyaltyCounter; - private int plusCounter; - private int otherCounter; - private int minusCounter; - private int lastCardWidth; - - private GlowText titleText; - private GlowText ptText; - private boolean displayEnabled = true; - private boolean isAnimationPanel; - public int cardXOffset, cardYOffset, cardWidth, cardHeight; - private int symbolWidth; - - private boolean isSelected; - private boolean isPlayable; - private boolean isChoosable; - private boolean canAttack; - private boolean showCastingCost; - private boolean hasImage = false; - private float alpha = 1.0f; - - private ActionCallback callback; - - protected boolean tooltipShowing; - protected TextPopup tooltipText = new TextPopup(); - protected UUID gameId; - private TransferData data = new TransferData(); - - private boolean isPermanent; - private boolean hasSickness; - private String zone; - - public double transformAngle = 1; - - private boolean transformed; - private boolean animationInProgress = false; - - private boolean displayTitleAnyway; - - private JPanel cardArea; - - private int yTextOffset = 10; - - // if this is set, it's opened if the user right clicks on the card panel - private JPopupMenu popupMenu; - - public CardPanel(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension) { - this.gameCard = newGameCard; - this.callback = callback; - this.gameId = gameId; - - this.isPermanent = this.gameCard instanceof PermanentView; - if (isPermanent) { - this.hasSickness = ((PermanentView) this.gameCard).hasSummoningSickness(); - } - - this.setCardBounds(0, 0, dimension.width, dimension.height); - - //for container debug (don't remove) - //setBorder(BorderFactory.createLineBorder(Color.green)); - if (this.gameCard.canTransform()) { - buttonPanel = new JPanel(); - buttonPanel.setLayout(null); - buttonPanel.setOpaque(false); - add(buttonPanel); - - dayNightButton = new JButton(""); - dayNightButton.setLocation(2, 2); - dayNightButton.setSize(25, 25); - - buttonPanel.setVisible(true); - - BufferedImage day = ImageManagerImpl.getInstance().getDayImage(); - dayNightButton.setIcon(new ImageIcon(day)); - - buttonPanel.add(dayNightButton); - - dayNightButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - // if card is being rotated, ignore action performed - // if card is tapped, no visual transforming is possible (implementation limitation) - // if card is permanent, it will be rotated by Mage, so manual rotate should be possible - if (animationInProgress || isTapped() || isPermanent) { - return; - } - Animation.transformCard(CardPanel.this, CardPanel.this, true); - } - }); - } - if (!newGameCard.isAbility()) { - // panel to show counters on the card - counterPanel = new JPanel(); - counterPanel.setLayout(null); - counterPanel.setOpaque(false); - add(counterPanel); - - plusCounterLabel = new JLabel(""); - plusCounterLabel.setToolTipText("+1/+1"); - counterPanel.add(plusCounterLabel); - - minusCounterLabel = new JLabel(""); - minusCounterLabel.setToolTipText("-1/-1"); - counterPanel.add(minusCounterLabel); - - loyaltyCounterLabel = new JLabel(""); - loyaltyCounterLabel.setToolTipText("loyalty"); - counterPanel.add(loyaltyCounterLabel); - - otherCounterLabel = new JLabel(""); - counterPanel.add(otherCounterLabel); - - counterPanel.setVisible(false); - } - if (newGameCard.isAbility()) { - if (AbilityType.TRIGGERED.equals(newGameCard.getAbilityType())) { - setTypeIcon(ImageManagerImpl.getInstance().getTriggeredAbilityImage(), "Triggered Ability"); - } else if (AbilityType.ACTIVATED.equals(newGameCard.getAbilityType())) { - setTypeIcon(ImageManagerImpl.getInstance().getActivatedAbilityImage(), "Activated Ability"); - } - } - - if (this.gameCard.isToken()) { - setTypeIcon(ImageManagerImpl.getInstance().getTokenIconImage(), "Token Permanent"); - } - - // icon to inform about permanent is copying something - if (this.gameCard instanceof PermanentView) { - copyIconPanel = new JPanel(); - copyIconPanel.setLayout(null); - copyIconPanel.setOpaque(false); - add(copyIconPanel); - - showCopySourceButton = new JButton(""); - showCopySourceButton.setLocation(2, 2); - showCopySourceButton.setSize(25, 25); - showCopySourceButton.setToolTipText("This permanent is copying a target. To see original image, push this button or turn mouse wheel down while hovering with the mouse pointer over the permanent."); - copyIconPanel.setVisible(((PermanentView) this.gameCard).isCopy()); - - showCopySourceButton.setIcon(new ImageIcon(ImageManagerImpl.getInstance().getCopyInformIconImage())); - - copyIconPanel.add(showCopySourceButton); - - showCopySourceButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ActionCallback callback = Plugins.getInstance().getActionCallback(); - ((MageActionCallback) callback).enlargeCard(EnlargeMode.COPY); - } - }); - } - - setBackground(Color.black); - setOpaque(false); - - addMouseListener(this); - addMouseMotionListener(this); - addMouseWheelListener(this); - addComponentListener(this); - - displayTitleAnyway = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_CARD_NAMES, "true").equals("true"); - - titleText = new GlowText(); - setText(gameCard); -// int fontSize = (int) cardHeight / 11; -// titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - titleText.setForeground(Color.white); - titleText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); - titleText.setWrap(true); - add(titleText); - - ptText = new GlowText(); - if (CardUtil.isCreature(gameCard)) { - ptText.setText(gameCard.getPower() + "/" + gameCard.getToughness()); - } else if (CardUtil.isPlaneswalker(gameCard)) { - ptText.setText(gameCard.getLoyalty()); - } -// ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - ptText.setForeground(Color.white); - ptText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); - add(ptText); - - BufferedImage sickness = ImageManagerImpl.getInstance().getSicknessImage(); - overlayPanel = new ImagePanel(sickness, ImagePanel.SCALED); - overlayPanel.setOpaque(false); - add(overlayPanel); - - imagePanel = new ScaledImagePanel(); - imagePanel.setBorder(BorderFactory.createLineBorder(Color.white)); - add(imagePanel); - imagePanel.setScaleLarger(true); - imagePanel.setScalingType(ScalingType.nearestNeighbor); - imagePanel.setScalingMultiPassType(MultipassType.none); - - String cardType = getType(newGameCard); - tooltipText.setText(getText(cardType, newGameCard)); - - Util.threadPool.submit(new Runnable() { - @Override - public void run() { - try { - tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; - flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; - if (!loadImage) { - return; - } - BufferedImage srcImage; - if (gameCard.isFaceDown()) { - srcImage = getFaceDownImage(); - } else { - srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); - } - if (srcImage != null) { - hasImage = true; - setText(gameCard); - setImage(srcImage); - } - if (gameCard.isTransformed()) { - toggleTransformed(); - } - setText(gameCard); - } catch (Exception e) { - LOGGER.fatal("Problem during image animation", e); - } catch (Error err) { - LOGGER.error("Problem during image animation", err); - } - } - }); - } - - private void setTypeIcon(BufferedImage bufferedImage, String toolTipText) { - iconPanel = new JPanel(); - iconPanel.setLayout(null); - iconPanel.setOpaque(false); - add(iconPanel); - - typeButton = new JButton(""); - typeButton.setLocation(2, 2); - typeButton.setSize(25, 25); - - iconPanel.setVisible(true); - typeButton.setIcon(new ImageIcon(bufferedImage)); - if (toolTipText != null) { - typeButton.setToolTipText(toolTipText); - } - iconPanel.add(typeButton); - } - - public void cleanUp() { - if (dayNightButton != null) { - for (ActionListener al : dayNightButton.getActionListeners()) { - dayNightButton.removeActionListener(al); - } - } - for (MouseListener ml : this.getMouseListeners()) { - this.removeMouseListener(ml); - } - for (MouseMotionListener ml : this.getMouseMotionListeners()) { - this.removeMouseMotionListener(ml); - } - for (MouseWheelListener ml : this.getMouseWheelListeners()) { - this.removeMouseWheelListener(ml); - } - // this holds reference to ActionCallback forever so set it to null to prevent - this.callback = null; - this.data = null; - this.counterPanel = null; - } - - private void setText(CardView card) { - titleText.setText(!displayTitleAnyway && hasImage ? "" : card.getName()); - } - - private void setImage(Image srcImage) { - synchronized (imagePanel) { - imagePanel.setImage(srcImage); - repaint(); - } - doLayout(); - } - - public void setImage(final CardPanel panel) { - synchronized (panel.imagePanel) { - if (panel.imagePanel.hasImage()) { - setImage(panel.imagePanel.getSrcImage()); - } - } - } - - @Override - public void setZone(String zone) { - this.zone = zone; - } - - @Override - public String getZone() { - return zone; - } - - public void setScalingType(ScalingType scalingType) { - imagePanel.setScalingType(scalingType); - } - - public void setDisplayEnabled(boolean displayEnabled) { - this.displayEnabled = displayEnabled; - } - - public boolean isDisplayEnabled() { - return displayEnabled; - } - - public void setAnimationPanel(boolean isAnimationPanel) { - this.isAnimationPanel = isAnimationPanel; - } - - @Override - public void setSelected(boolean isSelected) { - this.isSelected = isSelected; - if (isSelected) { - this.titleText.setGlowColor(Color.green); - } else { - this.titleText.setGlowColor(Color.black); - } - // noxx: bad idea is to call repaint in setter method - ////repaint(); - } - - @Override - public void setChoosable(boolean isChoosable) { - this.isChoosable = isChoosable; - } - - @Override - public void setCardAreaRef(JPanel cardArea) { - this.cardArea = cardArea; - } - - public boolean getSelected() { - return this.isSelected; - } - - public void setShowCastingCost(boolean showCastingCost) { - this.showCastingCost = showCastingCost; - } - - @Override - public void paint(Graphics g) { - if (!displayEnabled) { - return; - } - if (!isValid()) { - super.validate(); - } - Graphics2D g2d = (Graphics2D) g; - if (transformAngle < 1) { - float edgeOffset = (cardWidth + cardXOffset) / 2f; - g2d.translate(edgeOffset * (1 - transformAngle), 0); - g2d.scale(transformAngle, 1); - } - if (tappedAngle + flippedAngle > 0) { - g2d = (Graphics2D) g2d.create(); - float edgeOffset = cardWidth / 2f; - double angle = tappedAngle + (Math.abs(flippedAngle - FLIPPED_ANGLE) < 0.001 ? 0 : flippedAngle); - g2d.rotate(angle, cardXOffset + edgeOffset, cardYOffset + cardHeight - edgeOffset); - } - super.paint(g2d); - } - - @Override - protected void paintComponent(Graphics g) { - Graphics2D g2d = (Graphics2D) g; - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - if (alpha != 1.0f) { - AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha); - g2d.setComposite(composite); - } - - if (!hasImage) { - g2d.setColor(new Color(30, 200, 200, 120)); - } else { - g2d.setColor(new Color(0, 0, 0, 0)); - } - - int cornerSize = Math.max(4, Math.round(cardWidth * ROUNDED_CORNER_SIZE)); - g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize); - - if (isSelected) { - g2d.setColor(Color.green); - g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); - } else if (isChoosable) { - g2d.setColor(new Color(250, 250, 0, 230)); - g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); - } else if (isPlayable) { - g2d.setColor(new Color(153, 102, 204, 200)); - //g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); - g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize); - } - - if (canAttack) { - g2d.setColor(new Color(0, 0, 255, 230)); - g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); - } - - //TODO:uncomment - /* - if (gameCard.isAttacking()) { - g2d.setColor(new Color(200,10,10,200)); - g2d.fillRoundRect(cardXOffset+1, cardYOffset+1, cardWidth-2, cardHeight-2, cornerSize, cornerSize); - }*/ - } - - @Override - protected void paintChildren(Graphics g) { - super.paintChildren(g); - - if (showCastingCost && !isAnimationPanel && cardWidth < 200 && cardWidth > 60) { - String manaCost = ManaSymbols.getStringManaCost(gameCard.getManaCost()); - int width = getWidth(manaCost); - if (hasImage) { - ManaSymbols.draw(g, manaCost, cardXOffset + cardWidth - width - 5, cardYOffset + 5, symbolWidth); - } else { - ManaSymbols.draw(g, manaCost, cardXOffset + 8, cardHeight - 9, symbolWidth); - } - } - } - - private int getWidth(String manaCost) { - int width = 0; - manaCost = manaCost.replace("\\", ""); - StringTokenizer tok = new StringTokenizer(manaCost, " "); - while (tok.hasMoreTokens()) { - tok.nextToken(); - width += symbolWidth; - } - return width; - } - - @Override - public void doLayout() { - int borderSize = Math.round(cardWidth * BLACK_BORDER_SIZE); - imagePanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - imagePanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - - if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) { - overlayPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - overlayPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - } else { - overlayPanel.setVisible(false); - } - - if (buttonPanel != null) { - buttonPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - buttonPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - dayNightButton.setLocation(0, cardHeight - 30); - } - if (iconPanel != null) { - iconPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - iconPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - } - if (copyIconPanel != null) { - copyIconPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - copyIconPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - } - if (counterPanel != null) { - counterPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); - counterPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); - int size = cardWidth > WIDTH_LIMIT ? 40 : 20; - - minusCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size * 2); - minusCounterLabel.setSize(size, size); - - plusCounterLabel.setLocation(5, counterPanel.getHeight() - size * 2); - plusCounterLabel.setSize(size, size); - - loyaltyCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size); - loyaltyCounterLabel.setSize(size, size); - - otherCounterLabel.setLocation(5, counterPanel.getHeight() - size); - otherCounterLabel.setSize(size, size); - - } - int fontHeight = Math.round(cardHeight * (27f / 680)); - boolean showText = (!isAnimationPanel && fontHeight < 12); - titleText.setVisible(showText); - ptText.setVisible(showText); - - if (showText) { - int fontSize = (int) cardHeight / 11; - titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - - int titleX = Math.round(cardWidth * (20f / 480)); - int titleY = Math.round(cardHeight * (9f / 680)) + yTextOffset; - titleText.setBounds(cardXOffset + titleX, cardYOffset + titleY, cardWidth - titleX, cardHeight - titleY); - - ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); - Dimension ptSize = ptText.getPreferredSize(); - ptText.setSize(ptSize.width, ptSize.height); - int ptX = Math.round(cardWidth * (420f / 480)) - ptSize.width / 2; - int ptY = Math.round(cardHeight * (675f / 680)) - ptSize.height; - - int offsetX = Math.round((CARD_SIZE_FULL.width - cardWidth) / 10.0f); - - ptText.setLocation(cardXOffset + ptX - TEXT_GLOW_SIZE / 2 - offsetX, cardYOffset + ptY - TEXT_GLOW_SIZE / 2); - } - - if (isAnimationPanel || cardWidth < 200) { - imagePanel.setScalingType(ScalingType.nearestNeighbor); - } else { - imagePanel.setScalingType(ScalingType.bilinear); - } - } - - @Override - public String toString() { - return gameCard.toString(); - } - - @Override - public final void setCardBounds(int x, int y, int cardWidth, int cardHeight) { - this.cardWidth = cardWidth; - this.symbolWidth = cardWidth / 7; - this.cardHeight = cardHeight; - if (this.isPermanent) { - int rotCenterX = Math.round(cardWidth / 2f); - int rotCenterY = cardHeight - rotCenterX; - int rotCenterToTopCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_TOP_CORNER); - int rotCenterToBottomCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_BOTTOM_CORNER); - int xOffset = getXOffset(cardWidth); - int yOffset = getYOffset(cardWidth, cardHeight); - cardXOffset = -xOffset; - cardYOffset = -yOffset; - int width = -xOffset + rotCenterX + rotCenterToTopCorner; - int height = -yOffset + rotCenterY + rotCenterToBottomCorner; - setBounds(x + xOffset, y + yOffset, width, height); - } else { - cardXOffset = 5; - cardYOffset = 5; - int width = cardXOffset * 2 + cardWidth; - int height = cardYOffset * 2 + cardHeight; - setBounds(x - cardXOffset, y - cardYOffset, width, height); - } - } - - public int getXOffset(int cardWidth) { - if (this.isPermanent) { - int rotCenterX = Math.round(cardWidth / 2f); - int rotCenterToBottomCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_BOTTOM_CORNER); - int xOffset = rotCenterX - rotCenterToBottomCorner; - return xOffset; - } else { - return cardXOffset; - } - } - - public int getYOffset(int cardWidth, int cardHeight) { - if (this.isPermanent) { - int rotCenterX = Math.round(cardWidth / 2f); - int rotCenterY = cardHeight - rotCenterX; - int rotCenterToTopCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_TOP_CORNER); - int yOffset = rotCenterY - rotCenterToTopCorner; - return yOffset; - } else { - return cardYOffset; - } - - } - - public int getCardX() { - return getX() + cardXOffset; - } - - public int getCardY() { - return getY() + cardYOffset; - } - - public int getCardWidth() { - return cardWidth; - } - - public int getCardHeight() { - return cardHeight; - } - - public Point getCardLocation() { - Point p = getLocation(); - p.x += cardXOffset; - p.y += cardYOffset; - return p; - } - - public CardView getCard() { - return this.gameCard; - } - - @Override - public void setAlpha(float alpha) { - this.alpha = alpha; - if (alpha == 0) { - this.ptText.setVisible(false); - this.titleText.setVisible(false); - } else if (alpha == 1.0f) { - this.ptText.setVisible(true); - this.titleText.setVisible(true); - } - } - - @Override - public float getAlpha() { - return alpha; - } - - public int getCardXOffset() { - return cardXOffset; - } - - public int getCardYOffset() { - return cardYOffset; - } - - @Override - public void updateImage() { - Util.threadPool.submit(new Runnable() { - @Override - public void run() { - try { - tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; - flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; - BufferedImage srcImage; - if (gameCard.isFaceDown()) { - srcImage = getFaceDownImage(); - } else if (cardWidth > THUMBNAIL_SIZE_FULL.width) { - srcImage = ImageCache.getImage(gameCard, cardWidth, cardHeight); - } else { - srcImage = ImageCache.getThumbnail(gameCard); - } - if (srcImage != null) { - hasImage = true; - setText(gameCard); - setImage(srcImage); - } - } catch (Exception e) { - e.printStackTrace(); - } catch (Error err) { - err.printStackTrace(); - } - } - }); - } - - private BufferedImage getFaceDownImage() { - if (isPermanent) { - if (((PermanentView) gameCard).isMorphed()) { - return ImageCache.getMorphImage(); - } else { - return ImageCache.getManifestImage(); - } - } else if (this.gameCard instanceof StackAbilityView) { - return ImageCache.getMorphImage(); - } else { - return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename)); - } - } - - @Override - public List getLinks() { - return links; - } - - @Override - public boolean isTapped() { - if (isPermanent) { - return ((PermanentView) gameCard).isTapped(); - } - return false; - } - - @Override - public boolean isFlipped() { - if (isPermanent) { - return ((PermanentView) gameCard).isFlipped(); - } - return false; - } - - @Override - public boolean isTransformed() { - if (isPermanent) { - return gameCard.isTransformed(); - } - return false; - } - - @Override - public void showCardTitle() { - displayTitleAnyway = true; - setText(gameCard); - } - - @Override - public void onBeginAnimation() { - animationInProgress = true; - } - - @Override - public void onEndAnimation() { - animationInProgress = false; - } - - @Override - public void update(CardView card) { - this.updateCard = card; - if (isPermanent && (card instanceof PermanentView)) { - boolean needsTapping = isTapped() != ((PermanentView) card).isTapped(); - boolean needsFlipping = isFlipped() != ((PermanentView) card).isFlipped(); - if (needsTapping || needsFlipping) { - Animation.tapCardToggle(this, this, needsTapping, needsFlipping); - } - if (needsTapping && ((PermanentView) card).isTapped()) { - AudioManager.playTapPermanent(); - } - boolean needsTranforming = isTransformed() != card.isTransformed(); - if (needsTranforming) { - Animation.transformCard(this, this, card.isTransformed()); - } - } - if (card.canTransform()) { - dayNightButton.setVisible(!isPermanent); - } - - if (CardUtil.isCreature(card) && CardUtil.isPlaneswalker(card)) { - ptText.setText(card.getPower() + "/" + card.getToughness() + " (" + card.getLoyalty() + ")"); - } else if (CardUtil.isCreature(card)) { - ptText.setText(card.getPower() + "/" + card.getToughness()); - } else if (CardUtil.isPlaneswalker(card)) { - ptText.setText(card.getLoyalty()); - } else { - ptText.setText(""); - } - setText(card); - - this.isPlayable = card.isPlayable(); - this.isChoosable = card.isChoosable(); - this.canAttack = card.isCanAttack(); - this.isSelected = card.isSelected(); - - boolean updateImage = !gameCard.getName().equals(card.getName()) || gameCard.isFaceDown() != card.isFaceDown(); // update after e.g. turning a night/day card - if (updateImage && gameCard.canTransform() && card.canTransform() && transformed) { - if (card.getSecondCardFace() != null && card.getSecondCardFace().getName().equals(gameCard.getName())) { - transformed = false; - } - } - this.gameCard = card; - - String cardType = getType(card); - tooltipText.setText(getText(cardType, card)); - - if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) { - overlayPanel.setVisible(true); - } else { - overlayPanel.setVisible(false); - } - if (updateImage) { - updateImage(); - if (card.canTransform()) { - BufferedImage transformIcon; - if (transformed) { - transformIcon = ImageManagerImpl.getInstance().getNightImage(); - } else { - transformIcon = ImageManagerImpl.getInstance().getDayImage(); - } - dayNightButton.setIcon(new ImageIcon(transformIcon)); - } - } - - if (counterPanel != null) { - updateCounters(card); - } - - repaint(); - } - - private void updateCounters(CardView card) { - if (card.getCounters() != null && !card.getCounters().isEmpty()) { - String name = ""; - if (lastCardWidth != cardWidth) { - lastCardWidth = cardWidth; - plusCounter = 0; - minusCounter = 0; - otherCounter = 0; - loyaltyCounter = 0; - } - plusCounterLabel.setVisible(false); - minusCounterLabel.setVisible(false); - loyaltyCounterLabel.setVisible(false); - otherCounterLabel.setVisible(false); - for (CounterView counterView : card.getCounters()) { - if (counterView.getCount() == 0) { - continue; - } - switch (counterView.getName()) { - case "+1/+1": - if (counterView.getCount() != plusCounter) { - plusCounter = counterView.getCount(); - plusCounterLabel.setIcon(getCounterImageWithAmount(plusCounter, ImageManagerImpl.getInstance().getCounterImageGreen(), cardWidth)); - } - plusCounterLabel.setVisible(true); - break; - case "-1/-1": - if (counterView.getCount() != minusCounter) { - minusCounter = counterView.getCount(); - minusCounterLabel.setIcon(getCounterImageWithAmount(minusCounter, ImageManagerImpl.getInstance().getCounterImageRed(), cardWidth)); - } - minusCounterLabel.setVisible(true); - break; - case "loyalty": - if (counterView.getCount() != loyaltyCounter) { - loyaltyCounter = counterView.getCount(); - loyaltyCounterLabel.setIcon(getCounterImageWithAmount(loyaltyCounter, ImageManagerImpl.getInstance().getCounterImageViolet(), cardWidth)); - } - loyaltyCounterLabel.setVisible(true); - break; - default: - if (name.isEmpty()) { // only first other counter is shown - name = counterView.getName(); - otherCounter = counterView.getCount(); - otherCounterLabel.setToolTipText(name); - otherCounterLabel.setIcon(getCounterImageWithAmount(otherCounter, ImageManagerImpl.getInstance().getCounterImageGrey(), cardWidth)); - otherCounterLabel.setVisible(true); - } - } - } - - counterPanel.setVisible(true); - } else { - plusCounterLabel.setVisible(false); - minusCounterLabel.setVisible(false); - loyaltyCounterLabel.setVisible(false); - otherCounterLabel.setVisible(false); - counterPanel.setVisible(false); - } - - } - - private static ImageIcon getCounterImageWithAmount(int amount, BufferedImage image, int cardWidth) { - int factor = cardWidth > WIDTH_LIMIT ? 2 : 1; - int xOffset = amount > 9 ? 2 : 5; - int fontSize = factor == 1 ? amount < 10 ? 12 : amount < 100 ? 10 : amount < 1000 ? 7 : 6 - : amount < 10 ? 19 : amount < 100 ? 15 : amount < 1000 ? 12 : amount < 10000 ? 9 : 8; - BufferedImage newImage; - if (cardWidth > WIDTH_LIMIT) { - newImage = ImageManagerImpl.deepCopy(image); - } else { - newImage = ImageHelper.getResizedImage(image, 20, 20); - } - Graphics graphics = newImage.getGraphics(); - graphics.setColor(Color.BLACK); - graphics.setFont(new Font("Arial Black", amount > 100 ? Font.PLAIN : Font.BOLD, fontSize)); - graphics.drawString(Integer.toString(amount), xOffset * factor, 11 * factor); - return new ImageIcon(newImage); - } - - @Override - public boolean contains(int x, int y) { - return containsThis(x, y, true); - } - - public boolean containsThis(int x, int y, boolean root) { - Point component = getLocation(); - - int cx = getCardX() - component.x; - int cy = getCardY() - component.y; - int cw = getCardWidth(); - int ch = getCardHeight(); - if (isTapped()) { - cy = ch - cw + cx; - ch = cw; - cw = getCardHeight(); - } - - return x >= cx && x <= cx + cw && y >= cy && y <= cy + ch; - } - - @Override - public CardView getOriginal() { - return this.gameCard; - } - - @Override - public Image getImage() { - if (this.hasImage) { - if (gameCard.isFaceDown()) { - return getFaceDownImage(); - } else { - return ImageCache.getImageOriginal(gameCard); - } - } - return null; - } - - @Override - public void mouseClicked(MouseEvent e) { - } - - @Override - public void mouseEntered(MouseEvent e) { - if (gameCard.hideInfo()) { - return; - } - if (!tooltipShowing) { - synchronized (this) { - if (!tooltipShowing) { - TransferData transferData = getTransferDataForMouseEntered(); - if (this.isShowing()) { - tooltipShowing = true; - callback.mouseEntered(e, transferData); - } - } - } - } - } - - @Override - public void mouseDragged(MouseEvent e) { - data.component = this; - callback.mouseDragged(e, data); - } - - @Override - public void mouseMoved(MouseEvent e) { - if (gameCard.hideInfo()) { - return; - } - data.component = this; - callback.mouseMoved(e, data); - } - - @Override - public void mouseExited(MouseEvent e) { - if (gameCard.hideInfo()) { - return; - } - if (getMousePosition(true) != null) { - return; - } - if (tooltipShowing) { - synchronized (this) { - if (tooltipShowing) { - tooltipShowing = false; - data.component = this; - data.card = this.gameCard; - data.popupText = tooltipText; - callback.mouseExited(e, data); - } - } - } - } - - @Override - public void mousePressed(MouseEvent e) { - data.component = this; - data.card = this.gameCard; - data.gameId = this.gameId; - callback.mousePressed(e, data); - } - - @Override - public void mouseReleased(MouseEvent e) { - callback.mouseReleased(e, data); - } - - /** - * Prepares data to be sent to action callback on client side. - * - * @return - */ - private TransferData getTransferDataForMouseEntered() { - data.component = this; - data.card = this.gameCard; - data.popupText = tooltipText; - data.gameId = this.gameId; - data.locationOnScreen = data.component.getLocationOnScreen(); // we need this for popup - data.popupOffsetX = isTapped() ? cardHeight + cardXOffset + POPUP_X_GAP : cardWidth + cardXOffset + POPUP_X_GAP; - data.popupOffsetY = 40; - return data; - } - - protected final String getType(CardView card) { - StringBuilder sbType = new StringBuilder(); - - for (String superType : card.getSuperTypes()) { - sbType.append(superType).append(" "); - } - - for (CardType cardType : card.getCardTypes()) { - sbType.append(cardType.toString()).append(" "); - } - - if (card.getSubTypes().size() > 0) { - sbType.append("- "); - for (String subType : card.getSubTypes()) { - sbType.append(subType).append(" "); - } - } - - return sbType.toString().trim(); - } - - protected final String getText(String cardType, CardView card) { - StringBuilder sb = new StringBuilder(); - if (card instanceof StackAbilityView || card instanceof AbilityView) { - for (String rule : card.getRules()) { - sb.append("\n").append(rule); - } - } else { - sb.append(card.getName()); - if (card.getManaCost().size() > 0) { - sb.append("\n").append(card.getManaCost()); - } - sb.append("\n").append(cardType); - if (card.getColor().hasColor()) { - sb.append("\n").append(card.getColor().toString()); - } - if (card.getCardTypes().contains(CardType.CREATURE)) { - sb.append("\n").append(card.getPower()).append("/").append(card.getToughness()); - } else if (card.getCardTypes().contains(CardType.PLANESWALKER)) { - sb.append("\n").append(card.getLoyalty()); - } - if (card.getRules() == null) { - card.overrideRules(new ArrayList()); - } - for (String rule : card.getRules()) { - sb.append("\n").append(rule); - } - if (card.getExpansionSetCode() != null && card.getExpansionSetCode().length() > 0) { - sb.append("\n").append(card.getCardNumber()).append(" - "); - sb.append(card.getExpansionSetCode()).append(" - "); - sb.append(card.getRarity().toString()); - } - } - return sb.toString(); - } - - @Override - public void update(PermanentView card) { - this.hasSickness = card.hasSummoningSickness(); - this.copyIconPanel.setVisible(card.isCopy()); - update((CardView) card); - } - - @Override - public PermanentView getOriginalPermanent() { - if (isPermanent) { - return (PermanentView) this.gameCard; - } - throw new IllegalStateException("Is not permanent."); - } - - @Override - public void updateCallback(ActionCallback callback, UUID gameId) { - this.callback = callback; - this.gameId = gameId; - } - - public void setTransformed(boolean transformed) { - this.transformed = transformed; - } - - @Override - public void toggleTransformed() { - this.transformed = !this.transformed; - if (transformed) { - if (dayNightButton != null) { // if transformbable card is copied, button can be null - BufferedImage night = ImageManagerImpl.getInstance().getNightImage(); - dayNightButton.setIcon(new ImageIcon(night)); - } - if (this.gameCard.getSecondCardFace() == null) { - LOGGER.error("no second side for card to transform!"); - return; - } - if (!isPermanent) { // use only for custom transformation (when pressing day-night button) - this.temporary = this.gameCard; - update(this.gameCard.getSecondCardFace()); - } - } else { - if (dayNightButton != null) { // if transformbable card is copied, button can be null - BufferedImage day = ImageManagerImpl.getInstance().getDayImage(); - dayNightButton.setIcon(new ImageIcon(day)); - } - if (!isPermanent) { // use only for custom transformation (when pressing day-night button) - update(this.temporary); - this.temporary = null; - } - } - String temp = this.gameCard.getAlternateName(); - this.gameCard.setAlternateName(this.gameCard.getOriginalName()); - this.gameCard.setOriginalName(temp); - updateImage(); - } - - @Override - public void mouseWheelMoved(MouseWheelEvent e) { - if (gameCard.hideInfo()) { - return; - } - data.component = this; - callback.mouseWheelMoved(e, data); - } - - public JPanel getCardArea() { - return cardArea; - } - - @Override - public void componentResized(ComponentEvent ce) { - doLayout(); - // this update removes the isChoosable mark from targetCardsInLibrary - // so only done for permanents because it's needed to redraw counters in different size, if window size was changed - // no perfect solution yet (maybe also other not wanted effects for PermanentView objects) - if (updateCard != null && (updateCard instanceof PermanentView)) { - update(updateCard); - } - } - - @Override - public void componentMoved(ComponentEvent ce) { - } - - @Override - public void componentShown(ComponentEvent ce) { - } - - @Override - public void componentHidden(ComponentEvent ce) { - } - - @Override - public void setTextOffset(int yOffset) { - yTextOffset = yOffset; - } - - @Override - public JPopupMenu getPopupMenu() { - return popupMenu; - } - - @Override - public void setPopupMenu(JPopupMenu popupMenu) { - this.popupMenu = popupMenu; - } - -} +package org.mage.card.arcane; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.UUID; +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import mage.cards.MagePermanent; +import mage.cards.TextPopup; +import mage.cards.action.ActionCallback; +import mage.cards.action.TransferData; +import mage.client.dialog.PreferencesDialog; +import mage.client.plugins.adapters.MageActionCallback; +import mage.client.plugins.impl.Plugins; +import mage.client.util.ImageHelper; +import mage.client.util.audio.AudioManager; +import mage.components.ImagePanel; +import mage.constants.AbilityType; +import mage.constants.CardType; +import mage.constants.EnlargeMode; +import mage.utils.CardUtil; +import mage.view.AbilityView; +import mage.view.CardView; +import mage.view.CounterView; +import mage.view.PermanentView; +import mage.view.StackAbilityView; +import net.java.truevfs.access.TFile; +import org.apache.log4j.Logger; +import org.mage.card.arcane.ScaledImagePanel.MultipassType; +import org.mage.card.arcane.ScaledImagePanel.ScalingType; +import static org.mage.plugins.card.constants.Constants.THUMBNAIL_SIZE_FULL; +import org.mage.plugins.card.dl.sources.DirectLinksForDownload; +import org.mage.plugins.card.images.ImageCache; +import org.mage.plugins.card.utils.impl.ImageManagerImpl; + +/** + * Main class for drawing Mage card object. + * + * @author arcane, nantuko, noxx + */ +@SuppressWarnings({"unchecked", "rawtypes"}) +public class CardPanel extends MagePermanent implements MouseListener, MouseMotionListener, MouseWheelListener, ComponentListener { + + private static final long serialVersionUID = -3272134219262184410L; + + private static final Logger LOGGER = Logger.getLogger(CardPanel.class); + + private static final int WIDTH_LIMIT = 90; // card width limit to create smaller counter + public static final double TAPPED_ANGLE = Math.PI / 2; + public static final double FLIPPED_ANGLE = Math.PI; + public static final float ASPECT_RATIO = 3.5f / 2.5f; + public static final int POPUP_X_GAP = 1; // prevent tooltip window from blinking + + public static CardPanel dragAnimationPanel; + + public static final Rectangle CARD_SIZE_FULL = new Rectangle(101, 149); + + private static final float ROUNDED_CORNER_SIZE = 0.1f; + private static final float BLACK_BORDER_SIZE = 0.03f; + private static final int TEXT_GLOW_SIZE = 6; + private static final float TEXT_GLOW_INTENSITY = 3f; + private static final float ROT_CENTER_TO_TOP_CORNER = 1.0295630140987000315797369464196f; + private static final float ROT_CENTER_TO_BOTTOM_CORNER = 0.7071067811865475244008443621048f; + + public CardView gameCard; + public CardView updateCard; + + // for two faced cards + public CardView temporary; + private List links = new ArrayList<>(); + + public double tappedAngle = 0; + public double flippedAngle = 0; + public final ScaledImagePanel imagePanel; + public ImagePanel overlayPanel; + + public JPanel buttonPanel; + private JButton dayNightButton; + + public JPanel copyIconPanel; + private JButton showCopySourceButton; + + public JPanel iconPanel; + private JButton typeButton; + + public JPanel counterPanel; + private JLabel loyaltyCounterLabel; + private JLabel plusCounterLabel; + private JLabel otherCounterLabel; + private JLabel minusCounterLabel; + private int loyaltyCounter; + private int plusCounter; + private int otherCounter; + private int minusCounter; + private int lastCardWidth; + + private GlowText titleText; + private GlowText ptText; + private boolean displayEnabled = true; + private boolean isAnimationPanel; + public int cardXOffset, cardYOffset, cardWidth, cardHeight; + private int symbolWidth; + + private boolean isSelected; + private boolean isPlayable; + private boolean isChoosable; + private boolean canAttack; + private boolean showCastingCost; + private boolean hasImage = false; + private float alpha = 1.0f; + + private ActionCallback callback; + + protected boolean tooltipShowing; + protected TextPopup tooltipText = new TextPopup(); + protected UUID gameId; + private TransferData data = new TransferData(); + + private boolean isPermanent; + private boolean hasSickness; + private String zone; + + public double transformAngle = 1; + + private boolean transformed; + private boolean animationInProgress = false; + + private boolean displayTitleAnyway; + + private JPanel cardArea; + + private int yTextOffset = 10; + + // if this is set, it's opened if the user right clicks on the card panel + private JPopupMenu popupMenu; + + public CardPanel(CardView newGameCard, UUID gameId, final boolean loadImage, ActionCallback callback, final boolean foil, Dimension dimension) { + this.gameCard = newGameCard; + this.callback = callback; + this.gameId = gameId; + + this.isPermanent = this.gameCard instanceof PermanentView; + if (isPermanent) { + this.hasSickness = ((PermanentView) this.gameCard).hasSummoningSickness(); + } + + this.setCardBounds(0, 0, dimension.width, dimension.height); + + //for container debug (don't remove) + //setBorder(BorderFactory.createLineBorder(Color.green)); + if (this.gameCard.canTransform()) { + buttonPanel = new JPanel(); + buttonPanel.setLayout(null); + buttonPanel.setOpaque(false); + add(buttonPanel); + + dayNightButton = new JButton(""); + dayNightButton.setLocation(2, 2); + dayNightButton.setSize(25, 25); + + buttonPanel.setVisible(true); + + BufferedImage day = ImageManagerImpl.getInstance().getDayImage(); + dayNightButton.setIcon(new ImageIcon(day)); + + buttonPanel.add(dayNightButton); + + dayNightButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // if card is being rotated, ignore action performed + // if card is tapped, no visual transforming is possible (implementation limitation) + // if card is permanent, it will be rotated by Mage, so manual rotate should be possible + if (animationInProgress || isTapped() || isPermanent) { + return; + } + Animation.transformCard(CardPanel.this, CardPanel.this, true); + } + }); + } + if (!newGameCard.isAbility()) { + // panel to show counters on the card + counterPanel = new JPanel(); + counterPanel.setLayout(null); + counterPanel.setOpaque(false); + add(counterPanel); + + plusCounterLabel = new JLabel(""); + plusCounterLabel.setToolTipText("+1/+1"); + counterPanel.add(plusCounterLabel); + + minusCounterLabel = new JLabel(""); + minusCounterLabel.setToolTipText("-1/-1"); + counterPanel.add(minusCounterLabel); + + loyaltyCounterLabel = new JLabel(""); + loyaltyCounterLabel.setToolTipText("loyalty"); + counterPanel.add(loyaltyCounterLabel); + + otherCounterLabel = new JLabel(""); + counterPanel.add(otherCounterLabel); + + counterPanel.setVisible(false); + } + if (newGameCard.isAbility()) { + if (AbilityType.TRIGGERED.equals(newGameCard.getAbilityType())) { + setTypeIcon(ImageManagerImpl.getInstance().getTriggeredAbilityImage(), "Triggered Ability"); + } else if (AbilityType.ACTIVATED.equals(newGameCard.getAbilityType())) { + setTypeIcon(ImageManagerImpl.getInstance().getActivatedAbilityImage(), "Activated Ability"); + } + } + + if (this.gameCard.isToken()) { + setTypeIcon(ImageManagerImpl.getInstance().getTokenIconImage(), "Token Permanent"); + } + + // icon to inform about permanent is copying something + if (this.gameCard instanceof PermanentView) { + copyIconPanel = new JPanel(); + copyIconPanel.setLayout(null); + copyIconPanel.setOpaque(false); + add(copyIconPanel); + + showCopySourceButton = new JButton(""); + showCopySourceButton.setLocation(2, 2); + showCopySourceButton.setSize(25, 25); + showCopySourceButton.setToolTipText("This permanent is copying a target. To see original image, push this button or turn mouse wheel down while hovering with the mouse pointer over the permanent."); + copyIconPanel.setVisible(((PermanentView) this.gameCard).isCopy()); + + showCopySourceButton.setIcon(new ImageIcon(ImageManagerImpl.getInstance().getCopyInformIconImage())); + + copyIconPanel.add(showCopySourceButton); + + showCopySourceButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ActionCallback callback = Plugins.getInstance().getActionCallback(); + ((MageActionCallback) callback).enlargeCard(EnlargeMode.COPY); + } + }); + } + + setBackground(Color.black); + setOpaque(false); + + addMouseListener(this); + addMouseMotionListener(this); + addMouseWheelListener(this); + addComponentListener(this); + + displayTitleAnyway = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_CARD_NAMES, "true").equals("true"); + + titleText = new GlowText(); + setText(gameCard); +// int fontSize = (int) cardHeight / 11; +// titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); + titleText.setForeground(Color.white); + titleText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); + titleText.setWrap(true); + add(titleText); + + ptText = new GlowText(); + if (CardUtil.isCreature(gameCard)) { + ptText.setText(gameCard.getPower() + "/" + gameCard.getToughness()); + } else if (CardUtil.isPlaneswalker(gameCard)) { + ptText.setText(gameCard.getLoyalty()); + } +// ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); + ptText.setForeground(Color.white); + ptText.setGlow(Color.black, TEXT_GLOW_SIZE, TEXT_GLOW_INTENSITY); + add(ptText); + + BufferedImage sickness = ImageManagerImpl.getInstance().getSicknessImage(); + overlayPanel = new ImagePanel(sickness, ImagePanel.SCALED); + overlayPanel.setOpaque(false); + add(overlayPanel); + + imagePanel = new ScaledImagePanel(); + imagePanel.setBorder(BorderFactory.createLineBorder(Color.white)); + add(imagePanel); + imagePanel.setScaleLarger(true); + imagePanel.setScalingType(ScalingType.nearestNeighbor); + imagePanel.setScalingMultiPassType(MultipassType.none); + + String cardType = getType(newGameCard); + tooltipText.setText(getText(cardType, newGameCard)); + + Util.threadPool.submit(new Runnable() { + @Override + public void run() { + try { + tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; + flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; + if (!loadImage) { + return; + } + BufferedImage srcImage; + if (gameCard.isFaceDown()) { + srcImage = getFaceDownImage(); + } else { + srcImage = ImageCache.getImage(gameCard, getCardWidth(), getCardHeight()); + } + if (srcImage != null) { + hasImage = true; + setText(gameCard); + setImage(srcImage); + } + if (gameCard.isTransformed()) { + toggleTransformed(); + } + setText(gameCard); + } catch (Exception e) { + LOGGER.fatal("Problem during image animation", e); + } catch (Error err) { + LOGGER.error("Problem during image animation", err); + } + } + }); + } + + private void setTypeIcon(BufferedImage bufferedImage, String toolTipText) { + iconPanel = new JPanel(); + iconPanel.setLayout(null); + iconPanel.setOpaque(false); + add(iconPanel); + + typeButton = new JButton(""); + typeButton.setLocation(2, 2); + typeButton.setSize(25, 25); + + iconPanel.setVisible(true); + typeButton.setIcon(new ImageIcon(bufferedImage)); + if (toolTipText != null) { + typeButton.setToolTipText(toolTipText); + } + iconPanel.add(typeButton); + } + + public void cleanUp() { + if (dayNightButton != null) { + for (ActionListener al : dayNightButton.getActionListeners()) { + dayNightButton.removeActionListener(al); + } + } + for (MouseListener ml : this.getMouseListeners()) { + this.removeMouseListener(ml); + } + for (MouseMotionListener ml : this.getMouseMotionListeners()) { + this.removeMouseMotionListener(ml); + } + for (MouseWheelListener ml : this.getMouseWheelListeners()) { + this.removeMouseWheelListener(ml); + } + // this holds reference to ActionCallback forever so set it to null to prevent + this.callback = null; + this.data = null; + this.counterPanel = null; + } + + private void setText(CardView card) { + titleText.setText(!displayTitleAnyway && hasImage ? "" : card.getName()); + } + + private void setImage(Image srcImage) { + synchronized (imagePanel) { + imagePanel.setImage(srcImage); + repaint(); + } + doLayout(); + } + + public void setImage(final CardPanel panel) { + synchronized (panel.imagePanel) { + if (panel.imagePanel.hasImage()) { + setImage(panel.imagePanel.getSrcImage()); + } + } + } + + @Override + public void setZone(String zone) { + this.zone = zone; + } + + @Override + public String getZone() { + return zone; + } + + public void setScalingType(ScalingType scalingType) { + imagePanel.setScalingType(scalingType); + } + + public void setDisplayEnabled(boolean displayEnabled) { + this.displayEnabled = displayEnabled; + } + + public boolean isDisplayEnabled() { + return displayEnabled; + } + + public void setAnimationPanel(boolean isAnimationPanel) { + this.isAnimationPanel = isAnimationPanel; + } + + @Override + public void setSelected(boolean isSelected) { + this.isSelected = isSelected; + if (isSelected) { + this.titleText.setGlowColor(Color.green); + } else { + this.titleText.setGlowColor(Color.black); + } + // noxx: bad idea is to call repaint in setter method + ////repaint(); + } + + @Override + public void setChoosable(boolean isChoosable) { + this.isChoosable = isChoosable; + } + + @Override + public void setCardAreaRef(JPanel cardArea) { + this.cardArea = cardArea; + } + + public boolean getSelected() { + return this.isSelected; + } + + public void setShowCastingCost(boolean showCastingCost) { + this.showCastingCost = showCastingCost; + } + + @Override + public void paint(Graphics g) { + if (!displayEnabled) { + return; + } + if (!isValid()) { + super.validate(); + } + Graphics2D g2d = (Graphics2D) g; + if (transformAngle < 1) { + float edgeOffset = (cardWidth + cardXOffset) / 2f; + g2d.translate(edgeOffset * (1 - transformAngle), 0); + g2d.scale(transformAngle, 1); + } + if (tappedAngle + flippedAngle > 0) { + g2d = (Graphics2D) g2d.create(); + float edgeOffset = cardWidth / 2f; + double angle = tappedAngle + (Math.abs(flippedAngle - FLIPPED_ANGLE) < 0.001 ? 0 : flippedAngle); + g2d.rotate(angle, cardXOffset + edgeOffset, cardYOffset + cardHeight - edgeOffset); + } + super.paint(g2d); + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + if (alpha != 1.0f) { + AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha); + g2d.setComposite(composite); + } + + if (!hasImage) { + g2d.setColor(new Color(30, 200, 200, 120)); + } else { + g2d.setColor(new Color(0, 0, 0, 0)); + } + + int cornerSize = Math.max(4, Math.round(cardWidth * ROUNDED_CORNER_SIZE)); + g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize); + + if (isSelected) { + g2d.setColor(Color.green); + g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + } else if (isChoosable) { + g2d.setColor(new Color(250, 250, 0, 230)); + g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + } else if (isPlayable) { + g2d.setColor(new Color(153, 102, 204, 200)); + //g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + g2d.fillRoundRect(cardXOffset, cardYOffset, cardWidth, cardHeight, cornerSize, cornerSize); + } + + if (canAttack) { + g2d.setColor(new Color(0, 0, 255, 230)); + g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + } + + //TODO:uncomment + /* + if (gameCard.isAttacking()) { + g2d.setColor(new Color(200,10,10,200)); + g2d.fillRoundRect(cardXOffset+1, cardYOffset+1, cardWidth-2, cardHeight-2, cornerSize, cornerSize); + }*/ + } + + @Override + protected void paintChildren(Graphics g) { + super.paintChildren(g); + + if (showCastingCost && !isAnimationPanel && cardWidth < 200 && cardWidth > 60) { + String manaCost = ManaSymbols.getStringManaCost(gameCard.getManaCost()); + int width = getWidth(manaCost); + if (hasImage) { + ManaSymbols.draw(g, manaCost, cardXOffset + cardWidth - width - 5, cardYOffset + 5, symbolWidth); + } else { + ManaSymbols.draw(g, manaCost, cardXOffset + 8, cardHeight - 9, symbolWidth); + } + } + } + + private int getWidth(String manaCost) { + int width = 0; + manaCost = manaCost.replace("\\", ""); + StringTokenizer tok = new StringTokenizer(manaCost, " "); + while (tok.hasMoreTokens()) { + tok.nextToken(); + width += symbolWidth; + } + return width; + } + + @Override + public void doLayout() { + int borderSize = Math.round(cardWidth * BLACK_BORDER_SIZE); + imagePanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + imagePanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + + if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) { + overlayPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + overlayPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + } else { + overlayPanel.setVisible(false); + } + + if (buttonPanel != null) { + buttonPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + buttonPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + dayNightButton.setLocation(0, cardHeight - 30); + } + if (iconPanel != null) { + iconPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + iconPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + } + if (copyIconPanel != null) { + copyIconPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + copyIconPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + } + if (counterPanel != null) { + counterPanel.setLocation(cardXOffset + borderSize, cardYOffset + borderSize); + counterPanel.setSize(cardWidth - borderSize * 2, cardHeight - borderSize * 2); + int size = cardWidth > WIDTH_LIMIT ? 40 : 20; + + minusCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size * 2); + minusCounterLabel.setSize(size, size); + + plusCounterLabel.setLocation(5, counterPanel.getHeight() - size * 2); + plusCounterLabel.setSize(size, size); + + loyaltyCounterLabel.setLocation(counterPanel.getWidth() - size, counterPanel.getHeight() - size); + loyaltyCounterLabel.setSize(size, size); + + otherCounterLabel.setLocation(5, counterPanel.getHeight() - size); + otherCounterLabel.setSize(size, size); + + } + int fontHeight = Math.round(cardHeight * (27f / 680)); + boolean showText = (!isAnimationPanel && fontHeight < 12); + titleText.setVisible(showText); + ptText.setVisible(showText); + + if (showText) { + int fontSize = (int) cardHeight / 11; + titleText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); + + int titleX = Math.round(cardWidth * (20f / 480)); + int titleY = Math.round(cardHeight * (9f / 680)) + yTextOffset; + titleText.setBounds(cardXOffset + titleX, cardYOffset + titleY, cardWidth - titleX, cardHeight - titleY); + + ptText.setFont(getFont().deriveFont(Font.BOLD, fontSize)); + Dimension ptSize = ptText.getPreferredSize(); + ptText.setSize(ptSize.width, ptSize.height); + int ptX = Math.round(cardWidth * (420f / 480)) - ptSize.width / 2; + int ptY = Math.round(cardHeight * (675f / 680)) - ptSize.height; + + int offsetX = Math.round((CARD_SIZE_FULL.width - cardWidth) / 10.0f); + + ptText.setLocation(cardXOffset + ptX - TEXT_GLOW_SIZE / 2 - offsetX, cardYOffset + ptY - TEXT_GLOW_SIZE / 2); + } + + if (isAnimationPanel || cardWidth < 200) { + imagePanel.setScalingType(ScalingType.nearestNeighbor); + } else { + imagePanel.setScalingType(ScalingType.bilinear); + } + } + + @Override + public String toString() { + return gameCard.toString(); + } + + @Override + public final void setCardBounds(int x, int y, int cardWidth, int cardHeight) { + this.cardWidth = cardWidth; + this.symbolWidth = cardWidth / 7; + this.cardHeight = cardHeight; + if (this.isPermanent) { + int rotCenterX = Math.round(cardWidth / 2f); + int rotCenterY = cardHeight - rotCenterX; + int rotCenterToTopCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_TOP_CORNER); + int rotCenterToBottomCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_BOTTOM_CORNER); + int xOffset = getXOffset(cardWidth); + int yOffset = getYOffset(cardWidth, cardHeight); + cardXOffset = -xOffset; + cardYOffset = -yOffset; + int width = -xOffset + rotCenterX + rotCenterToTopCorner; + int height = -yOffset + rotCenterY + rotCenterToBottomCorner; + setBounds(x + xOffset, y + yOffset, width, height); + } else { + cardXOffset = 5; + cardYOffset = 5; + int width = cardXOffset * 2 + cardWidth; + int height = cardYOffset * 2 + cardHeight; + setBounds(x - cardXOffset, y - cardYOffset, width, height); + } + } + + public int getXOffset(int cardWidth) { + if (this.isPermanent) { + int rotCenterX = Math.round(cardWidth / 2f); + int rotCenterToBottomCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_BOTTOM_CORNER); + int xOffset = rotCenterX - rotCenterToBottomCorner; + return xOffset; + } else { + return cardXOffset; + } + } + + public int getYOffset(int cardWidth, int cardHeight) { + if (this.isPermanent) { + int rotCenterX = Math.round(cardWidth / 2f); + int rotCenterY = cardHeight - rotCenterX; + int rotCenterToTopCorner = Math.round(cardWidth * CardPanel.ROT_CENTER_TO_TOP_CORNER); + int yOffset = rotCenterY - rotCenterToTopCorner; + return yOffset; + } else { + return cardYOffset; + } + + } + + public int getCardX() { + return getX() + cardXOffset; + } + + public int getCardY() { + return getY() + cardYOffset; + } + + public int getCardWidth() { + return cardWidth; + } + + public int getCardHeight() { + return cardHeight; + } + + public Point getCardLocation() { + Point p = getLocation(); + p.x += cardXOffset; + p.y += cardYOffset; + return p; + } + + public CardView getCard() { + return this.gameCard; + } + + @Override + public void setAlpha(float alpha) { + this.alpha = alpha; + if (alpha == 0) { + this.ptText.setVisible(false); + this.titleText.setVisible(false); + } else if (alpha == 1.0f) { + this.ptText.setVisible(true); + this.titleText.setVisible(true); + } + } + + @Override + public float getAlpha() { + return alpha; + } + + public int getCardXOffset() { + return cardXOffset; + } + + public int getCardYOffset() { + return cardYOffset; + } + + @Override + public void updateImage() { + Util.threadPool.submit(new Runnable() { + @Override + public void run() { + try { + tappedAngle = isTapped() ? CardPanel.TAPPED_ANGLE : 0; + flippedAngle = isFlipped() ? CardPanel.FLIPPED_ANGLE : 0; + BufferedImage srcImage; + if (gameCard.isFaceDown()) { + srcImage = getFaceDownImage(); + } else if (cardWidth > THUMBNAIL_SIZE_FULL.width) { + srcImage = ImageCache.getImage(gameCard, cardWidth, cardHeight); + } else { + srcImage = ImageCache.getThumbnail(gameCard); + } + if (srcImage != null) { + hasImage = true; + setText(gameCard); + setImage(srcImage); + } + } catch (Exception e) { + e.printStackTrace(); + } catch (Error err) { + err.printStackTrace(); + } + } + }); + } + + private BufferedImage getFaceDownImage() { + if (isPermanent) { + if (((PermanentView) gameCard).isMorphed()) { + return ImageCache.getMorphImage(); + } else { + return ImageCache.getManifestImage(); + } + } else if (this.gameCard instanceof StackAbilityView) { + return ImageCache.getMorphImage(); + } else { + return ImageCache.loadImage(new TFile(DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename)); + } + } + + @Override + public List getLinks() { + return links; + } + + @Override + public boolean isTapped() { + if (isPermanent) { + return ((PermanentView) gameCard).isTapped(); + } + return false; + } + + @Override + public boolean isFlipped() { + if (isPermanent) { + return ((PermanentView) gameCard).isFlipped(); + } + return false; + } + + @Override + public boolean isTransformed() { + if (isPermanent) { + return gameCard.isTransformed(); + } + return false; + } + + @Override + public void showCardTitle() { + displayTitleAnyway = true; + setText(gameCard); + } + + @Override + public void onBeginAnimation() { + animationInProgress = true; + } + + @Override + public void onEndAnimation() { + animationInProgress = false; + } + + @Override + public void update(CardView card) { + this.updateCard = card; + if (isPermanent && (card instanceof PermanentView)) { + boolean needsTapping = isTapped() != ((PermanentView) card).isTapped(); + boolean needsFlipping = isFlipped() != ((PermanentView) card).isFlipped(); + if (needsTapping || needsFlipping) { + Animation.tapCardToggle(this, this, needsTapping, needsFlipping); + } + if (needsTapping && ((PermanentView) card).isTapped()) { + AudioManager.playTapPermanent(); + } + boolean needsTranforming = isTransformed() != card.isTransformed(); + if (needsTranforming) { + Animation.transformCard(this, this, card.isTransformed()); + } + } + if (card.canTransform()) { + dayNightButton.setVisible(!isPermanent); + } + + if (CardUtil.isCreature(card) && CardUtil.isPlaneswalker(card)) { + ptText.setText(card.getPower() + "/" + card.getToughness() + " (" + card.getLoyalty() + ")"); + } else if (CardUtil.isCreature(card)) { + ptText.setText(card.getPower() + "/" + card.getToughness()); + } else if (CardUtil.isPlaneswalker(card)) { + ptText.setText(card.getLoyalty()); + } else { + ptText.setText(""); + } + setText(card); + + this.isPlayable = card.isPlayable(); + this.isChoosable = card.isChoosable(); + this.canAttack = card.isCanAttack(); + this.isSelected = card.isSelected(); + + boolean updateImage = !gameCard.getName().equals(card.getName()) || gameCard.isFaceDown() != card.isFaceDown(); // update after e.g. turning a night/day card + if (updateImage && gameCard.canTransform() && card.canTransform() && transformed) { + if (card.getSecondCardFace() != null && card.getSecondCardFace().getName().equals(gameCard.getName())) { + transformed = false; + } + } + this.gameCard = card; + + String cardType = getType(card); + tooltipText.setText(getText(cardType, card)); + + if (hasSickness && CardUtil.isCreature(gameCard) && isPermanent) { + overlayPanel.setVisible(true); + } else { + overlayPanel.setVisible(false); + } + if (updateImage) { + updateImage(); + if (card.canTransform()) { + BufferedImage transformIcon; + if (transformed || card.isTransformed()) { + transformIcon = ImageManagerImpl.getInstance().getNightImage(); + } else { + transformIcon = ImageManagerImpl.getInstance().getDayImage(); + } + dayNightButton.setIcon(new ImageIcon(transformIcon)); + } + } + + if (counterPanel != null) { + updateCounters(card); + } + + repaint(); + } + + private void updateCounters(CardView card) { + if (card.getCounters() != null && !card.getCounters().isEmpty()) { + String name = ""; + if (lastCardWidth != cardWidth) { + lastCardWidth = cardWidth; + plusCounter = 0; + minusCounter = 0; + otherCounter = 0; + loyaltyCounter = 0; + } + plusCounterLabel.setVisible(false); + minusCounterLabel.setVisible(false); + loyaltyCounterLabel.setVisible(false); + otherCounterLabel.setVisible(false); + for (CounterView counterView : card.getCounters()) { + if (counterView.getCount() == 0) { + continue; + } + switch (counterView.getName()) { + case "+1/+1": + if (counterView.getCount() != plusCounter) { + plusCounter = counterView.getCount(); + plusCounterLabel.setIcon(getCounterImageWithAmount(plusCounter, ImageManagerImpl.getInstance().getCounterImageGreen(), cardWidth)); + } + plusCounterLabel.setVisible(true); + break; + case "-1/-1": + if (counterView.getCount() != minusCounter) { + minusCounter = counterView.getCount(); + minusCounterLabel.setIcon(getCounterImageWithAmount(minusCounter, ImageManagerImpl.getInstance().getCounterImageRed(), cardWidth)); + } + minusCounterLabel.setVisible(true); + break; + case "loyalty": + if (counterView.getCount() != loyaltyCounter) { + loyaltyCounter = counterView.getCount(); + loyaltyCounterLabel.setIcon(getCounterImageWithAmount(loyaltyCounter, ImageManagerImpl.getInstance().getCounterImageViolet(), cardWidth)); + } + loyaltyCounterLabel.setVisible(true); + break; + default: + if (name.isEmpty()) { // only first other counter is shown + name = counterView.getName(); + otherCounter = counterView.getCount(); + otherCounterLabel.setToolTipText(name); + otherCounterLabel.setIcon(getCounterImageWithAmount(otherCounter, ImageManagerImpl.getInstance().getCounterImageGrey(), cardWidth)); + otherCounterLabel.setVisible(true); + } + } + } + + counterPanel.setVisible(true); + } else { + plusCounterLabel.setVisible(false); + minusCounterLabel.setVisible(false); + loyaltyCounterLabel.setVisible(false); + otherCounterLabel.setVisible(false); + counterPanel.setVisible(false); + } + + } + + private static ImageIcon getCounterImageWithAmount(int amount, BufferedImage image, int cardWidth) { + int factor = cardWidth > WIDTH_LIMIT ? 2 : 1; + int xOffset = amount > 9 ? 2 : 5; + int fontSize = factor == 1 ? amount < 10 ? 12 : amount < 100 ? 10 : amount < 1000 ? 7 : 6 + : amount < 10 ? 19 : amount < 100 ? 15 : amount < 1000 ? 12 : amount < 10000 ? 9 : 8; + BufferedImage newImage; + if (cardWidth > WIDTH_LIMIT) { + newImage = ImageManagerImpl.deepCopy(image); + } else { + newImage = ImageHelper.getResizedImage(image, 20, 20); + } + Graphics graphics = newImage.getGraphics(); + graphics.setColor(Color.BLACK); + graphics.setFont(new Font("Arial Black", amount > 100 ? Font.PLAIN : Font.BOLD, fontSize)); + graphics.drawString(Integer.toString(amount), xOffset * factor, 11 * factor); + return new ImageIcon(newImage); + } + + @Override + public boolean contains(int x, int y) { + return containsThis(x, y, true); + } + + public boolean containsThis(int x, int y, boolean root) { + Point component = getLocation(); + + int cx = getCardX() - component.x; + int cy = getCardY() - component.y; + int cw = getCardWidth(); + int ch = getCardHeight(); + if (isTapped()) { + cy = ch - cw + cx; + ch = cw; + cw = getCardHeight(); + } + + return x >= cx && x <= cx + cw && y >= cy && y <= cy + ch; + } + + @Override + public CardView getOriginal() { + return this.gameCard; + } + + @Override + public Image getImage() { + if (this.hasImage) { + if (gameCard.isFaceDown()) { + return getFaceDownImage(); + } else { + return ImageCache.getImageOriginal(gameCard); + } + } + return null; + } + + @Override + public void mouseClicked(MouseEvent e) { + } + + @Override + public void mouseEntered(MouseEvent e) { + if (gameCard.hideInfo()) { + return; + } + if (!tooltipShowing) { + synchronized (this) { + if (!tooltipShowing) { + TransferData transferData = getTransferDataForMouseEntered(); + if (this.isShowing()) { + tooltipShowing = true; + callback.mouseEntered(e, transferData); + } + } + } + } + } + + @Override + public void mouseDragged(MouseEvent e) { + data.component = this; + callback.mouseDragged(e, data); + } + + @Override + public void mouseMoved(MouseEvent e) { + if (gameCard.hideInfo()) { + return; + } + data.component = this; + callback.mouseMoved(e, data); + } + + @Override + public void mouseExited(MouseEvent e) { + if (gameCard.hideInfo()) { + return; + } + if (getMousePosition(true) != null) { + return; + } + if (tooltipShowing) { + synchronized (this) { + if (tooltipShowing) { + tooltipShowing = false; + data.component = this; + data.card = this.gameCard; + data.popupText = tooltipText; + callback.mouseExited(e, data); + } + } + } + } + + @Override + public void mousePressed(MouseEvent e) { + data.component = this; + data.card = this.gameCard; + data.gameId = this.gameId; + callback.mousePressed(e, data); + } + + @Override + public void mouseReleased(MouseEvent e) { + callback.mouseReleased(e, data); + } + + /** + * Prepares data to be sent to action callback on client side. + * + * @return + */ + private TransferData getTransferDataForMouseEntered() { + data.component = this; + data.card = this.gameCard; + data.popupText = tooltipText; + data.gameId = this.gameId; + data.locationOnScreen = data.component.getLocationOnScreen(); // we need this for popup + data.popupOffsetX = isTapped() ? cardHeight + cardXOffset + POPUP_X_GAP : cardWidth + cardXOffset + POPUP_X_GAP; + data.popupOffsetY = 40; + return data; + } + + protected final String getType(CardView card) { + StringBuilder sbType = new StringBuilder(); + + for (String superType : card.getSuperTypes()) { + sbType.append(superType).append(" "); + } + + for (CardType cardType : card.getCardTypes()) { + sbType.append(cardType.toString()).append(" "); + } + + if (card.getSubTypes().size() > 0) { + sbType.append("- "); + for (String subType : card.getSubTypes()) { + sbType.append(subType).append(" "); + } + } + + return sbType.toString().trim(); + } + + protected final String getText(String cardType, CardView card) { + StringBuilder sb = new StringBuilder(); + if (card instanceof StackAbilityView || card instanceof AbilityView) { + for (String rule : card.getRules()) { + sb.append("\n").append(rule); + } + } else { + sb.append(card.getName()); + if (card.getManaCost().size() > 0) { + sb.append("\n").append(card.getManaCost()); + } + sb.append("\n").append(cardType); + if (card.getColor().hasColor()) { + sb.append("\n").append(card.getColor().toString()); + } + if (card.getCardTypes().contains(CardType.CREATURE)) { + sb.append("\n").append(card.getPower()).append("/").append(card.getToughness()); + } else if (card.getCardTypes().contains(CardType.PLANESWALKER)) { + sb.append("\n").append(card.getLoyalty()); + } + if (card.getRules() == null) { + card.overrideRules(new ArrayList()); + } + for (String rule : card.getRules()) { + sb.append("\n").append(rule); + } + if (card.getExpansionSetCode() != null && card.getExpansionSetCode().length() > 0) { + sb.append("\n").append(card.getCardNumber()).append(" - "); + sb.append(card.getExpansionSetCode()).append(" - "); + sb.append(card.getRarity().toString()); + } + } + return sb.toString(); + } + + @Override + public void update(PermanentView card) { + this.hasSickness = card.hasSummoningSickness(); + this.copyIconPanel.setVisible(card.isCopy()); + update((CardView) card); + } + + @Override + public PermanentView getOriginalPermanent() { + if (isPermanent) { + return (PermanentView) this.gameCard; + } + throw new IllegalStateException("Is not permanent."); + } + + @Override + public void updateCallback(ActionCallback callback, UUID gameId) { + this.callback = callback; + this.gameId = gameId; + } + + public void setTransformed(boolean transformed) { + this.transformed = transformed; + } + + @Override + public void toggleTransformed() { + this.transformed = !this.transformed; + if (transformed) { + if (dayNightButton != null) { // if transformbable card is copied, button can be null + BufferedImage night = ImageManagerImpl.getInstance().getNightImage(); + dayNightButton.setIcon(new ImageIcon(night)); + } + if (this.gameCard.getSecondCardFace() == null) { + LOGGER.error("no second side for card to transform!"); + return; + } + if (!isPermanent) { // use only for custom transformation (when pressing day-night button) + this.temporary = this.gameCard; + update(this.gameCard.getSecondCardFace()); + } + } else { + if (dayNightButton != null) { // if transformbable card is copied, button can be null + BufferedImage day = ImageManagerImpl.getInstance().getDayImage(); + dayNightButton.setIcon(new ImageIcon(day)); + } + if (!isPermanent) { // use only for custom transformation (when pressing day-night button) + update(this.temporary); + this.temporary = null; + } + } + String temp = this.gameCard.getAlternateName(); + this.gameCard.setAlternateName(this.gameCard.getOriginalName()); + this.gameCard.setOriginalName(temp); + updateImage(); + } + + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + if (gameCard.hideInfo()) { + return; + } + data.component = this; + callback.mouseWheelMoved(e, data); + } + + public JPanel getCardArea() { + return cardArea; + } + + @Override + public void componentResized(ComponentEvent ce) { + doLayout(); + // this update removes the isChoosable mark from targetCardsInLibrary + // so only done for permanents because it's needed to redraw counters in different size, if window size was changed + // no perfect solution yet (maybe also other not wanted effects for PermanentView objects) + if (updateCard != null && (updateCard instanceof PermanentView)) { + update(updateCard); + } + } + + @Override + public void componentMoved(ComponentEvent ce) { + } + + @Override + public void componentShown(ComponentEvent ce) { + } + + @Override + public void componentHidden(ComponentEvent ce) { + } + + @Override + public void setTextOffset(int yOffset) { + yTextOffset = yOffset; + } + + @Override + public JPopupMenu getPopupMenu() { + return popupMenu; + } + + @Override + public void setPopupMenu(JPopupMenu popupMenu) { + this.popupMenu = popupMenu; + } + +} diff --git a/Mage.Sets/src/mage/sets/betrayersofkamigawa/FinalJudgment.java b/Mage.Sets/src/mage/sets/betrayersofkamigawa/FinalJudgment.java index 4089078c877..2808eaf96fd 100644 --- a/Mage.Sets/src/mage/sets/betrayersofkamigawa/FinalJudgment.java +++ b/Mage.Sets/src/mage/sets/betrayersofkamigawa/FinalJudgment.java @@ -1,98 +1,59 @@ -/* - * 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 mage.sets.betrayersofkamigawa; - -import mage.constants.CardType; -import mage.constants.Rarity; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.cards.CardImpl; -import mage.constants.Outcome; -import mage.filter.FilterPermanent; -import mage.filter.predicate.mageobject.CardTypePredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; - -import java.util.UUID; - -/** - * - * @author Loki - */ -public class FinalJudgment extends CardImpl { - - public FinalJudgment(UUID ownerId) { - super(ownerId, 4, "Final Judgment", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{4}{W}{W}"); - this.expansionSetCode = "BOK"; - - // Exile all creatures. - this.getSpellAbility().addEffect(new FinalJudgmentEffect()); - - } - - public FinalJudgment(final FinalJudgment card) { - super(card); - } - - @Override - public FinalJudgment copy() { - return new FinalJudgment(this); - } -} - -class FinalJudgmentEffect extends OneShotEffect { - - private static final FilterPermanent filter = new FilterPermanent(""); - - static { - filter.add(new CardTypePredicate(CardType.CREATURE)); - } - - public FinalJudgmentEffect() { - super(Outcome.Exile); - staticText = "Exile all creatures"; - } - - public FinalJudgmentEffect(final FinalJudgmentEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { - permanent.moveToExile(null, null,source.getSourceId(), game); - } - return true; - } - - @Override - public FinalJudgmentEffect copy() { - return new FinalJudgmentEffect(this); - } - -} \ No newline at end of file +/* + * 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 mage.sets.betrayersofkamigawa; + +import java.util.UUID; +import mage.abilities.effects.common.ExileAllEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterCreaturePermanent; + +/** + * + * @author Loki + */ +public class FinalJudgment extends CardImpl { + + public FinalJudgment(UUID ownerId) { + super(ownerId, 4, "Final Judgment", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{4}{W}{W}"); + this.expansionSetCode = "BOK"; + + // Exile all creatures. + this.getSpellAbility().addEffect(new ExileAllEffect(new FilterCreaturePermanent())); + } + + public FinalJudgment(final FinalJudgment card) { + super(card); + } + + @Override + public FinalJudgment copy() { + return new FinalJudgment(this); + } +} diff --git a/Mage.Sets/src/mage/sets/gameday/AnguishedUnmaking.java b/Mage.Sets/src/mage/sets/gameday/AnguishedUnmaking.java new file mode 100644 index 00000000000..703e860e096 --- /dev/null +++ b/Mage.Sets/src/mage/sets/gameday/AnguishedUnmaking.java @@ -0,0 +1,52 @@ +/* + * 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 mage.sets.gameday; + +import java.util.UUID; + +/** + * + * @author fireshoes + */ +public class AnguishedUnmaking extends mage.sets.shadowsoverinnistrad.AnguishedUnmaking { + + public AnguishedUnmaking(UUID ownerId) { + super(ownerId); + this.cardNumber = 52; + this.expansionSetCode = "MGDC"; + } + + public AnguishedUnmaking(final AnguishedUnmaking card) { + super(card); + } + + @Override + public AnguishedUnmaking copy() { + return new AnguishedUnmaking(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AngelOfDeliverance.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AngelOfDeliverance.java new file mode 100644 index 00000000000..144720c4481 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AngelOfDeliverance.java @@ -0,0 +1,126 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class AngelOfDeliverance extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature an opponent controls"); + + static { + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public AngelOfDeliverance(UUID ownerId) { + super(ownerId, 2, "Angel of Deliverance", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{6}{W}{W}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Angel"); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Delirium — Whenever Angel of Deliverance deals damage, if there are four or more card types among cards in your graveyard, + // exile target creature an opponent controls. + Ability ability = new ConditionalTriggeredAbility( + new AngelOfDeliveranceDealsDamageTriggeredAbility(), + new DeliriumCondition(), + "Delirium — Whenever {this} deals damage, if there are four or more card types among cards in your graveyard, exile target creature an opponent controls" + ); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); + } + + public AngelOfDeliverance(final AngelOfDeliverance card) { + super(card); + } + + @Override + public AngelOfDeliverance copy() { + return new AngelOfDeliverance(this); + } +} + +class AngelOfDeliveranceDealsDamageTriggeredAbility extends TriggeredAbilityImpl { + + public AngelOfDeliveranceDealsDamageTriggeredAbility() { + super(Zone.BATTLEFIELD, new ExileTargetEffect(), false); + } + + public AngelOfDeliveranceDealsDamageTriggeredAbility(final AngelOfDeliveranceDealsDamageTriggeredAbility ability) { + super(ability); + } + + @Override + public AngelOfDeliveranceDealsDamageTriggeredAbility copy() { + return new AngelOfDeliveranceDealsDamageTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.DAMAGED_PLAYER + || event.getType() == EventType.DAMAGED_CREATURE + || event.getType() == EventType.DAMAGED_PLANESWALKER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getSourceId().equals(this.getSourceId())) { + for (Effect effect : this.getEffects()) { + effect.setValue("damage", event.getAmount()); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AnguishedUnmaking.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AnguishedUnmaking.java new file mode 100644 index 00000000000..f7b2ae24d52 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AnguishedUnmaking.java @@ -0,0 +1,62 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.LoseLifeSourceControllerEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.target.common.TargetNonlandPermanent; + +/** + * + * @author fireshoes + */ +public class AnguishedUnmaking extends CardImpl { + + public AnguishedUnmaking(UUID ownerId) { + super(ownerId, 242, "Anguished Unmaking", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{1}{W}{B}"); + this.expansionSetCode = "SOI"; + + // Exile target nonland permanent. You lose 3 life. + getSpellAbility().addEffect(new ExileTargetEffect()); + getSpellAbility().addTarget(new TargetNonlandPermanent()); + getSpellAbility().addEffect(new LoseLifeSourceControllerEffect(3)); + } + + public AnguishedUnmaking(final AnguishedUnmaking card) { + super(card); + } + + @Override + public AnguishedUnmaking copy() { + return new AnguishedUnmaking(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DescendUponTheSinful.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DescendUponTheSinful.java new file mode 100644 index 00000000000..b960906818a --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DescendUponTheSinful.java @@ -0,0 +1,69 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.ExileAllEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.permanent.token.AngelToken; + +/** + * + * @author fireshoes + */ +public class DescendUponTheSinful extends CardImpl { + + public DescendUponTheSinful(UUID ownerId) { + super(ownerId, 13, "Descend upon the Sinful", Rarity.MYTHIC, new CardType[]{CardType.SORCERY}, "{4}{W}{W}"); + this.expansionSetCode = "SOI"; + + // Exile all creatures + this.getSpellAbility().addEffect(new ExileAllEffect(new FilterCreaturePermanent())); + + // Delirium — Put a 4/4 white Angel creature token with flying onto the battlefield if there are four or more card types among cards in your graveyard. + Effect effect = new ConditionalOneShotEffect(new CreateTokenEffect(new AngelToken()), DeliriumCondition.getInstance()); + effect.setText("
Delirium — Put a 4/4 white Angel creature token with flying onto the battlefield if there are four or more card types among cards in your graveyard"); + this.getSpellAbility().addEffect(effect); + } + + public DescendUponTheSinful(final DescendUponTheSinful card) { + super(card); + } + + @Override + public DescendUponTheSinful copy() { + return new DescendUponTheSinful(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrogskolCavalry.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrogskolCavalry.java new file mode 100644 index 00000000000..e74d3536262 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrogskolCavalry.java @@ -0,0 +1,86 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.game.permanent.token.SpiritWhiteToken; + +/** + * + * @author fireshoes + */ +public class DrogskolCavalry extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("another Spirit"); + + static { + filter.add(new AnotherPredicate()); + filter.add(new SubtypePredicate("Spirit")); + } + + public DrogskolCavalry(UUID ownerId) { + super(ownerId, 15, "Drogskol Cavalry", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{5}{W}{W}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Spirit"); + this.subtype.add("Knight"); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever another Spirit enters the battlefield under your control, you gain 2 life. + this.addAbility(new EntersBattlefieldControlledTriggeredAbility(new GainLifeEffect(2), filter)); + + // {3}{W}: Put a 1/1 white Spirit creature token with flying onto the battlefield. + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new SpiritWhiteToken()), new ManaCostsImpl("{3}{W}"))); + } + + public DrogskolCavalry(final DrogskolCavalry card) { + super(card); + } + + @Override + public DrogskolCavalry copy() { + return new DrogskolCavalry(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NephaliaMoondrakes.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NephaliaMoondrakes.java new file mode 100644 index 00000000000..51bbb29ab4d --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NephaliaMoondrakes.java @@ -0,0 +1,85 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.ExileSourceFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class NephaliaMoondrakes extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Creatures you control"); + + public NephaliaMoondrakes(UUID ownerId) { + super(ownerId, 75, "Nephalia Moondrakes", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{5}{U}{U}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Drake"); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Nephalia Moondrakes enters the battlefield, target creature gains flying until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new GainAbilityTargetEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), false); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + + // {4}{U}{U}, Exile Nephalia Moondrakes from your graveyard: Creatures you control gain flying until end of turn. + ability = new SimpleActivatedAbility(Zone.GRAVEYARD, new GainAbilityControlledEffect(FlyingAbility.getInstance(), Duration.EndOfTurn, filter), new ManaCostsImpl("{4}{U}{U}")); + ability.addCost(new ExileSourceFromGraveCost()); + this.addAbility(ability); + } + + public NephaliaMoondrakes(final NephaliaMoondrakes card) { + super(card); + } + + @Override + public NephaliaMoondrakes copy() { + return new NephaliaMoondrakes(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PiousEvangel.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PiousEvangel.java index 4501a9419fe..de41e3d1a80 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PiousEvangel.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PiousEvangel.java @@ -41,13 +41,11 @@ import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.AnotherPredicate; -import mage.filter.predicate.permanent.ControllerPredicate; import mage.target.common.TargetControlledPermanent; /** @@ -60,7 +58,6 @@ public class PiousEvangel extends CardImpl { private static final FilterControlledPermanent filter2 = new FilterControlledPermanent("another permanent"); static { - filter.add(new ControllerPredicate(TargetController.YOU)); filter2.add(new AnotherPredicate()); } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SinisterConcoction.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SinisterConcoction.java new file mode 100644 index 00000000000..3b973761bc7 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SinisterConcoction.java @@ -0,0 +1,73 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.PutTopCardOfYourLibraryToGraveyardCost; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class SinisterConcoction extends CardImpl { + + public SinisterConcoction(UUID ownerId) { + super(ownerId, 135, "Sinister Concoction", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{B}"); + this.expansionSetCode = "SOI"; + + // {B}, Pay 1 life, Put the top card of your library into your graveyard, Discard a card, Sacrifice Sinister Concoction: Destroy target creature. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DestroyTargetEffect(), new ManaCostsImpl("{B}")); + ability.addCost(new PayLifeCost(1)); + ability.addCost(new PutTopCardOfYourLibraryToGraveyardCost()); + ability.addCost(new DiscardCardCost()); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + } + + public SinisterConcoction(final SinisterConcoction card) { + super(card); + } + + @Override + public SinisterConcoction copy() { + return new SinisterConcoction(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/AngelToken.java b/Mage/src/main/java/mage/game/permanent/token/AngelToken.java index 95a43e34646..a879bb96a90 100644 --- a/Mage/src/main/java/mage/game/permanent/token/AngelToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/AngelToken.java @@ -1,34 +1,34 @@ -package mage.game.permanent.token; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import mage.MageInt; -import mage.abilities.keyword.FlyingAbility; -import mage.constants.CardType; - -public class AngelToken extends Token { - - final static private List tokenImageSets = new ArrayList<>(); - - static { - tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CFX", "GTC", "ISD", "M14", "ORI", "ZEN")); - } - - public AngelToken() { - this(null); - } - - public AngelToken(String setCode) { - super("Angel", "4/4 white Angel creature token with flying"); - availableImageSetCodes = tokenImageSets; - setOriginalExpansionSetCode(setCode); - - cardType.add(CardType.CREATURE); - color.setWhite(true); - subtype.add("Angel"); - power = new MageInt(4); - toughness = new MageInt(4); - addAbility(FlyingAbility.getInstance()); - } -} +package mage.game.permanent.token; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; + +public class AngelToken extends Token { + + final static private List tokenImageSets = new ArrayList<>(); + + static { + tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CFX", "GTC", "ISD", "M14", "ORI", "SOI", "ZEN")); + } + + public AngelToken() { + this(null); + } + + public AngelToken(String setCode) { + super("Angel", "4/4 white Angel creature token with flying"); + availableImageSetCodes = tokenImageSets; + setOriginalExpansionSetCode(setCode); + + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add("Angel"); + power = new MageInt(4); + toughness = new MageInt(4); + addAbility(FlyingAbility.getInstance()); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java b/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java index 75ef08c1855..cf5e5201187 100644 --- a/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/SpiritWhiteToken.java @@ -1,88 +1,88 @@ -/* - * 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 mage.game.permanent.token; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import mage.MageInt; -import mage.abilities.keyword.FlyingAbility; -import mage.constants.CardType; - -/** - * @author nantuko - */ -public class SpiritWhiteToken extends Token { - - final static private List tokenImageSets = new ArrayList<>(); - - static { - tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CNS", "DDC", "DDK", "FRF", "ISD", "KTK", "M15", "MM2", "SHM")); - } - - public SpiritWhiteToken() { - this(null, 0); - } - - public SpiritWhiteToken(String setCode) { - this(setCode, 0); - } - - public SpiritWhiteToken(String setCode, int tokenType) { - super("Spirit", "1/1 white Spirit creature token with flying"); - availableImageSetCodes = tokenImageSets; - setOriginalExpansionSetCode(setCode); - if (tokenType > 0) { - setTokenType(tokenType); - } - cardType.add(CardType.CREATURE); - subtype.add("Spirit"); - color.setWhite(true); - power = new MageInt(1); - toughness = new MageInt(1); - - addAbility(FlyingAbility.getInstance()); - } - - @Override - public void setExpansionSetCodeForImage(String code) { - super.setExpansionSetCodeForImage(code); - if (getOriginalExpansionSetCode() != null && getOriginalExpansionSetCode().equals("AVR")) { - setTokenType(1); - } - } - - public SpiritWhiteToken(final SpiritWhiteToken token) { - super(token); - } - - @Override - public SpiritWhiteToken copy() { - return new SpiritWhiteToken(this); - } -} +/* + * 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 mage.game.permanent.token; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; + +/** + * @author nantuko + */ +public class SpiritWhiteToken extends Token { + + final static private List tokenImageSets = new ArrayList<>(); + + static { + tokenImageSets.addAll(Arrays.asList("AVR", "C14", "CNS", "DDC", "DDK", "FRF", "ISD", "KTK", "M15", "MM2", "SHM", "SOI")); + } + + public SpiritWhiteToken() { + this(null, 0); + } + + public SpiritWhiteToken(String setCode) { + this(setCode, 0); + } + + public SpiritWhiteToken(String setCode, int tokenType) { + super("Spirit", "1/1 white Spirit creature token with flying"); + availableImageSetCodes = tokenImageSets; + setOriginalExpansionSetCode(setCode); + if (tokenType > 0) { + setTokenType(tokenType); + } + cardType.add(CardType.CREATURE); + subtype.add("Spirit"); + color.setWhite(true); + power = new MageInt(1); + toughness = new MageInt(1); + + addAbility(FlyingAbility.getInstance()); + } + + @Override + public void setExpansionSetCodeForImage(String code) { + super.setExpansionSetCodeForImage(code); + if (getOriginalExpansionSetCode() != null && getOriginalExpansionSetCode().equals("AVR")) { + setTokenType(1); + } + } + + public SpiritWhiteToken(final SpiritWhiteToken token) { + super(token); + } + + @Override + public SpiritWhiteToken copy() { + return new SpiritWhiteToken(this); + } +} diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 75d0f3254f1..b34280cb21f 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -56948,6 +56948,7 @@ Descend upon the Sinful|Shadows over Innistrad|13|M|{4}{W}{W}|Sorcery|||Exile al Drogskol Cavalry|Shadows over Innistrad|15|R|{5}{W}{W}|Creature - Spirit Knight|4|4|Flying$Whenever another Spirit enters the battlefield under your control, you gain 2 life.${3}{W}: Put a 1/1 white Spirit creature token with flying onto the battlefield.| Eerie Interlude|Shadows over Innistrad|16|R|{2}{W}|Instant|||Exile any number of target creatures you control. Return those cards to the battlefield under their owner's control at the beginning of the next end step.| Expose Evil|Shadows over Innistrad|19|C|{1}{W}|Instant|||Tap up to two target creatures.$Investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| +Odric, Lunarch Marshal|Shadows over Innistrad|31|R|{3}{W}|Legendary Creature - Human Soldier|3|3|At the beginning of each combat, creatures you control gain first strike until end of turn if a creature you control has first strike. The same is true for flying, deathtouch, double strike, haste, hexproof, indestructible, lifelink, menace, reach, skulk, trample, and vigilance.| Pious Evangel|Shadows over Innistrad|34a|U|{2}{W}|Creature - Human Cleric|2|2|Whenever Pious Evangel or another creature enters the battlefield under your control, you gain 1 life.${2}, {T}, Sacrifice another permanent: Transform Pious Evangel.| Wayward Disciple|Shadows over Innistrad|34b|U||Creature - Human Cleric|2|4|Whenever Wayward Disciple or another creature you control dies, target opponent loses 1 life and you gain 1 life.| Reaper of Flight Moonsilver|Shadows over Innistrad|36|U|{3}{W}{W}|Creature - Angel|3|3|Flying$Delirium — Sacrifice another creature: Reaper of Flight Moonsilver gets +2/+1 until end of turn. Activate this ability only if there are four or more card types among cards in your graveyard.| @@ -56965,8 +56966,9 @@ Nephalia Moondrakes|Shadows over Innistrad|75|R|{5}{U}{U}|Creature - Drake|5|5|F Niblis of Dusk|Shadows over Innistrad|76|C|{2}{U}|Creature - Spirit|2|1|Flying$Prowess| Pieces of the Puzzle|Shadows over Innistrad|78|C|{2}{U}|Sorcery|||Reveal the top five cards of your library. Put up to two instant and/or sorcery cards from among them into your hand and the rest into your graveyard.| Pore Over the Pages|Shadows over Innistrad|79|U|{3}{U}{U}|Sorcery|||Draw three cards, untap up to two lands, then discard a card.| -Startled Awake|Shadows over Innistrad|88a|{2}{U}{U}|Sorcery|||Target opponent puts the top thirteen cards of his or her library into his or her graveyard.${3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery.| -Persistent Nightmare|Shadows over Innistrad|88b||Creature - Nightmare|1|1|Skulk (This creature can't be blocked by creatures with greater power.)$When Persistent Nightmare deals combat damage to a player, return it to its owner's hand.| +Rattlechains|Shadows over Innistrad|81|R|{1}{U}|Creature - Spirit|2|1|Flash$Flying$When Rattlechains enters the battlefield, target spirit gains hexproof until end of turn.$You may cast spirit cards as though they had flash.| +Startled Awake|Shadows over Innistrad|88a|M|{2}{U}{U}|Sorcery|||Target opponent puts the top thirteen cards of his or her library into his or her graveyard.${3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery.| +Persistent Nightmare|Shadows over Innistrad|88b|M||Creature - Nightmare|1|1|Skulk (This creature can't be blocked by creatures with greater power.)$When Persistent Nightmare deals combat damage to a player, return it to its owner's hand.| Stitched Mangler|Shadows over Innistrad|89|C|{2}{U}|Creature - Zombie Horror|2|3|Stitched Mangler enters the battlefield tapped.$When Stitched Mangler enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.| Thing in the Ice|Shadows over Innistrad|92a|R|{1}{U}|Creature - Horror|0|4|Defender$Thing in the Ice enters the battlefield with four ice counters on it.$Whenever you cast an instant or sorcery spell, remove an ice counter from Thing in the Ice. Then if it has no ice counters on it, transform it.| Awoken Horror|Shadows over Innistrad|92b|R||Creature - Kraken Horror|7|8|When this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands.| @@ -56978,25 +56980,29 @@ Farbog Revenant|Shadows over Innistrad|110|C|{2}{B}|Creature - Spirit|1|3|Skulk Heir of Falkenrath|Shadows over Innistrad|116a|U|{1}{B}|Creature - Vampire|2|1|Discard a card: Transform Heir of Falkenrath. Activate this ability only once each turn.| Heir to the Night|Shadows over Innistrad|116b|U||Creature - Vampire Berserker|3|2|Flying| Hound of the Farbogs|Shadows over Innistrad|117|C|{4}{B}|Creature - Zombie Hound|5|3|Delirium — Hound of the Farborgs has menace as long as there are four or more card types among cards in your graveyard. (A creature with menace can't be blocked except by two or more creatures.)| -Markov Dreadknight|Shadows over Innistrad|998|R|{3}{B}{B}|Creature - Vampire Knight|3|3|Flying${2}{B}, Discard a card: Put two +1/+1 counters on Markhov Dreadknight.| +Macabre Waltz|Shadows over Innistrad|121|C|{1}{B}|Sorcery|||Return up to two target creature cards from your graveyard to your hand, then discard a card.| +Markov Dreadknight|Shadows over Innistrad|122|R|{3}{B}{B}|Creature - Vampire Knight|3|3|Flying${2}{B}, Discard a card: Put two +1/+1 counters on Markhov Dreadknight.| Mindwrack Demon|Shadows over Innistrad|124|M|{2}{B}{B}|Creature - Demon|4|5|Flying, trample$When Mindwrack Demon enters the battlefield, put the top four cards of your library into your graveyard.$Delirium — At the beginning of your upkeep, unless there are four or more card types among card in your graveyard, you lose 4 life.| Pick the Brain|Shadows over Innistrad|129|U|{2}{B}|Sorcery|||Target opponent reveals his or her hand. You choose a nonland card from it and exile that card.$Delirium — If there are four or more card types among cards in your graveyard, search that player's graveyard, hand, and library for any number of cards with the same name as the exiled card, exile those cards, then that player shuffles his or her library.| Relentless Dead|Shadows over Innistrad|131|M|{B}{B}|Creature - Zombie|2|2|Menace (This creature can't be blocked except by two or more creatures.)$When Relentless Dead dies, you may pay {B}. If you do, return it to its owner's hand.$When Relentless Dead dies, you may pay {X}. If you do, return another target Zombie creature card with converted mana cost X from your graveyard to the battlefield.| Shamble Back|Shadows over Innistrad|134|C|{B}|Sorcery|||Exile target creature card from a graveyard. Put a 2/2 black Zombie creature token onto the battlefield. You gain 2 life.| Sinister Concoction|Shadows over Innistrad|135|U|{B}|Enchantment|||{B}, Pay 1 life, Put the top card of your library into your graveyard, Discard a card, Sacrifice Sinister Concoction: Destroy target creature.| +To the Slaughter|Shadows over Innistrad|139|R|{2}{B}|Instant|||Target player sacrifices a creature or planeswalker.$Delirium — If there are four or more card types among cards in your graveyard, instead that player sacrifices a creature and a planeswalker.| Tooth Collector|Shadows over Innistrad|140|U|{2}{B}|Creature - Human Rogue|3|2|When Tooth Collector enters the battlefield, target creature an opponent controls gets -1/-1 until end of turn.${Delirium — At the beginning of each opponent's upkeep, if there are four or more card types among cards in your graveyard, target creature that player controls gets -1/-1 until end of turn.| Dance with Devils|Shadows over Innistrad|150|U|{3}{R}|Instant|||Put two 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player."| Devil's Playground|Shadows over Innistrad|151|R|{4}{R}{R}|Sorcery|||Put four 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player."| Ember-Eye Wolf|Shadows over Innistrad|154|C|{2}{R}|Creature - Wolf|1|2|Haste${1}{R}: Ember-Eye Wolf gets +2/+0 until end of turn.| +Falkenrath Gorger|Shadows over Innistrad|155|R|{R}|Creature - Vampire Warrior|2|1|Each Vampire creature card you own that isn't on the battlefield has madness. Its madness cost is equal to its mana cost. (If you discard a card with madness, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| Fiery Temper|Shadows over Innistrad|156|C|{1}{R}{R}|Instant|||Fiery Temper deals 3 damage to target creature or player.$Madness {R} (If you discard this card, you may play it for its madness cost instead of putting it into your graveyard.)| Incorrigible Youths|Shadows over Innistrad|166|U|{3}{R}{R}|Creature - Vampire|4|3|Haste$Madness {2}{R} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| -Flameblade Angel|Shadows over Innistrad|997|R|{4}{R}{R}|Creature - Angel|4|4|Flying$Whenever a source an opponent controls deals damage to you or a permanent you control, you may have Flameblade Angel deal 1 damage to that source's controller.| +Flameblade Angel|Shadows over Innistrad|157|R|{4}{R}{R}|Creature - Angel|4|4|Flying$Whenever a source an opponent controls deals damage to you or a permanent you control, you may have Flameblade Angel deal 1 damage to that source's controller.| Lightning Axe|Shadows over Innistrad|999|U|{R}|Instant|||As an additional cost to cast Lightning Axe, discard a card or pay {5}.$Lightning Axe deals 5 damage to target creature.| Magmatic Chasm|Shadows over Innistrad|172|C|{1}{R}|Sorcery|||Creatures without flying can't block this turn.| Ravenous Bloodseeker|Shadows over Innistrad|175|U|{1}{R}|Creature - Vampire Berserker|1|3|Discard a card: Ravenous Bloodseeker gets +2/-2 until end of turn.| Sanguinary Mage|Shadows over Innistrad|178|C|{1}{R}|Creature - Vampire Wizard|1|3|Prowess| Structural Distortion|Shadows over Innistrad|185|C|{3}{R}|Sorcery|||Exile target artifact or land. Structural Distortion deals 2 damage to that permanent's controller.| Voldaren Duelist|Shadows over Innistrad|191|C|{3}{R}|Creature - Vampire Warrior|3|2|Haste$When Voldaren Duelist enters the battlefield, target creature can't block this turn.| +Wolf of Devil's Breach|Shadows over Innistrad|192|M|{3}{R}{R}|Creature - Elemental Wolf|5|5|Whenever Wolf of Devil's Breach attacks, you may pay {1}{R} and discard a card. If you do, Wolf of Devil's Breach deals damage to target creature or planeswalker equal to the discarded card's converted mana cost.| Clip Wings|Shadows over Innistrad|197|C|{1}{G}|Instant|||Each opponent sacrifices a creature with flying.| Duskwatch Recruiter|Shadows over Innistrad|203a|U|{1}{G}|Creature - Human Warrior Werewolf|2|2|{2}{G}: Look at the top three cards of your library. You may reveal a creature card from among them and put it into your hand. Put the rest on the bottom of your library in any order.$At the beginning of each upkeep, if no spells were cast last turn, transform Duskwatch Recruiter.| Krallenhorde Howler|Shadows over Innistrad|203b|U||Creature - Werewolf|3|3|Creature spells you cast cost {1} less to cast.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Krallenhorde Howler.| @@ -57004,14 +57010,20 @@ Hinterland Logger|Shadows over Innistrad|210a|C|{1}{G}|Creature - Human Werewolf Timber Shredder|Shadows over Innistrad|210b|C||Creature - Werewolf|4|2|Trample$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Timber Shredder.| Pack Guardian|Shadows over Innistrad|221|U|{2}{G}{G}|Creature - Wolf Spirit|4|3|Flash$When Pack Guardian enters the battlefield, you may discard a land card. If you do, put a 2/2 green Wolf creature token onto the battlefield.| Quilled Wolf|Shadows over Innistrad|222|C|{1}{G}|Creature - Wolf|2|2|{5}{G}: Quilled Wolf gets +4/+4 until end of turn.| +Sage of Ancient Lore|Shadows over Innistrad|225a|R|{4}{G}|Creature - Human Shaman Werewolf|0|0|Sage of Ancient Lore's power and toughness are each equal to the number of cards in your hand.$When Sage of Ancient Lore enters the battlefield, draw a card.$At the beginning of each upkeep, if no spells were cast last turn, transform Sage of Ancient Lore.| +Werewolf of Ancient Hunger|Shadows over Innistrad|225b|R||Creature - Werewolf|0|0|Vigilance, trample$Werewolf of Ancient Hunger's power and toughness are each equal to the total number of cards in all players' hands.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Werewolf of Ancient Hunger.| Soul Swallower|Shadows over Innistrad|230|R|{2}{G}{G}|Creature - Wurm|3|3|Trample$Delirium — At the beginning of your upkeep, if there are four or more card types among cards in your graveyard, put three +1/+1 counters on Soul Swallower.| Stoic Builder|Shadows over Innistrad|231|C|{2}{G}|Creature - Human|2|3|When Stoic Builder enters the battlefield, you may return target land card from your graveyard to your hand.| Watcher in the Web|Shadows over Innistrad|239|C|{4}{G}|Creature - Spider|2|5|Reach (This creature can block creature with flying.)$Watcher in the Web can block an additional seven creatures each combat.| Anguished Unmaking|Shadows over Innistrad|242|R|{1}{W}{B}|Instant|||Exile target nonland permanent. You lose 3 life.| -Nahiri, the Harbinger|Shadows over Innistrad|247|M|{2}{R}{W}|Planeswalker - Nahiri|||+2: You may discard a card. If you do, draw a card.$-2: Exile target enchantment, tapped artifact, or tapped creature.$-8 Search your library for an artifact or creature card, put it onto the battlefield, then shuffle your library. It gains haste. Return it to your hand at the beginning of the next end step.| +Arlinn Kord|Shadows over Innistrad|243a|M|{2}{R}{G}|Planeswalker - Arlinn|||+1: Until end of turn, up to one target creature gets +2/+2 and gains vigilance and haste.$0: Put a 2/2 green Wolf creature token onto the battlefield. Transform Arlinn Kord.| +Arlinn, Embraced by the Moon|Shadows over Innistrad|243b|M||Planeswalker - Arlinn|||+1: Creatures you control get +1/+1 and gain trample until end of turn.$-1: Arlinn, Embraced by the Moon deals 3 damage to target creature or player. Transform Arlinn, Embraced by the Moon.$-6: You get an emblem with "Creatures you control have haste and '{T}: This creature deals damage equal to its power to target creature or player.'"| +Nahiri, the Harbinger|Shadows over Innistrad|247|M|{2}{R}{W}|Planeswalker - Nahiri|||+2: You may discard a card. If you do, draw a card.$-2: Exile target enchantment, tapped artifact, or tapped creature.$-8: Search your library for an artifact or creature card, put it onto the battlefield, then shuffle your library. It gains haste. Return it to your hand at the beginning of the next end step.| Brain in a Jar|Shadows over Innistrad|252|R|{2}|Artifact|||{1}, {T}: Put a charge counter on Brain in a Jar, then you may cast an instant or sorcery card with converted mana costs equal to the number of charge counters on Brain in a Jar from your hand without paying its mana cost.${3}< {T}, Remove X charge counters from Brain in a Jar: Scry X.| Explosive Apparatus|Shadows over Innistrad|255|C|{1}|Artifact|||{3}, {T}, Sacrifice Explosive Apparatus: Explosive Apparatus deals 2 damage to target creature or player.| Magnifying Glass|Shadows over Innistrad|258|U|{3}|Artifact|||{T}: Add {C} to your mana pool.${4}, {T}: Investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| +Neglected Heirloom|Shadows over Innistrad|260a|U|{1}|Artifact - Equipment|||Equipped creature gets +1/+1.$When equipped creature transforms, transform Neglected Heirloom.$Equip {1}| +Ashmouth Blade|Shadows over Innistrad|260b|U||Artifact - Equipment|||Equipped creature gets +3/+3.$Equip {3}| Shard of Broken Glass|Shadows over Innistrad|262|C|{1}|Artifact - Equipment|||Equipped creature gets +1/+0.$Whenever equipped creature attacks, you may put the top two cards of your library into your graveyard.$Equip {1} ({1}: Attach to target creature you control. Equip only as a sorcery.)| Tamiyo's Journal|Shadows over Innistrad|265|R|{5}|Legendary Artifact|||At the beginning of your upkeep, investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")${T}, Sacrifice three Clues: Search your library for a card and put that card into your hand. Then shuffle your library.| Forsaken Sanctuary|Shadows over Innistrad|273|U||Land|||Forsaken Sanctuary enters the battlefield tapped.${T}: Add {W} or {B} to your mana pool.| From fb077baeeb4e29ed808ffcfbf7705686c3ccd41a Mon Sep 17 00:00:00 2001 From: fireshoes Date: Tue, 15 Mar 2016 18:41:16 -0500 Subject: [PATCH 23/44] [SOI] Added Nahiri, the Harbinger and Macabre Waltz reprint. --- .../shadowsoverinnistrad/MacabreWaltz.java | 52 ++++++ .../NahiriTheHarbinger.java | 159 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/MacabreWaltz.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/NahiriTheHarbinger.java diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/MacabreWaltz.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/MacabreWaltz.java new file mode 100644 index 00000000000..bee45a225d9 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/MacabreWaltz.java @@ -0,0 +1,52 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; + +/** + * + * @author fireshoes + */ +public class MacabreWaltz extends mage.sets.magicorigins.MacabreWaltz { + + public MacabreWaltz(UUID ownerId) { + super(ownerId); + this.cardNumber = 121; + this.expansionSetCode = "SOI"; + } + + public MacabreWaltz(final MacabreWaltz card) { + super(card); + } + + @Override + public MacabreWaltz copy() { + return new MacabreWaltz(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NahiriTheHarbinger.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NahiriTheHarbinger.java new file mode 100644 index 00000000000..bf83929e35a --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NahiriTheHarbinger.java @@ -0,0 +1,159 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlanswalkerEntersWithLoyalityCountersAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.SearchEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInLibrary; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author fireshoes + */ +public class NahiriTheHarbinger extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("enchantment, tapped artifact, or tapped creature"); + + static { + filter.add(Predicates.or(new CardTypePredicate(CardType.ENCHANTMENT), + (Predicates.and(new CardTypePredicate(CardType.ARTIFACT), + new TappedPredicate())), + (Predicates.and(new CardTypePredicate(CardType.CREATURE), + new TappedPredicate())))); + } + + public NahiriTheHarbinger(UUID ownerId) { + super(ownerId, 247, "Nahiri, the Harbinger", Rarity.MYTHIC, new CardType[]{CardType.PLANESWALKER}, "{2}{R}{W}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Nahiri"); + + this.addAbility(new PlanswalkerEntersWithLoyalityCountersAbility(4)); + + // +2: You may discard a card. If you do, draw a card. + this.addAbility(new LoyaltyAbility(new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new DiscardCardCost()), 2)); + + // -2: Exile target enchantment, tapped artifact, or tapped creature. + LoyaltyAbility ability = new LoyaltyAbility(new ExileTargetEffect(), -2); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + + // -8: Search your library for an artifact or creature card, put it onto the battlefield, then shuffle your library. It gains haste. + // Return it to your hand at the beginning of the next end step. + this.addAbility(new LoyaltyAbility(new NahiriTheHarbingerEffect(), -8)); + } + + public NahiriTheHarbinger(final NahiriTheHarbinger card) { + super(card); + } + + @Override + public NahiriTheHarbinger copy() { + return new NahiriTheHarbinger(this); + } +} + +class NahiriTheHarbingerEffect extends SearchEffect { + + private static final FilterCard filterCard = new FilterCard("artifact or creature card"); + + static { + filterCard.add(Predicates.or(new CardTypePredicate(CardType.ARTIFACT), + new CardTypePredicate(CardType.CREATURE))); + } + + NahiriTheHarbingerEffect() { + super(new TargetCardInLibrary(0, 1, filterCard), Outcome.PutCardInPlay); + this.staticText = "Search your library for an artifact or creature card, put it onto the battlefield, then shuffle your library. It gains haste. Return it to your hand at the beginning of the next end step"; + } + + NahiriTheHarbingerEffect(final NahiriTheHarbingerEffect effect) { + super(effect); + } + + @Override + public NahiriTheHarbingerEffect copy() { + return new NahiriTheHarbingerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + if (controller.searchLibrary(target, game)) { + if (target.getTargets().size() > 0) { + Card card = controller.getLibrary().getCard(target.getFirstTarget(), game); + controller.moveCards(card, Zone.BATTLEFIELD, source, game); + Permanent permanent = game.getPermanent(card.getId()); + if (permanent != null) { + ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom); + effect.setTargetPointer(new FixedTarget(permanent.getId(), permanent.getZoneChangeCounter(game))); + game.addEffect(effect, source); + Effect effect2 = new ReturnToHandTargetEffect(); + effect2.setTargetPointer(new FixedTarget(permanent.getId(), permanent.getZoneChangeCounter(game))); + DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect2); + game.addDelayedTriggeredAbility(delayedAbility, source); + } + } + controller.shuffleLibrary(source, game); + } + return true; + } + return false; + } +} From 32c014f2f8dc4a670668dabfafc31a507b9d8d6d Mon Sep 17 00:00:00 2001 From: fireshoes Date: Tue, 15 Mar 2016 22:33:37 -0500 Subject: [PATCH 24/44] [SOI] Added Arlinn Kord, To the Slaughter, Sage of Ancient Lore, Rattlechains, and Wolf of Devil's Breach. --- .../ArlinnEmbracedByTheMoon.java | 121 ++++++++++++++++++ .../sets/shadowsoverinnistrad/ArlinnKord.java | 92 +++++++++++++ .../shadowsoverinnistrad/Rattlechains.java | 94 ++++++++++++++ .../SageOfAncientLore.java | 90 +++++++++++++ .../shadowsoverinnistrad/ToTheSlaughter.java | 78 +++++++++++ .../WerewolfOfAncientHunger.java | 88 +++++++++++++ .../WolfOfDevilsBreach.java | 76 +++++++++++ 7 files changed, 639 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnEmbracedByTheMoon.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnKord.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/Rattlechains.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/SageOfAncientLore.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/ToTheSlaughter.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/WolfOfDevilsBreach.java diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnEmbracedByTheMoon.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnEmbracedByTheMoon.java new file mode 100644 index 00000000000..15c80e19096 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnEmbracedByTheMoon.java @@ -0,0 +1,121 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.GetEmblemEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.command.Emblem; +import mage.target.common.TargetCreatureOrPlayer; + +/** + * + * @author fireshoes + */ +public class ArlinnEmbracedByTheMoon extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Creatures you control"); + + public ArlinnEmbracedByTheMoon(UUID ownerId) { + super(ownerId, 243, "Arlinn, Embraced by the Moon", Rarity.MYTHIC, new CardType[]{CardType.PLANESWALKER}, ""); + this.expansionSetCode = "SOI"; + this.subtype.add("Arlinn"); + + this.nightCard = true; + this.canTransform = true; + + // +1: Creatures you control get +1/+1 and gain trample until end of turn. + Effect effect = new BoostControlledEffect(1, 1, Duration.EndOfTurn, filter); + effect.setText("Creatures you control get +1/+1"); + LoyaltyAbility ability = new LoyaltyAbility(effect, 1); + effect = new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.EndOfTurn, filter); + effect.setText("and gain trample until end of turn"); + ability.addEffect(effect); + this.addAbility(ability); + + // -1: Arlinn, Embraced by the Moon deals 3 damage to target creature or player. Transform Arlinn, Embraced by the Moon. + this.addAbility(new TransformAbility()); + ability = new LoyaltyAbility(new DamageTargetEffect(3), -1); + ability.addTarget(new TargetCreatureOrPlayer()); + ability.addEffect(new TransformSourceEffect(false)); + this.addAbility(ability); + + // -6: You get an emblem with "Creatures you control have haste and '{T}: This creature deals damage equal to its power to target creature or player.'" + this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new ArlinnEmbracedByTheMoonEmblem()), -6)); + } + + public ArlinnEmbracedByTheMoon(final ArlinnEmbracedByTheMoon card) { + super(card); + } + + @Override + public ArlinnEmbracedByTheMoon copy() { + return new ArlinnEmbracedByTheMoon(this); + } +} + +class ArlinnEmbracedByTheMoonEmblem extends Emblem { + + // "Creatures you control have haste and '{T}: This creature deals damage equal to its power to target creature or player.'" + public ArlinnEmbracedByTheMoonEmblem() { + this.setName("EMBLEM: Arlinn, Embraced by the Moon"); + FilterPermanent filter = new FilterControlledCreaturePermanent("Creatures"); + GainAbilityControlledEffect effect = new GainAbilityControlledEffect(HasteAbility.getInstance(), Duration.EndOfGame, filter); + effect.setText("Creatures you control have haste"); + Ability ability = new SimpleStaticAbility(Zone.COMMAND, effect); + Effect effect2 = new DamageTargetEffect(new SourcePermanentPowerCount()); + effect2.setText("This creature deals damage equal to its power to target creature or player"); + Ability ability2 = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect2, new TapSourceCost()); + ability2.addTarget(new TargetCreatureOrPlayer()); + effect = new GainAbilityControlledEffect(ability2, Duration.EndOfGame, filter); + effect.setText("and '{T}: This creature deals damage equal to its power to target creature or player"); + ability.addEffect(effect); + this.getAbilities().add(ability); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnKord.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnKord.java new file mode 100644 index 00000000000..c04e88b44a5 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnKord.java @@ -0,0 +1,92 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlanswalkerEntersWithLoyalityCountersAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TransformAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.game.permanent.token.WolfToken; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class ArlinnKord extends CardImpl { + + public ArlinnKord(UUID ownerId) { + super(ownerId, 243, "Arlinn Kord", Rarity.MYTHIC, new CardType[]{CardType.PLANESWALKER}, "{2}{R}{G}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Arlinn"); + + this.canTransform = true; + this.secondSideCard = new ArlinnEmbracedByTheMoon(ownerId); + + this.addAbility(new PlanswalkerEntersWithLoyalityCountersAbility(3)); + + // +1: Until end of turn, up to one target creature gets +2/+2 and gains vigilance and haste. + Effect effect = new BoostTargetEffect(2, 2, Duration.EndOfTurn); + effect.setText("Until end of turn, up to one target creature gets +2/+2"); + LoyaltyAbility ability = new LoyaltyAbility(effect, 1); + effect = new GainAbilityTargetEffect(VigilanceAbility.getInstance(), Duration.EndOfTurn); + effect.setText("and gains vigilance"); + ability.addEffect(effect); + effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); + effect.setText("and haste"); + ability.addEffect(effect); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // 0: Put a 2/2 green Wolf creature token onto the battlefield. Transform Arlinn Kord. + this.addAbility(new TransformAbility()); + ability = new LoyaltyAbility(new CreateTokenEffect(new WolfToken()), 0); + ability.addEffect(new TransformSourceEffect(true)); + this.addAbility(ability); + } + + public ArlinnKord(final ArlinnKord card) { + super(card); + } + + @Override + public ArlinnKord copy() { + return new ArlinnKord(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/Rattlechains.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/Rattlechains.java new file mode 100644 index 00000000000..38096868b17 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/Rattlechains.java @@ -0,0 +1,94 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class Rattlechains extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("spirit"); + private static final FilterCard filterCard = new FilterCard("spirit cards"); + + static { + filter.add(new SubtypePredicate("Spirit")); + filterCard.add(new SubtypePredicate("Spirit")); + } + + public Rattlechains(UUID ownerId) { + super(ownerId, 81, "Rattlechains", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{1}{U}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Spirit"); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Rattlechains enters the battlefield, target spirit gains hexproof until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new GainAbilityTargetEffect(HexproofAbility.getInstance(), Duration.EndOfTurn), false); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); + + // You may cast spirit cards as though they had flash. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CastAsThoughItHadFlashAllEffect(Duration.WhileOnBattlefield, filterCard, false))); + } + + public Rattlechains(final Rattlechains card) { + super(card); + } + + @Override + public Rattlechains copy() { + return new Rattlechains(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SageOfAncientLore.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SageOfAncientLore.java new file mode 100644 index 00000000000..b65d7d8d3f2 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SageOfAncientLore.java @@ -0,0 +1,90 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.NoSpellsWereCastLastTurnCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.constants.Zone; + +/** + * + * @author fireshoes + */ +public class SageOfAncientLore extends CardImpl { + + public SageOfAncientLore(UUID ownerId) { + super(ownerId, 225, "Sage of Ancient Lore", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{4}{G}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Human"); + this.subtype.add("Shaman"); + this.subtype.add("Werewolf"); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + this.canTransform = true; + this.secondSideCard = new WerewolfOfAncientHunger(ownerId); + + // Sage of Ancient Lore's power and toughness are each equal to the number of cards in your hand. + DynamicValue xValue= new CardsInControllerHandCount(); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetPowerToughnessSourceEffect(xValue, Duration.EndOfGame))); + + // When Sage of Ancient Lore enters the battlefield, draw a card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1), false)); + + // At the beginning of each upkeep, if no spells were cast last turn, transform Sage of Ancient Lore. + this.addAbility(new TransformAbility()); + TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(new TransformSourceEffect(true), TargetController.ANY, false); + this.addAbility(new ConditionalTriggeredAbility(ability, NoSpellsWereCastLastTurnCondition.getInstance(), TransformAbility.NO_SPELLS_TRANSFORM_RULE)); + } + + public SageOfAncientLore(final SageOfAncientLore card) { + super(card); + } + + @Override + public SageOfAncientLore copy() { + return new SageOfAncientLore(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ToTheSlaughter.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ToTheSlaughter.java new file mode 100644 index 00000000000..636ecb8adce --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ToTheSlaughter.java @@ -0,0 +1,78 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.condition.InvertCondition; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.SacrificeEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.common.FilterPlaneswalkerPermanent; +import mage.target.TargetPlayer; + +/** + * + * @author fireshoes + */ +public class ToTheSlaughter extends CardImpl { + + public ToTheSlaughter(UUID ownerId) { + super(ownerId, 139, "To the Slaughter", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{2}{B}"); + this.expansionSetCode = "SOI"; + + // Target player sacrifices a creature or planeswalker. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SacrificeEffect(new FilterCreatureOrPlaneswalkerPermanent(), 1, "Target player"), + new InvertCondition(DeliriumCondition.getInstance()), + "Target player sacrifices a creature or planeswalker.")); + + // Delirium — If there are four or more card types among cards in your graveyard, instead that player sacrifices a creature and a planeswalker. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SacrificeEffect(new FilterCreaturePermanent(), 1, "Target player"), + DeliriumCondition.getInstance(), + "
Delirium — If there are four or more card types among cards in your graveyard, instead that player sacrifices a creature")); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SacrificeEffect(new FilterPlaneswalkerPermanent(), 1, "Target player"), + DeliriumCondition.getInstance(), "and a planeswalker.")); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + public ToTheSlaughter(final ToTheSlaughter card) { + super(card); + } + + @Override + public ToTheSlaughter copy() { + return new ToTheSlaughter(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java new file mode 100644 index 00000000000..fe2b0437686 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java @@ -0,0 +1,88 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.TwoOrMoreSpellsWereCastLastTurnCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.dynamicvalue.common.CardsInAllHandsCount; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.TransformAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.constants.Zone; + +/** + * + * @author fireshoes + */ +public class WerewolfOfAncientHunger extends CardImpl { + + public WerewolfOfAncientHunger(UUID ownerId) { + super(ownerId, 225, "Werewolf of Ancient Hunger", Rarity.RARE, new CardType[]{CardType.CREATURE}, ""); + this.expansionSetCode = "SOI"; + this.subtype.add("Werewolf"); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + this.nightCard = true; + this.canTransform = true; + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Werewolf of Ancient Hunger's power and toughness are each equal to the total number of cards in all players' hands. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetPowerToughnessSourceEffect(new CardsInAllHandsCount(), Duration.EndOfGame))); + + // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Werewolf of Ancient Hunger. + TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(new TransformSourceEffect(false), TargetController.ANY, false); + this.addAbility(new ConditionalTriggeredAbility(ability, TwoOrMoreSpellsWereCastLastTurnCondition.getInstance(), TransformAbility.TWO_OR_MORE_SPELLS_TRANSFORM_RULE)); + } + + public WerewolfOfAncientHunger(final WerewolfOfAncientHunger card) { + super(card); + } + + @Override + public WerewolfOfAncientHunger copy() { + return new WerewolfOfAncientHunger(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WolfOfDevilsBreach.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WolfOfDevilsBreach.java new file mode 100644 index 00000000000..dd1f5d9129c --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WolfOfDevilsBreach.java @@ -0,0 +1,76 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.common.DiscardCostCardConvertedMana; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.target.common.TargetCreatureOrPlaneswalker; + +/** + * + * @author fireshoes + */ +public class WolfOfDevilsBreach extends CardImpl { + + public WolfOfDevilsBreach(UUID ownerId) { + super(ownerId, 192, "Wolf of Devil's Breach", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Elemental"); + this.subtype.add("Wolf"); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Whenever Wolf of Devil's Breach attacks, you may pay {1}{R} and discard a card. If you do, Wolf of Devil's Breach deals + // damage to target creature or planeswalker equal to the discarded card's converted mana cost. + Ability ability = new AttacksTriggeredAbility(new DoIfCostPaid(new DamageTargetEffect(new DiscardCostCardConvertedMana()), new ManaCostsImpl("{1}{R}")), false, + "Whenever {this} attacks you may pay {1}{R} and discard a card. If you do, {this} deals damage to target creature or planeswalker " + + "equal to the discarded card's converted mana cost."); + ability.addCost(new DiscardCardCost()); + ability.addTarget(new TargetCreatureOrPlaneswalker()); + this.addAbility(ability); + } + + public WolfOfDevilsBreach(final WolfOfDevilsBreach card) { + super(card); + } + + @Override + public WolfOfDevilsBreach copy() { + return new WolfOfDevilsBreach(this); + } +} From e0625f20367518878c14ebd7ae0b0ee63be970a5 Mon Sep 17 00:00:00 2001 From: okuRaku Date: Tue, 15 Mar 2016 23:06:08 -0500 Subject: [PATCH 25/44] update per pull request feedback --- .../common/DontUntapInOpponentsNextUntapStepAllEffect.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java index be645f86abf..2a20c30e913 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java @@ -121,6 +121,9 @@ public class DontUntapInOpponentsNextUntapStepAllEffect extends ContinuousRuleMo Permanent permanent = game.getPermanent(event.getTargetId()); if (permanent != null) { Player controller = game.getPlayer(source.getControllerId()); + if (!permanent.getControllerId().equals(getTargetPointer().getFirst(game, source))) { + return false; + } if (controller != null && !game.isOpponent(controller, permanent.getControllerId())) { return false; } From a482bf2a0f7814ad62a0f877ae6d8c4c4f74a23c Mon Sep 17 00:00:00 2001 From: okuRaku Date: Mon, 14 Mar 2016 23:23:01 -0500 Subject: [PATCH 26/44] fix for Exhaustion on things that entered after resolution --- .../sets/portalthreekingdoms/Exhaustion.java | 16 +- ...ntapInOpponentsNextUntapStepAllEffect.java | 143 ++++++++++++++++++ 2 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java diff --git a/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java b/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java index 6156526574e..2f6f38fa629 100644 --- a/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java +++ b/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java @@ -33,7 +33,7 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.DontUntapInOpponentsNextUntapStepAllEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Outcome; @@ -42,7 +42,6 @@ import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; import mage.game.Game; -import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetOpponent; import mage.target.targetpointer.FixedTargets; @@ -97,16 +96,11 @@ class ExhaustionEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getFirstTarget()); + if (player != null) { - List doNotUntapNextUntapStep = new ArrayList<>(); - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, player.getId(), game)) { - doNotUntapNextUntapStep.add(permanent); - } - if (!doNotUntapNextUntapStep.isEmpty()) { - ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect("This creature"); - effect.setTargetPointer(new FixedTargets(doNotUntapNextUntapStep, game)); - game.addEffect(effect, source); - } + ContinuousEffect effect = new DontUntapInOpponentsNextUntapStepAllEffect(filter); + effect.setTargetPointer(new FixedTarget(player.getId())); + game.addEffect(effect, source); return true; } return false; diff --git a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java new file mode 100644 index 00000000000..be645f86abf --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java @@ -0,0 +1,143 @@ +/* + * 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 mage.abilities.effects.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.PhaseStep; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * @author okuRaku + */ +public class DontUntapInOpponentsNextUntapStepAllEffect extends ContinuousRuleModifyingEffectImpl { + + private int validForTurnNum; + //private String targetName; + TargetController targetController; + FilterPermanent filter; + + /** + * Attention: This effect won't work with targets controlled by different + * controllers If this is needed, the validForTurnNum has to be saved per + * controller. + */ + public DontUntapInOpponentsNextUntapStepAllEffect(FilterPermanent filter) { + super(Duration.Custom, Outcome.Detriment, false, true); + this.filter = filter; + } + + public DontUntapInOpponentsNextUntapStepAllEffect(final DontUntapInOpponentsNextUntapStepAllEffect effect) { + super(effect); + this.validForTurnNum = effect.validForTurnNum; + this.targetController = effect.targetController; + this.filter = effect.filter; + + } + + @Override + public DontUntapInOpponentsNextUntapStepAllEffect copy() { + return new DontUntapInOpponentsNextUntapStepAllEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public String getInfoMessage(Ability source, GameEvent event, Game game) { + MageObject mageObject = game.getObject(source.getSourceId()); + Permanent permanentToUntap = game.getPermanent((event.getTargetId())); + if (permanentToUntap != null && mageObject != null) { + return permanentToUntap.getLogName() + " doesn't untap (" + mageObject.getLogName() + ")"; + } + return null; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == EventType.UNTAP_STEP || event.getType() == EventType.UNTAP; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + // the check for turn number is needed if multiple effects are added to prevent untap in next untap step of controller + // if we don't check for turn number, every untap step of a turn only one effect would be used instead of correctly only one time + // to skip the untap effect. + + // Discard effect if it's related to a previous turn + if (validForTurnNum > 0 && validForTurnNum < game.getTurnNum()) { + discard(); + return false; + } + // remember the turn of the untap step the effect has to be applied + if (GameEvent.EventType.UNTAP_STEP.equals(event.getType())) { + if (game.getActivePlayerId().equals(getTargetPointer().getFirst(game, source))) { + if (validForTurnNum == game.getTurnNum()) { // the turn has a second untap step but the effect is already related to the first untap step + discard(); + return false; + } + validForTurnNum = game.getTurnNum(); + } + } + + if (game.getTurn().getStepType() == PhaseStep.UNTAP && event.getType() == EventType.UNTAP) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null && !game.isOpponent(controller, permanent.getControllerId())) { + return false; + } + if (game.getActivePlayerId().equals(permanent.getControllerId()) && // controller's untap step + filter.match(permanent, source.getSourceId(), source.getControllerId(), game)) { + return true; + } + } + } + return false; + } + + @Override + public String getText(Mode mode) { + if (!staticText.isEmpty()) { + return staticText; + } + return filter.getMessage() + " target opponent controls don't untap during his or her next untap step."; + } +} From 11da32823145863f907d0defa9c033a6fbfde716 Mon Sep 17 00:00:00 2001 From: okuRaku Date: Tue, 15 Mar 2016 23:06:08 -0500 Subject: [PATCH 27/44] update per pull request feedback --- .../common/DontUntapInOpponentsNextUntapStepAllEffect.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java index be645f86abf..2a20c30e913 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DontUntapInOpponentsNextUntapStepAllEffect.java @@ -121,6 +121,9 @@ public class DontUntapInOpponentsNextUntapStepAllEffect extends ContinuousRuleMo Permanent permanent = game.getPermanent(event.getTargetId()); if (permanent != null) { Player controller = game.getPlayer(source.getControllerId()); + if (!permanent.getControllerId().equals(getTargetPointer().getFirst(game, source))) { + return false; + } if (controller != null && !game.isOpponent(controller, permanent.getControllerId())) { return false; } From 867fe61007133247e3be797e9884d655f6cee482 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 16 Mar 2016 10:12:56 +0100 Subject: [PATCH 28/44] * Leeching Sliver - Fixed that all defending players of combat lost life instead of only the defending player of Leeching Sliver. --- .../mage/sets/alarareborn/VedalkenGhoul.java | 54 ++----------------- .../sets/battleforzendikar/SilentSkimmer.java | 6 +-- .../mage/sets/magic2015/LeechingSliver.java | 5 +- .../common/LoseLifeDefendingPlayerEffect.java | 45 +++++++++------- 4 files changed, 32 insertions(+), 78 deletions(-) diff --git a/Mage.Sets/src/mage/sets/alarareborn/VedalkenGhoul.java b/Mage.Sets/src/mage/sets/alarareborn/VedalkenGhoul.java index ddd5bc56763..9c1ede5ca65 100644 --- a/Mage.Sets/src/mage/sets/alarareborn/VedalkenGhoul.java +++ b/Mage.Sets/src/mage/sets/alarareborn/VedalkenGhoul.java @@ -29,17 +29,11 @@ package mage.sets.alarareborn; import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.common.BecomesBlockedTriggeredAbility; +import mage.abilities.effects.common.LoseLifeDefendingPlayerEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.target.targetpointer.FixedTarget; /** * @@ -53,13 +47,11 @@ public class VedalkenGhoul extends CardImpl { this.subtype.add("Vedalken"); this.subtype.add("Zombie"); - - this.power = new MageInt(1); this.toughness = new MageInt(1); // Whenever Vedalken Ghoul becomes blocked, defending player loses 4 life. - this.addAbility(new VedalkenGhoulTriggeredAbility()); + this.addAbility(new BecomesBlockedTriggeredAbility(new LoseLifeDefendingPlayerEffect(4, true), false)); } @@ -72,43 +64,3 @@ public class VedalkenGhoul extends CardImpl { return new VedalkenGhoul(this); } } - -class VedalkenGhoulTriggeredAbility extends TriggeredAbilityImpl { - - public VedalkenGhoulTriggeredAbility() { - super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(4), false); - } - - public VedalkenGhoulTriggeredAbility(final VedalkenGhoulTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == EventType.CREATURE_BLOCKED; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getTargetId().equals(this.getSourceId())) { - UUID defendingPlayer = game.getCombat().getDefenderId(this.getSourceId()); - if (defendingPlayer != null) { - for (Effect effect : this.getEffects()) { - effect.setTargetPointer(new FixedTarget(defendingPlayer)); - } - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever {this} becomes blocked, defending player loses 4 life."; - } - - @Override - public VedalkenGhoulTriggeredAbility copy() { - return new VedalkenGhoulTriggeredAbility(this); - } -} diff --git a/Mage.Sets/src/mage/sets/battleforzendikar/SilentSkimmer.java b/Mage.Sets/src/mage/sets/battleforzendikar/SilentSkimmer.java index bd3526bc99b..6fd8d305112 100644 --- a/Mage.Sets/src/mage/sets/battleforzendikar/SilentSkimmer.java +++ b/Mage.Sets/src/mage/sets/battleforzendikar/SilentSkimmer.java @@ -31,13 +31,12 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.effects.common.LoseLifeTargetEffect; +import mage.abilities.effects.common.LoseLifeDefendingPlayerEffect; import mage.abilities.keyword.DevoidAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.constants.SetTargetPointer; /** * @@ -60,8 +59,7 @@ public class SilentSkimmer extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // Whenever Silent Skimmer attacks, defending player loses 2 life. - this.addAbility(new AttacksTriggeredAbility(new LoseLifeTargetEffect(2), false, - "Whenever {this} attacks, defending player loses 2 life", SetTargetPointer.PLAYER)); + this.addAbility(new AttacksTriggeredAbility(new LoseLifeDefendingPlayerEffect(2, true), false)); } diff --git a/Mage.Sets/src/mage/sets/magic2015/LeechingSliver.java b/Mage.Sets/src/mage/sets/magic2015/LeechingSliver.java index 32bb2c61750..82221b1d0be 100644 --- a/Mage.Sets/src/mage/sets/magic2015/LeechingSliver.java +++ b/Mage.Sets/src/mage/sets/magic2015/LeechingSliver.java @@ -27,6 +27,7 @@ */ package mage.sets.magic2015; +import java.util.UUID; import mage.MageInt; import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility; import mage.abilities.effects.common.LoseLifeDefendingPlayerEffect; @@ -36,8 +37,6 @@ import mage.constants.Rarity; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.SubtypePredicate; -import java.util.UUID; - /** * @author noxx */ @@ -58,7 +57,7 @@ public class LeechingSliver extends CardImpl { this.toughness = new MageInt(1); // Whenever a Sliver you control attacks, defending player loses 1 life. - this.addAbility(new AttacksCreatureYouControlTriggeredAbility(new LoseLifeDefendingPlayerEffect(1), false, filter)); + this.addAbility(new AttacksCreatureYouControlTriggeredAbility(new LoseLifeDefendingPlayerEffect(1, true), false, filter, true)); } public LeechingSliver(final LeechingSliver card) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/LoseLifeDefendingPlayerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/LoseLifeDefendingPlayerEffect.java index 5d7fbbe3f21..54d0a5fedfb 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/LoseLifeDefendingPlayerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/LoseLifeDefendingPlayerEffect.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,12 +20,11 @@ * 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 mage.abilities.effects.common; import mage.abilities.Ability; @@ -37,20 +36,25 @@ import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; -import java.util.UUID; - /** * @author noxx */ public class LoseLifeDefendingPlayerEffect extends OneShotEffect { protected DynamicValue amount; + protected boolean attackerIsSource; - public LoseLifeDefendingPlayerEffect(int amount) { - this(new StaticValue(amount)); + /** + * + * @param amount + * @param attackerIsSource true if the source.getSourceId() contains the + * attacker false if attacker has to be taken from targetPointer + */ + public LoseLifeDefendingPlayerEffect(int amount, boolean attackerIsSource) { + this(new StaticValue(amount), attackerIsSource); } - public LoseLifeDefendingPlayerEffect(DynamicValue amount) { + public LoseLifeDefendingPlayerEffect(DynamicValue amount, boolean attackerIsSource) { super(Outcome.Damage); this.amount = amount; } @@ -58,6 +62,7 @@ public class LoseLifeDefendingPlayerEffect extends OneShotEffect { public LoseLifeDefendingPlayerEffect(final LoseLifeDefendingPlayerEffect effect) { super(effect); this.amount = effect.amount.copy(); + this.attackerIsSource = effect.attackerIsSource; } @Override @@ -67,21 +72,21 @@ public class LoseLifeDefendingPlayerEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - for (UUID defenderId : game.getCombat().getDefenders()) { - Player defender = game.getPlayer(defenderId); - if (defender != null) { - defender.loseLife(amount.calculate(game, source, this), game); - } + Player defender = null; + if (attackerIsSource) { + defender = game.getPlayer(game.getCombat().getDefenderId(source.getSourceId())); + } else { + defender = game.getPlayer(game.getCombat().getDefenderId(getTargetPointer().getFirst(game, source))); + } + if (defender != null) { + defender.loseLife(amount.calculate(game, source, this), game); } return true; } @Override public String getText(Mode mode) { - StringBuilder sb = new StringBuilder("defending player loses "); - sb.append(amount); - sb.append(" life"); - return sb.toString(); + return "defending player loses " + amount + " life"; } } From 58e4cb45c387f3c354fc1ef4f3085fa355e93f7e Mon Sep 17 00:00:00 2001 From: fireshoes Date: Wed, 16 Mar 2016 10:57:35 -0500 Subject: [PATCH 29/44] [SOI] Updated mtg-cards-data.txt with 3/16 spoilers. Added missing color identifiers for DFCs. --- .../shadowsoverinnistrad/ArlinnEmbracedByTheMoon.java | 2 ++ .../src/mage/sets/shadowsoverinnistrad/AwokenHorror.java | 1 + .../mage/sets/shadowsoverinnistrad/HeirToTheNight.java | 1 + .../src/mage/sets/shadowsoverinnistrad/InsidiousMist.java | 1 + .../sets/shadowsoverinnistrad/LunarchInquisitors.java | 1 + .../src/mage/sets/shadowsoverinnistrad/PerfectedForm.java | 1 + .../src/mage/sets/shadowsoverinnistrad/Rattlechains.java | 6 +++--- .../mage/sets/shadowsoverinnistrad/WaywardDisciple.java | 1 + .../shadowsoverinnistrad/WerewolfOfAncientHunger.java | 1 + Utils/mtg-cards-data.txt | 8 ++++++++ 10 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnEmbracedByTheMoon.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnEmbracedByTheMoon.java index 15c80e19096..f3e50ae5484 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnEmbracedByTheMoon.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/ArlinnEmbracedByTheMoon.java @@ -66,6 +66,8 @@ public class ArlinnEmbracedByTheMoon extends CardImpl { super(ownerId, 243, "Arlinn, Embraced by the Moon", Rarity.MYTHIC, new CardType[]{CardType.PLANESWALKER}, ""); this.expansionSetCode = "SOI"; this.subtype.add("Arlinn"); + this.color.setRed(true); + this.color.setGreen(true); this.nightCard = true; this.canTransform = true; diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AwokenHorror.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AwokenHorror.java index 095915bec1b..0f864b229c2 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AwokenHorror.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/AwokenHorror.java @@ -51,6 +51,7 @@ public class AwokenHorror extends CardImpl { this.subtype.add("Horror"); this.power = new MageInt(7); this.toughness = new MageInt(8); + this.color.setBlue(true); this.nightCard = true; diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/HeirToTheNight.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/HeirToTheNight.java index a0a5299cf87..c33a8c45a7b 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/HeirToTheNight.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/HeirToTheNight.java @@ -47,6 +47,7 @@ public class HeirToTheNight extends CardImpl { this.subtype.add("Berserker"); this.power = new MageInt(3); this.toughness = new MageInt(2); + this.color.setBlack(true); // this card is the second face of double-faced card this.nightCard = true; diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InsidiousMist.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InsidiousMist.java index 05d7ed9d92e..1d254d9666f 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InsidiousMist.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InsidiousMist.java @@ -59,6 +59,7 @@ public class InsidiousMist extends CardImpl { this.subtype.add("Elemental"); this.power = new MageInt(0); this.toughness = new MageInt(1); + this.color.setBlue(true); this.nightCard = true; this.canTransform = true; diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/LunarchInquisitors.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/LunarchInquisitors.java index a2b34b986d2..1eb05772650 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/LunarchInquisitors.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/LunarchInquisitors.java @@ -51,6 +51,7 @@ public class LunarchInquisitors extends CardImpl { this.subtype.add("Cleric"); this.power = new MageInt(4); this.toughness = new MageInt(4); + this.color.setWhite(true); // this card is the second face of double-faced card this.nightCard = true; diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PerfectedForm.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PerfectedForm.java index b40aa079edc..76a62e6f543 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PerfectedForm.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/PerfectedForm.java @@ -47,6 +47,7 @@ public class PerfectedForm extends CardImpl { this.subtype.add("Horror"); this.power = new MageInt(5); this.toughness = new MageInt(4); + this.color.setBlue(true); // this card is the second face of double-faced card this.nightCard = true; diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/Rattlechains.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/Rattlechains.java index 38096868b17..64fe19de39b 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/Rattlechains.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/Rattlechains.java @@ -53,8 +53,8 @@ import mage.target.common.TargetCreaturePermanent; */ public class Rattlechains extends CardImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("spirit"); - private static final FilterCard filterCard = new FilterCard("spirit cards"); + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Spirit"); + private static final FilterCard filterCard = new FilterCard("Spirit spells"); static { filter.add(new SubtypePredicate("Spirit")); @@ -79,7 +79,7 @@ public class Rattlechains extends CardImpl { ability.addTarget(new TargetCreaturePermanent(filter)); this.addAbility(ability); - // You may cast spirit cards as though they had flash. + // You may cast Spirit spells as though they had flash. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CastAsThoughItHadFlashAllEffect(Duration.WhileOnBattlefield, filterCard, false))); } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WaywardDisciple.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WaywardDisciple.java index 9ed58f01a72..bcfe39df817 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WaywardDisciple.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WaywardDisciple.java @@ -61,6 +61,7 @@ public class WaywardDisciple extends CardImpl { this.subtype.add("Cleric"); this.power = new MageInt(2); this.toughness = new MageInt(4); + this.color.setBlack(true); // this card is the second face of double-faced card this.nightCard = true; diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java index fe2b0437686..2e0df2d4c31 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java @@ -59,6 +59,7 @@ public class WerewolfOfAncientHunger extends CardImpl { this.subtype.add("Werewolf"); this.power = new MageInt(0); this.toughness = new MageInt(0); + this.color.setGreen(true); this.nightCard = true; this.canTransform = true; diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index b34280cb21f..48583a87631 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -56957,9 +56957,11 @@ Topplegeist|Shadows over Innistrad|45|U|{W}|Creature - Spirit|1|1|Flying$When To Aberrant Researcher|Shadows over Innistrad|49a|U|{3}{U}|Creature - Human Insect|3|2|Flying$At the beginning of your upkeep, put the top card of your library into your graveyard. If it's an instant or sorcery card, transform Aberrant Researcher.| Perfected Form|Shadows over Innistrad|49b|U||Creature - Insect Horror|5|4|Flying| Compelling Deterrence|Shadows over Innistrad|52|U|{1}{U}|Instant|||Return target nonland permanent to its owner's hand. Then that player discards a card if you control a Zombie.| +Epiphany at the Drownyard|Shadows over Innistrad|59|R|{X}{U}|Instant|||Reveal the top X plus one cards of your library and separate them into two pilse. An opponent chooses one of those piles. Put that pile into your hand and the other into your graveyard.| Furtive Homunculus|Shadows over Innistrad|64|C|{1}{U}|Creature - Homunculus|2|1|Skulk (This creature can't be blocked by creatures with greater power.)| Geralf's Masterpiece|Shadows over Innistrad|65|M|{3}{U}{U}|Creature - Zombie Horror|7|7|Flying$Geralf's Masterpiece gets -1/-1 for each card in your hand.${3}{U}, Discard three cards: Return Geralf's Masterpiece from your graveyard to the battlefield tapped.| Invasive Surgery|Shadows over Innistrad|68|U|{U}|Instant|||Counter target sorcery spell.$Delirium — If there are four or more card types among cards in your graveyard, search the graveyard, hand, and library of that spell's controller for any number of cards with the same name as that spell, exile those cards, then that player shuffles his or her library.| +Jace, Unraveler of Secrets|Shadows over Innistrad|69|M|{3}{U}{U}|Planeswalker - Jace|||+1: Scry 1, then draw a card.$-2: Return target creature to its owner's hand.$-8: You get an emblem with "Whenever an opponent casts his or her first spell each turn, counter that spell."| Just the Wind|Shadows over Innistrad|71|C|{1}{U}|Instant|||Return target creature to its owner's hand.$Madness {U} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| Lamplighter of Selhoff|Shadows over Innistrad|72|C|{4}{U}|Creature - Zombie Horror|3|5|When Lamplighter of Selhoff enters the battlefield, if you control another Zombie, you may draw a card. If you do, discard a card.| Nephalia Moondrakes|Shadows over Innistrad|75|R|{5}{U}{U}|Creature - Drake|5|5|Flying$When Nephalia Moondrakes enters the battlefield, target creature gains flying until end of turn.${4}{U}{U}, Exile Nephalia Moondrakes from your graveyard: Creatures you control gain flying until end of turn.| @@ -56967,6 +56969,7 @@ Niblis of Dusk|Shadows over Innistrad|76|C|{2}{U}|Creature - Spirit|2|1|Flying$P Pieces of the Puzzle|Shadows over Innistrad|78|C|{2}{U}|Sorcery|||Reveal the top five cards of your library. Put up to two instant and/or sorcery cards from among them into your hand and the rest into your graveyard.| Pore Over the Pages|Shadows over Innistrad|79|U|{3}{U}{U}|Sorcery|||Draw three cards, untap up to two lands, then discard a card.| Rattlechains|Shadows over Innistrad|81|R|{1}{U}|Creature - Spirit|2|1|Flash$Flying$When Rattlechains enters the battlefield, target spirit gains hexproof until end of turn.$You may cast spirit cards as though they had flash.| +Rise from the Tides|Shadows over Innistrad|83|U|{5}{U}|Sorcery|||For each instant or sorcery card in your graveyard, put a 2/2 black Zombie creature token onto the battlefield.| Startled Awake|Shadows over Innistrad|88a|M|{2}{U}{U}|Sorcery|||Target opponent puts the top thirteen cards of his or her library into his or her graveyard.${3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery.| Persistent Nightmare|Shadows over Innistrad|88b|M||Creature - Nightmare|1|1|Skulk (This creature can't be blocked by creatures with greater power.)$When Persistent Nightmare deals combat damage to a player, return it to its owner's hand.| Stitched Mangler|Shadows over Innistrad|89|C|{2}{U}|Creature - Zombie Horror|2|3|Stitched Mangler enters the battlefield tapped.$When Stitched Mangler enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.| @@ -56989,6 +56992,8 @@ Shamble Back|Shadows over Innistrad|134|C|{B}|Sorcery|||Exile target creature ca Sinister Concoction|Shadows over Innistrad|135|U|{B}|Enchantment|||{B}, Pay 1 life, Put the top card of your library into your graveyard, Discard a card, Sacrifice Sinister Concoction: Destroy target creature.| To the Slaughter|Shadows over Innistrad|139|R|{2}{B}|Instant|||Target player sacrifices a creature or planeswalker.$Delirium — If there are four or more card types among cards in your graveyard, instead that player sacrifices a creature and a planeswalker.| Tooth Collector|Shadows over Innistrad|140|U|{2}{B}|Creature - Human Rogue|3|2|When Tooth Collector enters the battlefield, target creature an opponent controls gets -1/-1 until end of turn.${Delirium — At the beginning of each opponent's upkeep, if there are four or more card types among cards in your graveyard, target creature that player controls gets -1/-1 until end of turn.| +Breakneck Rider|Shadows over Innistrad|147a|U|{1}{R}{R}|Creature - Human Scout Werewolf|3|3|At the beginning of each upkeep, if no spells were cast last turn, transform Breakneck Rider.| +Neck Breaker|Shadows over Innistrad|147b|U||Creature - Werewolf|4|3|Attacking creatures you control get +1/+0 and have trample.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Neck Breaker.| Dance with Devils|Shadows over Innistrad|150|U|{3}{R}|Instant|||Put two 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player."| Devil's Playground|Shadows over Innistrad|151|R|{4}{R}{R}|Sorcery|||Put four 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player."| Ember-Eye Wolf|Shadows over Innistrad|154|C|{2}{R}|Creature - Wolf|1|2|Haste${1}{R}: Ember-Eye Wolf gets +2/+0 until end of turn.| @@ -57006,6 +57011,7 @@ Wolf of Devil's Breach|Shadows over Innistrad|192|M|{3}{R}{R}|Creature - Element Clip Wings|Shadows over Innistrad|197|C|{1}{G}|Instant|||Each opponent sacrifices a creature with flying.| Duskwatch Recruiter|Shadows over Innistrad|203a|U|{1}{G}|Creature - Human Warrior Werewolf|2|2|{2}{G}: Look at the top three cards of your library. You may reveal a creature card from among them and put it into your hand. Put the rest on the bottom of your library in any order.$At the beginning of each upkeep, if no spells were cast last turn, transform Duskwatch Recruiter.| Krallenhorde Howler|Shadows over Innistrad|203b|U||Creature - Werewolf|3|3|Creature spells you cast cost {1} less to cast.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Krallenhorde Howler.| +Groundskeeper|Shadows over Innistrad|208|U|{G}|Creature - Human Druid|1|1|{1}{G}: Return target basic land card from your graveyard to your hand.| Hinterland Logger|Shadows over Innistrad|210a|C|{1}{G}|Creature - Human Werewolf|2|1|At the beginning of each upkeep, if no spells were cast last turn, transform Hinterland Logger.| Timber Shredder|Shadows over Innistrad|210b|C||Creature - Werewolf|4|2|Trample$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Timber Shredder.| Pack Guardian|Shadows over Innistrad|221|U|{2}{G}{G}|Creature - Wolf Spirit|4|3|Flash$When Pack Guardian enters the battlefield, you may discard a land card. If you do, put a 2/2 green Wolf creature token onto the battlefield.| @@ -57019,6 +57025,8 @@ Anguished Unmaking|Shadows over Innistrad|242|R|{1}{W}{B}|Instant|||Exile target Arlinn Kord|Shadows over Innistrad|243a|M|{2}{R}{G}|Planeswalker - Arlinn|||+1: Until end of turn, up to one target creature gets +2/+2 and gains vigilance and haste.$0: Put a 2/2 green Wolf creature token onto the battlefield. Transform Arlinn Kord.| Arlinn, Embraced by the Moon|Shadows over Innistrad|243b|M||Planeswalker - Arlinn|||+1: Creatures you control get +1/+1 and gain trample until end of turn.$-1: Arlinn, Embraced by the Moon deals 3 damage to target creature or player. Transform Arlinn, Embraced by the Moon.$-6: You get an emblem with "Creatures you control have haste and '{T}: This creature deals damage equal to its power to target creature or player.'"| Nahiri, the Harbinger|Shadows over Innistrad|247|M|{2}{R}{W}|Planeswalker - Nahiri|||+2: You may discard a card. If you do, draw a card.$-2: Exile target enchantment, tapped artifact, or tapped creature.$-8: Search your library for an artifact or creature card, put it onto the battlefield, then shuffle your library. It gains haste. Return it to your hand at the beginning of the next end step.| +Oliva, Mobilized for War|Shadows over Innistrad|248|M|{1}{B}{R}|Legendary Creature - Vampire Knight|3|3|Flying$Whenever another creature enters the battlefield under your control, you may discard a card. If you do, put a +1/+1 counter on that creature, it gains haste until end of turn, and it becomes a Vampire in addition to its other types.| +Sigarda, Heron's Grace|Shadows over Innistrad|250|M|{3}{G}{W}|Legendary Creature - Angel|4|5|Flying$You and Humans you control have hexproof.${2}, Exile a card from your graveyard: Put a 1/1 white Human Soldier creature token onto the battlefield.| Brain in a Jar|Shadows over Innistrad|252|R|{2}|Artifact|||{1}, {T}: Put a charge counter on Brain in a Jar, then you may cast an instant or sorcery card with converted mana costs equal to the number of charge counters on Brain in a Jar from your hand without paying its mana cost.${3}< {T}, Remove X charge counters from Brain in a Jar: Scry X.| Explosive Apparatus|Shadows over Innistrad|255|C|{1}|Artifact|||{3}, {T}, Sacrifice Explosive Apparatus: Explosive Apparatus deals 2 damage to target creature or player.| Magnifying Glass|Shadows over Innistrad|258|U|{3}|Artifact|||{T}: Add {C} to your mana pool.${4}, {T}: Investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| From eec3e779bd59ee8f99e4b732436f61ba830ff5f8 Mon Sep 17 00:00:00 2001 From: rkfg Date: Thu, 17 Mar 2016 03:19:04 +0300 Subject: [PATCH 30/44] Simplify controlling of advanced deck generator sliders. --- .../deck/generator/DeckGeneratorDialog.java | 18 ++- .../generator/RatioAdjustingSliderPanel.java | 110 +++++++++--------- 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java index 3d541a75eb7..d723a09aba3 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java @@ -289,15 +289,15 @@ public class DeckGeneratorDialog { adjustingSliderPanel = new RatioAdjustingSliderPanel(); // Restore saved slider values - String creaturePercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE, - Integer.toString(DeckGeneratorPool.DEFAULT_CREATURE_PERCENTAGE)); - adjustingSliderPanel.setCreaturePercentage(Integer.parseInt(creaturePercentage)); - String nonCreaturePercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE, - Integer.toString(DeckGeneratorPool.DEFAULT_NON_CREATURE_PERCENTAGE)); - adjustingSliderPanel.setNonCreaturePercentage(Integer.parseInt(nonCreaturePercentage)); String landPercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_LAND_PERCENTAGE, Integer.toString(DeckGeneratorPool.DEFAULT_LAND_PERCENTAGE)); adjustingSliderPanel.setLandPercentage(Integer.parseInt(landPercentage)); + String nonCreaturePercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_NON_CREATURE_PERCENTAGE, + Integer.toString(DeckGeneratorPool.DEFAULT_NON_CREATURE_PERCENTAGE)); + adjustingSliderPanel.setNonCreaturePercentage(Integer.parseInt(nonCreaturePercentage)); + String creaturePercentage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_CREATURE_PERCENTAGE, + Integer.toString(DeckGeneratorPool.DEFAULT_CREATURE_PERCENTAGE)); + adjustingSliderPanel.setCreaturePercentage(Integer.parseInt(creaturePercentage)); advancedPanel.add(adjustingSliderPanel, c); // Reset @@ -306,7 +306,7 @@ public class DeckGeneratorDialog { c.ipady = 0; c.weightx = 1.0; c.anchor = GridBagConstraints.LAST_LINE_END; - c.insets = new Insets(10,10, 0, 0); + c.insets = new Insets(10, 10, 0, 0); c.gridx = 2; c.gridwidth = 1; c.gridy = 2; @@ -418,13 +418,11 @@ public class DeckGeneratorDialog { } public DeckGeneratorCMC getDeckGeneratorCMC() { - DeckGeneratorCMC selectedCMC = (DeckGeneratorCMC)cbCMC.getSelectedItem(); + DeckGeneratorCMC selectedCMC = (DeckGeneratorCMC) cbCMC.getSelectedItem(); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ADVANCED_CMC, selectedCMC.name()); return selectedCMC; } - - public String getSelectedColors() { if (selectedColors != null) { PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_DECK_SIZE, cbDeckSize.getSelectedItem().toString()); diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java index 665fdbbfef5..e25f42a7ad3 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java @@ -45,20 +45,21 @@ import java.util.List; */ public class RatioAdjustingSliderPanel extends JPanel { + private static final int MAXIMUM = 100; private JStorageSlider creatureSlider, nonCreatureSlider, landSlider; private List textLabels = new ArrayList<>(); private AdjustingSliderGroup sg; private class JStorageSlider extends JSlider { - // Slider stores its initial value to revert to when reset + // Slider stores its initial value to revert to when reset private int defaultValue; - private int previousValue; + private boolean adjust; - public JStorageSlider(int min, int max, int value) { + public JStorageSlider(int min, int max, int value, boolean adjust) { super(min, max, value); - previousValue = value; defaultValue = value; + this.adjust = adjust; setMinorTickSpacing(5); setMajorTickSpacing(10); setPaintTicks(true); @@ -66,30 +67,23 @@ public class RatioAdjustingSliderPanel extends JPanel { setLabelTable(createStandardLabels(10)); } - public int getPreviousValue() { - return previousValue; - } - - public void setPreviousValue(int value) { - previousValue = value; - } - public void resetDefault() { this.setValue(defaultValue); - previousValue = defaultValue; + } + + public boolean isAdjust() { + return adjust; } } - private class AdjustingSliderGroup - { + private class AdjustingSliderGroup { private final ArrayList storageSliders; private int sliderIndex = 0; - AdjustingSliderGroup(JStorageSlider... sliders) - { + AdjustingSliderGroup(JStorageSlider... sliders) { storageSliders = new ArrayList<>(); - for(JStorageSlider slider: sliders) { + for (JStorageSlider slider : sliders) { storageSliders.add(slider); slider.addChangeListener(new ChangeListener() { @Override @@ -99,35 +93,40 @@ public class RatioAdjustingSliderPanel extends JPanel { }); } } + public void fireSliderChangedEvent(JStorageSlider source) { // We don't want to do anything if the value isn't changing - if(!source.getValueIsAdjusting()) + if (!source.getValueIsAdjusting()) return; - // Update the slider depending on how much it's changed relative to its previous position - int change = (source.getValue() - source.getPreviousValue()); - updateSliderPosition(change, source); + // Update the slider depending on how much it's changed relative to its previous position + updateSliderPosition(source); } - private void updateSliderPosition(int change, JStorageSlider source) { - int remaining = change; - while (remaining != 0) { - // Get the currently indexed slider - JStorageSlider slider = storageSliders.get(sliderIndex); - // If it's not the slider that fired the event - if (slider != source) { - // Check we don't go over the upper and lower bounds - if (remaining < 0 || (remaining > 0 && slider.getValue() > 0)) { - // Adjust the currently selected slider by +/- 1 - int adjustment = Integer.signum(remaining); - slider.setValue(slider.getValue() - adjustment); - remaining -= adjustment; - } - } - // Select the next slider in the list of sliders - sliderIndex = (sliderIndex + 1) % storageSliders.size(); + private void updateSliderPosition(JStorageSlider source) { + int maximum = MAXIMUM; + int excess = 100; + for (JStorageSlider slider : storageSliders) { + excess -= slider.getValue(); } - for (JStorageSlider slider : storageSliders) { - slider.setPreviousValue(slider.getValue()); + int addition = excess / (storageSliders.size() - 1); // divide the deficit between all sliders except this one + if (addition == 0 && excess != 0) { + addition = Integer.signum(excess); + } + for (int i = storageSliders.size() - 1; i >= 0; i--) { + JStorageSlider slider = storageSliders.get(i); + int value = slider.getValue(); + if (slider != source && slider.isAdjust()) { + slider.setMaximum(maximum); + if (excess > addition) { + value += addition; + excess -= addition; + } else { + value += excess; + excess = 0; + } + slider.setValue(value); + } + maximum -= value; } } @@ -143,10 +142,10 @@ public class RatioAdjustingSliderPanel extends JPanel { private void initPanel() { - // Create three sliders with default values - creatureSlider = new JStorageSlider(0, 100, DeckGeneratorPool.DEFAULT_CREATURE_PERCENTAGE); - nonCreatureSlider = new JStorageSlider(0, 100, DeckGeneratorPool.DEFAULT_NON_CREATURE_PERCENTAGE); - landSlider = new JStorageSlider(0, 100, DeckGeneratorPool.DEFAULT_LAND_PERCENTAGE); + // Create three sliders with default values + creatureSlider = new JStorageSlider(0, MAXIMUM, DeckGeneratorPool.DEFAULT_CREATURE_PERCENTAGE, true); + nonCreatureSlider = new JStorageSlider(0, MAXIMUM, DeckGeneratorPool.DEFAULT_NON_CREATURE_PERCENTAGE, true); + landSlider = new JStorageSlider(0, MAXIMUM, DeckGeneratorPool.DEFAULT_LAND_PERCENTAGE, false); sg = new AdjustingSliderGroup(creatureSlider, nonCreatureSlider, landSlider); @@ -178,7 +177,7 @@ public class RatioAdjustingSliderPanel extends JPanel { return sliderPanel; } - private static JLabel createChangingPercentageLabel(final JSlider slider) { + private static JLabel createChangingPercentageLabel(final JSlider slider) { final JLabel label = new JLabel(" " + String.valueOf(slider.getValue()) + "%"); @@ -187,8 +186,8 @@ public class RatioAdjustingSliderPanel extends JPanel { public void stateChanged(ChangeEvent e) { String value = String.valueOf(slider.getValue()); StringBuilder labelBuilder = new StringBuilder(); - // Pad with spaces so all percentage labels are of equal size - for(int i = 0; i < (5-value.length()); i++) { + // Pad with spaces so all percentage labels are of equal size + for (int i = 0; i < (5 - value.length()); i++) { labelBuilder.append(" "); } labelBuilder.append(value); @@ -201,16 +200,16 @@ public class RatioAdjustingSliderPanel extends JPanel { @Override public void setEnabled(boolean enabled) { - for(JStorageSlider slider: sg.getSliders()) { + for (JStorageSlider slider : sg.getSliders()) { slider.setEnabled(enabled); } - for(JLabel label: textLabels) { + for (JLabel label : textLabels) { label.setEnabled(enabled); } } public void resetValues() { - for(JStorageSlider slider: sg.getSliders()) { + for (JStorageSlider slider : sg.getSliders()) { slider.resetDefault(); } } @@ -229,20 +228,17 @@ public class RatioAdjustingSliderPanel extends JPanel { public void setCreaturePercentage(int percentage) { creatureSlider.setValue(percentage); - creatureSlider.previousValue = percentage; + sg.updateSliderPosition(creatureSlider); } public void setNonCreaturePercentage(int percentage) { nonCreatureSlider.setValue(percentage); - nonCreatureSlider.previousValue = percentage; + sg.updateSliderPosition(nonCreatureSlider); } public void setLandPercentage(int percentage) { landSlider.setValue(percentage); - landSlider.previousValue = percentage; + sg.updateSliderPosition(landSlider); } - - - } From 74f925b579d20d88c0b2094ec276d9b8579f1b73 Mon Sep 17 00:00:00 2001 From: rkfg Date: Thu, 17 Mar 2016 06:25:56 +0300 Subject: [PATCH 31/44] Fix deck generator sliders. --- .../generator/RatioAdjustingSliderPanel.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java index e25f42a7ad3..b1c07c9acac 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java @@ -46,6 +46,7 @@ import java.util.List; public class RatioAdjustingSliderPanel extends JPanel { private static final int MAXIMUM = 100; + private int adjustableCount; private JStorageSlider creatureSlider, nonCreatureSlider, landSlider; private List textLabels = new ArrayList<>(); private AdjustingSliderGroup sg; @@ -91,6 +92,9 @@ public class RatioAdjustingSliderPanel extends JPanel { fireSliderChangedEvent((JStorageSlider) e.getSource()); } }); + if (slider.isAdjust()) { + adjustableCount++; + } } } @@ -105,25 +109,32 @@ public class RatioAdjustingSliderPanel extends JPanel { private void updateSliderPosition(JStorageSlider source) { int maximum = MAXIMUM; int excess = 100; + int sign = 0; for (JStorageSlider slider : storageSliders) { excess -= slider.getValue(); } - int addition = excess / (storageSliders.size() - 1); // divide the deficit between all sliders except this one + sign = Integer.signum(excess); + excess = Math.abs(excess); + int addition = excess / (adjustableCount - 1); // divide the deficit between all adjustable sliders except this one if (addition == 0 && excess != 0) { - addition = Integer.signum(excess); + addition = 1; } for (int i = storageSliders.size() - 1; i >= 0; i--) { JStorageSlider slider = storageSliders.get(i); int value = slider.getValue(); if (slider != source && slider.isAdjust()) { slider.setMaximum(maximum); - if (excess > addition) { - value += addition; + if (excess >= addition) { + value += addition * sign; excess -= addition; } else { - value += excess; + value += excess * sign; excess = 0; } + if (value < 0) { + excess += value; + value = 0; + } slider.setValue(value); } maximum -= value; From e3b5827a76dee472ea93374c0465544beafd5b06 Mon Sep 17 00:00:00 2001 From: rkfg Date: Thu, 17 Mar 2016 06:48:24 +0300 Subject: [PATCH 32/44] Fix imports in Exhaustion.java --- Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java b/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java index 2f6f38fa629..cd21bc9dd09 100644 --- a/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java +++ b/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java @@ -27,8 +27,6 @@ */ package mage.sets.portalthreekingdoms; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; @@ -44,7 +42,7 @@ import mage.filter.predicate.mageobject.CardTypePredicate; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetOpponent; -import mage.target.targetpointer.FixedTargets; +import mage.target.targetpointer.FixedTarget; /** * From 98d8269918a8eb0a0aa3d0ae4794e89de1fe7904 Mon Sep 17 00:00:00 2001 From: fireshoes Date: Wed, 16 Mar 2016 23:27:46 -0500 Subject: [PATCH 33/44] Merge/origin --- .../sets/portalthreekingdoms/Exhaustion.java | 214 +++++++++--------- 1 file changed, 106 insertions(+), 108 deletions(-) diff --git a/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java b/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java index 2f6f38fa629..40d5edf0b36 100644 --- a/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java +++ b/Mage.Sets/src/mage/sets/portalthreekingdoms/Exhaustion.java @@ -1,108 +1,106 @@ -/* - * 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 mage.sets.portalthreekingdoms; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DontUntapInOpponentsNextUntapStepAllEffect; -import mage.cards.CardImpl; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; -import mage.filter.FilterPermanent; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.mageobject.CardTypePredicate; -import mage.game.Game; -import mage.players.Player; -import mage.target.common.TargetOpponent; -import mage.target.targetpointer.FixedTargets; - -/** - * - * @author emerald000 - */ -public class Exhaustion extends CardImpl { - - public Exhaustion(UUID ownerId) { - super(ownerId, 42, "Exhaustion", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{2}{U}"); - this.expansionSetCode = "PTK"; - - // Creatures and lands target opponent controls don't untap during his or her next untap step. - this.getSpellAbility().addEffect(new ExhaustionEffect()); - this.getSpellAbility().addTarget(new TargetOpponent()); - } - - public Exhaustion(final Exhaustion card) { - super(card); - } - - @Override - public Exhaustion copy() { - return new Exhaustion(this); - } -} - -class ExhaustionEffect extends OneShotEffect { - - private static final FilterPermanent filter = new FilterPermanent(); - - static { - filter.add(Predicates.or(new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.LAND))); - } - - ExhaustionEffect() { - super(Outcome.Detriment); - this.staticText = "Creatures and lands target opponent controls don't untap during his or her next untap step."; - } - - ExhaustionEffect(final ExhaustionEffect effect) { - super(effect); - } - - @Override - public ExhaustionEffect copy() { - return new ExhaustionEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getFirstTarget()); - - if (player != null) { - ContinuousEffect effect = new DontUntapInOpponentsNextUntapStepAllEffect(filter); - effect.setTargetPointer(new FixedTarget(player.getId())); - game.addEffect(effect, source); - return true; - } - return false; - } -} +/* + * 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 mage.sets.portalthreekingdoms; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DontUntapInOpponentsNextUntapStepAllEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author emerald000 + */ +public class Exhaustion extends CardImpl { + + public Exhaustion(UUID ownerId) { + super(ownerId, 42, "Exhaustion", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{2}{U}"); + this.expansionSetCode = "PTK"; + + // Creatures and lands target opponent controls don't untap during his or her next untap step. + this.getSpellAbility().addEffect(new ExhaustionEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + } + + public Exhaustion(final Exhaustion card) { + super(card); + } + + @Override + public Exhaustion copy() { + return new Exhaustion(this); + } +} + +class ExhaustionEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterPermanent(); + + static { + filter.add(Predicates.or(new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.LAND))); + } + + ExhaustionEffect() { + super(Outcome.Detriment); + this.staticText = "Creatures and lands target opponent controls don't untap during his or her next untap step."; + } + + ExhaustionEffect(final ExhaustionEffect effect) { + super(effect); + } + + @Override + public ExhaustionEffect copy() { + return new ExhaustionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getFirstTarget()); + + if (player != null) { + ContinuousEffect effect = new DontUntapInOpponentsNextUntapStepAllEffect(filter); + effect.setTargetPointer(new FixedTarget(player.getId())); + game.addEffect(effect, source); + return true; + } + return false; + } +} From 03b80f2a98ea70cef5748fa1427269352c857723 Mon Sep 17 00:00:00 2001 From: fireshoes Date: Thu, 17 Mar 2016 02:31:09 -0500 Subject: [PATCH 34/44] [SOI] Add Olivia Mobilizer for War, Sigarda Heron's Grace, Olivia's Bloodsworn, Stromkirk Mentor, Breakneck Rider, Indulgent Aristocrat, Twins of Maurer Estate, Rise from the Tides, Merciless Resolve, Groundskeeper reprint. Fixed Sage of Ancient Lore P/T not changing when transforming back to day side. --- .../shadowsoverinnistrad/BreakneckRider.java | 75 ++++++++++++ .../shadowsoverinnistrad/Groundskeeper.java | 52 ++++++++ .../IndulgentAristocrat.java | 84 +++++++++++++ .../MercilessResolve.java | 72 +++++++++++ .../shadowsoverinnistrad/NeckBreaker.java | 86 ++++++++++++++ .../OliviaMobilizedForWar.java | 100 ++++++++++++++++ .../OliviasBloodsworn.java | 88 ++++++++++++++ .../RiseFromTheTides.java | 62 ++++++++++ .../SageOfAncientLore.java | 6 +- .../SigardaHeronsGrace.java | 112 ++++++++++++++++++ .../shadowsoverinnistrad/StromkirkMentor.java | 79 ++++++++++++ .../TwinsOfMaurerEstate.java | 63 ++++++++++ .../WerewolfOfAncientHunger.java | 6 +- Utils/mtg-cards-data.txt | 12 +- 14 files changed, 892 insertions(+), 5 deletions(-) create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/BreakneckRider.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/Groundskeeper.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/IndulgentAristocrat.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/MercilessResolve.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/NeckBreaker.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/OliviaMobilizedForWar.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/OliviasBloodsworn.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/RiseFromTheTides.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/SigardaHeronsGrace.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/StromkirkMentor.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/TwinsOfMaurerEstate.java diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/BreakneckRider.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/BreakneckRider.java new file mode 100644 index 00000000000..913a2b4e64e --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/BreakneckRider.java @@ -0,0 +1,75 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.condition.common.NoSpellsWereCastLastTurnCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.TargetController; + +/** + * + * @author fireshoes + */ +public class BreakneckRider extends CardImpl { + + public BreakneckRider(UUID ownerId) { + super(ownerId, 147, "Breakneck Rider", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{1}{R}{R}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Human"); + this.subtype.add("Scout"); + this.subtype.add("Werewolf"); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + this.canTransform = true; + this.secondSideCard = new NeckBreaker(ownerId); + + // At the beginning of each upkeep, if no spells were cast last turn, transform Breakneck Rider. + this.addAbility(new TransformAbility()); + TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(new TransformSourceEffect(true), TargetController.ANY, false); + this.addAbility(new ConditionalTriggeredAbility(ability, NoSpellsWereCastLastTurnCondition.getInstance(), TransformAbility.NO_SPELLS_TRANSFORM_RULE)); + } + + public BreakneckRider(final BreakneckRider card) { + super(card); + } + + @Override + public BreakneckRider copy() { + return new BreakneckRider(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/Groundskeeper.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/Groundskeeper.java new file mode 100644 index 00000000000..29efca53618 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/Groundskeeper.java @@ -0,0 +1,52 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; + +/** + * + * @author fireshoes + */ +public class Groundskeeper extends mage.sets.ninthedition.Groundskeeper { + + public Groundskeeper(UUID ownerId) { + super(ownerId); + this.cardNumber = 208; + this.expansionSetCode = "SOI"; + } + + public Groundskeeper(final Groundskeeper card) { + super(card); + } + + @Override + public Groundskeeper copy() { + return new Groundskeeper(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/IndulgentAristocrat.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/IndulgentAristocrat.java new file mode 100644 index 00000000000..1945476591e --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/IndulgentAristocrat.java @@ -0,0 +1,84 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class IndulgentAristocrat extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("each Vampire you control"); + + static { + filter.add(new SubtypePredicate("Vampire")); + } + + public IndulgentAristocrat(UUID ownerId) { + super(ownerId, 118, "Indulgent Aristocrat", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{B}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Vampire"); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // {2}, Sacrifice a creature: Put a +1/+1 counter on each Vampire you control. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter), new GenericManaCost(2)); + ability.addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(new FilterControlledCreaturePermanent("a creature")))); + this.addAbility(ability); + } + + public IndulgentAristocrat(final IndulgentAristocrat card) { + super(card); + } + + @Override + public IndulgentAristocrat copy() { + return new IndulgentAristocrat(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/MercilessResolve.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/MercilessResolve.java new file mode 100644 index 00000000000..37684f87ecc --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/MercilessResolve.java @@ -0,0 +1,72 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author fireshoes + */ +public class MercilessResolve extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("a creature or land"); + + static { + filter.add(Predicates.or(new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.LAND))); + } + + public MercilessResolve(UUID ownerId) { + super(ownerId, 123, "Merciless Resolve", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{2}{B}"); + this.expansionSetCode = "SOI"; + + // As an additional cost to cast Merciless Resolve, sacrifice a creature or land. + this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetControlledPermanent(filter))); + + // Draw two cards. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); + } + + public MercilessResolve(final MercilessResolve card) { + super(card); + } + + @Override + public MercilessResolve copy() { + return new MercilessResolve(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NeckBreaker.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NeckBreaker.java new file mode 100644 index 00000000000..ac4466b46a4 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/NeckBreaker.java @@ -0,0 +1,86 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.TransformedCondition; +import mage.abilities.condition.common.TwoOrMoreSpellsWereCastLastTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterAttackingCreature; + +/** + * + * @author fireshoes + */ +public class NeckBreaker extends CardImpl { + + public NeckBreaker(UUID ownerId) { + super(ownerId, 147, "Neck Breaker", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, ""); + this.expansionSetCode = "SOI"; + this.subtype.add("Werewolf"); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + this.color.setRed(true); + + // this card is the second face of double-faced card + this.nightCard = true; + this.canTransform = true; + + // Attacking creatures you control get +1/+0 and have trample. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new ConditionalContinuousEffect(new BoostControlledEffect(1, 0, Duration.WhileOnBattlefield, new FilterAttackingCreature()), + new TransformedCondition(false), "Attacking creatures you control get +1/+0"))); + + // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Neck Breaker. + TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(new TransformSourceEffect(false), TargetController.ANY, false); + this.addAbility(new ConditionalTriggeredAbility(ability, TwoOrMoreSpellsWereCastLastTurnCondition.getInstance(), TransformAbility.TWO_OR_MORE_SPELLS_TRANSFORM_RULE)); + } + + public NeckBreaker(final NeckBreaker card) { + super(card); + } + + @Override + public NeckBreaker copy() { + return new NeckBreaker(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OliviaMobilizedForWar.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OliviaMobilizedForWar.java new file mode 100644 index 00000000000..365411f3d98 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OliviaMobilizedForWar.java @@ -0,0 +1,100 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.continuous.BecomesCreatureTypeTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.AnotherPredicate; + +/** + * + * @author fireshoes + */ +public class OliviaMobilizedForWar extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("another creature"); + + static { + filter.add(new AnotherPredicate()); + } + + public OliviaMobilizedForWar(UUID ownerId) { + super(ownerId, 248, "Olivia, Mobilized for War", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, "{1}{B}{R}"); + this.expansionSetCode = "SOI"; + this.supertype.add("Legendary"); + this.subtype.add("Vampire"); + this.subtype.add("Knight"); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever another creature enters the battlefield under your control, you may discard a card. If you do, put a +1/+1 counter on that creature, + // it gains haste until end of turn, and it becomes a Vampire in addition to its other types. + Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance()); + effect.setText("put a +1/+1 counter on that creature"); + DoIfCostPaid doIfCostPaid = new DoIfCostPaid(effect, new DiscardCardCost()); + effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn); + effect.setText(", it gains haste until end of turn,"); + doIfCostPaid.addEffect(effect); + effect = new BecomesCreatureTypeTargetEffect(Duration.WhileOnBattlefield, new ArrayList<>(Arrays.asList("Vampire")), false); + effect.setText("and it becomes a Vampire in addition to its other types"); + doIfCostPaid.addEffect(effect); + this.addAbility(new EntersBattlefieldControlledTriggeredAbility(Zone.BATTLEFIELD, doIfCostPaid, filter, false, SetTargetPointer.PERMANENT, null)); + } + + public OliviaMobilizedForWar(final OliviaMobilizedForWar card) { + super(card); + } + + @Override + public OliviaMobilizedForWar copy() { + return new OliviaMobilizedForWar(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OliviasBloodsworn.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OliviasBloodsworn.java new file mode 100644 index 00000000000..e3db48b6748 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OliviasBloodsworn.java @@ -0,0 +1,88 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.CantBlockAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class OliviasBloodsworn extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Vampire"); + + static { + filter.add(new SubtypePredicate("Vampire")); + } + + public OliviasBloodsworn(UUID ownerId) { + super(ownerId, 127, "Olivia's Bloodsworn", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{1}{B}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Vampire"); + this.subtype.add("Soldier"); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Olivia's Bloodsworn can't block. + this.addAbility(new CantBlockAbility()); + + // {R}: Target Vampire gains haste until end of turn. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn), new ManaCostsImpl("{R}")); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); + } + + public OliviasBloodsworn(final OliviasBloodsworn card) { + super(card); + } + + @Override + public OliviasBloodsworn copy() { + return new OliviasBloodsworn(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/RiseFromTheTides.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/RiseFromTheTides.java new file mode 100644 index 00000000000..40875f738c3 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/RiseFromTheTides.java @@ -0,0 +1,62 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterInstantOrSorceryCard; +import mage.game.permanent.token.ZombieToken; + +/** + * + * @author fireshoes + */ +public class RiseFromTheTides extends CardImpl { + + public RiseFromTheTides(UUID ownerId) { + super(ownerId, 83, "Rise from the Tides", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{5}{U}"); + this.expansionSetCode = "SOI"; + + // Put a 2/2 black Zombie creature token onto the battlefield tapped for each instant and sorcery card in your graveyard. + CardsInControllerGraveyardCount value = new CardsInControllerGraveyardCount(new FilterInstantOrSorceryCard()); + this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieToken(), value, true, false)); + } + + public RiseFromTheTides(final RiseFromTheTides card) { + super(card); + } + + @Override + public RiseFromTheTides copy() { + return new RiseFromTheTides(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SageOfAncientLore.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SageOfAncientLore.java index b65d7d8d3f2..dd2546eb660 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SageOfAncientLore.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SageOfAncientLore.java @@ -34,6 +34,8 @@ import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.common.NoSpellsWereCastLastTurnCondition; +import mage.abilities.condition.common.TransformedCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; @@ -68,7 +70,9 @@ public class SageOfAncientLore extends CardImpl { // Sage of Ancient Lore's power and toughness are each equal to the number of cards in your hand. DynamicValue xValue= new CardsInControllerHandCount(); - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetPowerToughnessSourceEffect(xValue, Duration.EndOfGame))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, + new ConditionalContinuousEffect(new SetPowerToughnessSourceEffect(xValue, Duration.EndOfGame), + new TransformedCondition(true), "{this}'s power and toughness are each equal to the total number of cards in your hand"))); // When Sage of Ancient Lore enters the battlefield, draw a card. this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1), false)); diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SigardaHeronsGrace.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SigardaHeronsGrace.java new file mode 100644 index 00000000000..ed2a40180b4 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SigardaHeronsGrace.java @@ -0,0 +1,112 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.ExileFromGraveCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.permanent.token.Token; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author fireshoes + */ +public class SigardaHeronsGrace extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("Humans you control"); + + static { + filter.add(new SubtypePredicate("Human")); + } + + public SigardaHeronsGrace(UUID ownerId) { + super(ownerId, 250, "Sigarda, Heron's Grace", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, "{3}{G}{W}"); + this.expansionSetCode = "SOI"; + this.supertype.add("Legendary"); + this.subtype.add("Angel"); + this.power = new MageInt(4); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // You and Humans you control have hexproof. + Effect effect = new GainAbilityControllerEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield); + effect.setText("You and"); + Ability ability =new SimpleStaticAbility(Zone.BATTLEFIELD, effect); + effect = new GainAbilityControlledEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield, filter); + effect.setText("and Humans you control have hexproof"); + ability.addEffect(effect); + this.addAbility(ability); + + // {2}, Exile a card from your graveyard: Put a 1/1 white Human Soldier creature token onto the battlefield. + ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new HumanSoldierToken()), new GenericManaCost(2)); + ability.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard())); + this.addAbility(ability); + } + + public SigardaHeronsGrace(final SigardaHeronsGrace card) { + super(card); + } + + @Override + public SigardaHeronsGrace copy() { + return new SigardaHeronsGrace(this); + } +} + +class HumanSoldierToken extends Token { + + public HumanSoldierToken() { + super("Human Soldier", "1/1 white Human Soldier creature token"); + cardType.add(CardType.CREATURE); + subtype.add("Human"); + subtype.add("Soldier"); + color.setWhite(true); + power = new MageInt(1); + toughness = new MageInt(1); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/StromkirkMentor.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/StromkirkMentor.java new file mode 100644 index 00000000000..c072d33b4a3 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/StromkirkMentor.java @@ -0,0 +1,79 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author fireshoes + */ +public class StromkirkMentor extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("another target Vampire you control"); + + static { + filter.add(new AnotherPredicate()); + filter.add(new SubtypePredicate("Vampire")); + } + + public StromkirkMentor(UUID ownerId) { + super(ownerId, 137, "Stromkirk Mentor", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{3}{B}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Vampire"); + this.subtype.add("Soldier"); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // When Stromkirk Mentor enters the battlefield, put a +1/+1 counter on another target Vampire you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance()), false); + ability.addTarget(new TargetControlledPermanent(filter)); + this.addAbility(ability); + } + + public StromkirkMentor(final StromkirkMentor card) { + super(card); + } + + @Override + public StromkirkMentor copy() { + return new StromkirkMentor(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TwinsOfMaurerEstate.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TwinsOfMaurerEstate.java new file mode 100644 index 00000000000..82bf62bed06 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TwinsOfMaurerEstate.java @@ -0,0 +1,63 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.MadnessAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; + +/** + * + * @author fireshoes + */ +public class TwinsOfMaurerEstate extends CardImpl { + + public TwinsOfMaurerEstate(UUID ownerId) { + super(ownerId, 142, "Twins of Maurer Estate", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{4}{B}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Vampire"); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Madness {2}{B} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.) + this.addAbility(new MadnessAbility(this, new ManaCostsImpl("{2}{B}"))); + } + + public TwinsOfMaurerEstate(final TwinsOfMaurerEstate card) { + super(card); + } + + @Override + public TwinsOfMaurerEstate copy() { + return new TwinsOfMaurerEstate(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java index 2e0df2d4c31..c11731a4115 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WerewolfOfAncientHunger.java @@ -32,7 +32,9 @@ import mage.MageInt; import mage.abilities.TriggeredAbility; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.TransformedCondition; import mage.abilities.condition.common.TwoOrMoreSpellsWereCastLastTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.dynamicvalue.common.CardsInAllHandsCount; import mage.abilities.effects.common.TransformSourceEffect; @@ -71,7 +73,9 @@ public class WerewolfOfAncientHunger extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Werewolf of Ancient Hunger's power and toughness are each equal to the total number of cards in all players' hands. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetPowerToughnessSourceEffect(new CardsInAllHandsCount(), Duration.EndOfGame))); + this.addAbility(new SimpleStaticAbility(Zone.ALL, + new ConditionalContinuousEffect(new SetPowerToughnessSourceEffect(new CardsInAllHandsCount(), Duration.EndOfGame), + new TransformedCondition(false), "{this}'s power and toughness are each equal to the total number of cards in all players' hands"))); // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Werewolf of Ancient Hunger. TriggeredAbility ability = new BeginningOfUpkeepTriggeredAbility(new TransformSourceEffect(false), TargetController.ANY, false); diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 48583a87631..2419200f9a3 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -56969,7 +56969,7 @@ Niblis of Dusk|Shadows over Innistrad|76|C|{2}{U}|Creature - Spirit|2|1|Flying$P Pieces of the Puzzle|Shadows over Innistrad|78|C|{2}{U}|Sorcery|||Reveal the top five cards of your library. Put up to two instant and/or sorcery cards from among them into your hand and the rest into your graveyard.| Pore Over the Pages|Shadows over Innistrad|79|U|{3}{U}{U}|Sorcery|||Draw three cards, untap up to two lands, then discard a card.| Rattlechains|Shadows over Innistrad|81|R|{1}{U}|Creature - Spirit|2|1|Flash$Flying$When Rattlechains enters the battlefield, target spirit gains hexproof until end of turn.$You may cast spirit cards as though they had flash.| -Rise from the Tides|Shadows over Innistrad|83|U|{5}{U}|Sorcery|||For each instant or sorcery card in your graveyard, put a 2/2 black Zombie creature token onto the battlefield.| +Rise from the Tides|Shadows over Innistrad|83|U|{5}{U}|Sorcery|||Put a 2/2 black Zombie creature token onto the battlefield tapped for each instant and sorcery card in your graveyard.| Startled Awake|Shadows over Innistrad|88a|M|{2}{U}{U}|Sorcery|||Target opponent puts the top thirteen cards of his or her library into his or her graveyard.${3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery.| Persistent Nightmare|Shadows over Innistrad|88b|M||Creature - Nightmare|1|1|Skulk (This creature can't be blocked by creatures with greater power.)$When Persistent Nightmare deals combat damage to a player, return it to its owner's hand.| Stitched Mangler|Shadows over Innistrad|89|C|{2}{U}|Creature - Zombie Horror|2|3|Stitched Mangler enters the battlefield tapped.$When Stitched Mangler enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.| @@ -56983,15 +56983,20 @@ Farbog Revenant|Shadows over Innistrad|110|C|{2}{B}|Creature - Spirit|1|3|Skulk Heir of Falkenrath|Shadows over Innistrad|116a|U|{1}{B}|Creature - Vampire|2|1|Discard a card: Transform Heir of Falkenrath. Activate this ability only once each turn.| Heir to the Night|Shadows over Innistrad|116b|U||Creature - Vampire Berserker|3|2|Flying| Hound of the Farbogs|Shadows over Innistrad|117|C|{4}{B}|Creature - Zombie Hound|5|3|Delirium — Hound of the Farborgs has menace as long as there are four or more card types among cards in your graveyard. (A creature with menace can't be blocked except by two or more creatures.)| +Indulgent Aristocrat|Shadows over Innistrad|118|U|{B}|Creature - Vampire|1|1|Lifelink${2}, Sacrifice a creature: Put a +1/+1 counter on each Vampire you control.| Macabre Waltz|Shadows over Innistrad|121|C|{1}{B}|Sorcery|||Return up to two target creature cards from your graveyard to your hand, then discard a card.| Markov Dreadknight|Shadows over Innistrad|122|R|{3}{B}{B}|Creature - Vampire Knight|3|3|Flying${2}{B}, Discard a card: Put two +1/+1 counters on Markhov Dreadknight.| +Merciless Resolve|Shadows over Innistrad|123|C|{2}{B}|Instant|||As an additional cost to cast Merciless Resolve, sacrifice a creature or land.$Draw two cards.| Mindwrack Demon|Shadows over Innistrad|124|M|{2}{B}{B}|Creature - Demon|4|5|Flying, trample$When Mindwrack Demon enters the battlefield, put the top four cards of your library into your graveyard.$Delirium — At the beginning of your upkeep, unless there are four or more card types among card in your graveyard, you lose 4 life.| +Olivia's Bloodsworn|Shadows over Innistrad|127|U|{1}{B}|Creature - Vampire Soldier|2|1|Flying$Olivia's Bloodsworn can't block.${R}: Target Vampire gains haste until end of turn.| Pick the Brain|Shadows over Innistrad|129|U|{2}{B}|Sorcery|||Target opponent reveals his or her hand. You choose a nonland card from it and exile that card.$Delirium — If there are four or more card types among cards in your graveyard, search that player's graveyard, hand, and library for any number of cards with the same name as the exiled card, exile those cards, then that player shuffles his or her library.| Relentless Dead|Shadows over Innistrad|131|M|{B}{B}|Creature - Zombie|2|2|Menace (This creature can't be blocked except by two or more creatures.)$When Relentless Dead dies, you may pay {B}. If you do, return it to its owner's hand.$When Relentless Dead dies, you may pay {X}. If you do, return another target Zombie creature card with converted mana cost X from your graveyard to the battlefield.| Shamble Back|Shadows over Innistrad|134|C|{B}|Sorcery|||Exile target creature card from a graveyard. Put a 2/2 black Zombie creature token onto the battlefield. You gain 2 life.| Sinister Concoction|Shadows over Innistrad|135|U|{B}|Enchantment|||{B}, Pay 1 life, Put the top card of your library into your graveyard, Discard a card, Sacrifice Sinister Concoction: Destroy target creature.| +Stromkirk Mentor|Shadows over Innistrad|137|C|{3}{B}|Creature - Vampire Soldier|4|2|When Stromkirk Mentor enters the battlefield, put a +1/+1 counter on another target Vampire you control.| To the Slaughter|Shadows over Innistrad|139|R|{2}{B}|Instant|||Target player sacrifices a creature or planeswalker.$Delirium — If there are four or more card types among cards in your graveyard, instead that player sacrifices a creature and a planeswalker.| Tooth Collector|Shadows over Innistrad|140|U|{2}{B}|Creature - Human Rogue|3|2|When Tooth Collector enters the battlefield, target creature an opponent controls gets -1/-1 until end of turn.${Delirium — At the beginning of each opponent's upkeep, if there are four or more card types among cards in your graveyard, target creature that player controls gets -1/-1 until end of turn.| +Twins of Maurer Estate|Shadows over Innistrad|142|C|{4}{B}|Creature - Vampire|3|5|Madness {2}{B} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| Breakneck Rider|Shadows over Innistrad|147a|U|{1}{R}{R}|Creature - Human Scout Werewolf|3|3|At the beginning of each upkeep, if no spells were cast last turn, transform Breakneck Rider.| Neck Breaker|Shadows over Innistrad|147b|U||Creature - Werewolf|4|3|Attacking creatures you control get +1/+0 and have trample.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Neck Breaker.| Dance with Devils|Shadows over Innistrad|150|U|{3}{R}|Instant|||Put two 1/1 red Devil creature tokens onto the battlefield. They have "When this creature dies, it deals 1 damage to target creature or player."| @@ -56999,8 +57004,9 @@ Devil's Playground|Shadows over Innistrad|151|R|{4}{R}{R}|Sorcery|||Put four 1/1 Ember-Eye Wolf|Shadows over Innistrad|154|C|{2}{R}|Creature - Wolf|1|2|Haste${1}{R}: Ember-Eye Wolf gets +2/+0 until end of turn.| Falkenrath Gorger|Shadows over Innistrad|155|R|{R}|Creature - Vampire Warrior|2|1|Each Vampire creature card you own that isn't on the battlefield has madness. Its madness cost is equal to its mana cost. (If you discard a card with madness, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| Fiery Temper|Shadows over Innistrad|156|C|{1}{R}{R}|Instant|||Fiery Temper deals 3 damage to target creature or player.$Madness {R} (If you discard this card, you may play it for its madness cost instead of putting it into your graveyard.)| -Incorrigible Youths|Shadows over Innistrad|166|U|{3}{R}{R}|Creature - Vampire|4|3|Haste$Madness {2}{R} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| Flameblade Angel|Shadows over Innistrad|157|R|{4}{R}{R}|Creature - Angel|4|4|Flying$Whenever a source an opponent controls deals damage to you or a permanent you control, you may have Flameblade Angel deal 1 damage to that source's controller.| +Harness the Storm|Shadows over Innistrad|163|R|{2}{R}|Enchantment|||Whenever you cast an instant or sorcery spell from your hand, you may cast target card with the same name as that spell from your graveyard.| +Incorrigible Youths|Shadows over Innistrad|166|U|{3}{R}{R}|Creature - Vampire|4|3|Haste$Madness {2}{R} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| Lightning Axe|Shadows over Innistrad|999|U|{R}|Instant|||As an additional cost to cast Lightning Axe, discard a card or pay {5}.$Lightning Axe deals 5 damage to target creature.| Magmatic Chasm|Shadows over Innistrad|172|C|{1}{R}|Sorcery|||Creatures without flying can't block this turn.| Ravenous Bloodseeker|Shadows over Innistrad|175|U|{1}{R}|Creature - Vampire Berserker|1|3|Discard a card: Ravenous Bloodseeker gets +2/-2 until end of turn.| @@ -57025,7 +57031,7 @@ Anguished Unmaking|Shadows over Innistrad|242|R|{1}{W}{B}|Instant|||Exile target Arlinn Kord|Shadows over Innistrad|243a|M|{2}{R}{G}|Planeswalker - Arlinn|||+1: Until end of turn, up to one target creature gets +2/+2 and gains vigilance and haste.$0: Put a 2/2 green Wolf creature token onto the battlefield. Transform Arlinn Kord.| Arlinn, Embraced by the Moon|Shadows over Innistrad|243b|M||Planeswalker - Arlinn|||+1: Creatures you control get +1/+1 and gain trample until end of turn.$-1: Arlinn, Embraced by the Moon deals 3 damage to target creature or player. Transform Arlinn, Embraced by the Moon.$-6: You get an emblem with "Creatures you control have haste and '{T}: This creature deals damage equal to its power to target creature or player.'"| Nahiri, the Harbinger|Shadows over Innistrad|247|M|{2}{R}{W}|Planeswalker - Nahiri|||+2: You may discard a card. If you do, draw a card.$-2: Exile target enchantment, tapped artifact, or tapped creature.$-8: Search your library for an artifact or creature card, put it onto the battlefield, then shuffle your library. It gains haste. Return it to your hand at the beginning of the next end step.| -Oliva, Mobilized for War|Shadows over Innistrad|248|M|{1}{B}{R}|Legendary Creature - Vampire Knight|3|3|Flying$Whenever another creature enters the battlefield under your control, you may discard a card. If you do, put a +1/+1 counter on that creature, it gains haste until end of turn, and it becomes a Vampire in addition to its other types.| +Olivia, Mobilized for War|Shadows over Innistrad|248|M|{1}{B}{R}|Legendary Creature - Vampire Knight|3|3|Flying$Whenever another creature enters the battlefield under your control, you may discard a card. If you do, put a +1/+1 counter on that creature, it gains haste until end of turn, and it becomes a Vampire in addition to its other types.| Sigarda, Heron's Grace|Shadows over Innistrad|250|M|{3}{G}{W}|Legendary Creature - Angel|4|5|Flying$You and Humans you control have hexproof.${2}, Exile a card from your graveyard: Put a 1/1 white Human Soldier creature token onto the battlefield.| Brain in a Jar|Shadows over Innistrad|252|R|{2}|Artifact|||{1}, {T}: Put a charge counter on Brain in a Jar, then you may cast an instant or sorcery card with converted mana costs equal to the number of charge counters on Brain in a Jar from your hand without paying its mana cost.${3}< {T}, Remove X charge counters from Brain in a Jar: Scry X.| Explosive Apparatus|Shadows over Innistrad|255|C|{1}|Artifact|||{3}, {T}, Sacrifice Explosive Apparatus: Explosive Apparatus deals 2 damage to target creature or player.| From 981665bcd007edcc23b238ff7d2cb403523bc8a3 Mon Sep 17 00:00:00 2001 From: rkfg Date: Thu, 17 Mar 2016 15:07:43 +0300 Subject: [PATCH 35/44] Remove adjust parameter. --- .../generator/RatioAdjustingSliderPanel.java | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java index b1c07c9acac..d7401fd06df 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java @@ -55,12 +55,10 @@ public class RatioAdjustingSliderPanel extends JPanel { // Slider stores its initial value to revert to when reset private int defaultValue; - private boolean adjust; - public JStorageSlider(int min, int max, int value, boolean adjust) { + public JStorageSlider(int min, int max, int value) { super(min, max, value); defaultValue = value; - this.adjust = adjust; setMinorTickSpacing(5); setMajorTickSpacing(10); setPaintTicks(true); @@ -72,10 +70,6 @@ public class RatioAdjustingSliderPanel extends JPanel { this.setValue(defaultValue); } - public boolean isAdjust() { - return adjust; - } - } private class AdjustingSliderGroup { @@ -92,10 +86,8 @@ public class RatioAdjustingSliderPanel extends JPanel { fireSliderChangedEvent((JStorageSlider) e.getSource()); } }); - if (slider.isAdjust()) { - adjustableCount++; - } } + adjustableCount = storageSliders.size() - 1; } public void fireSliderChangedEvent(JStorageSlider source) { @@ -108,7 +100,7 @@ public class RatioAdjustingSliderPanel extends JPanel { private void updateSliderPosition(JStorageSlider source) { int maximum = MAXIMUM; - int excess = 100; + int excess = MAXIMUM; int sign = 0; for (JStorageSlider slider : storageSliders) { excess -= slider.getValue(); @@ -122,7 +114,7 @@ public class RatioAdjustingSliderPanel extends JPanel { for (int i = storageSliders.size() - 1; i >= 0; i--) { JStorageSlider slider = storageSliders.get(i); int value = slider.getValue(); - if (slider != source && slider.isAdjust()) { + if (slider != source && maximum < MAXIMUM) { slider.setMaximum(maximum); if (excess >= addition) { value += addition * sign; @@ -154,9 +146,9 @@ public class RatioAdjustingSliderPanel extends JPanel { private void initPanel() { // Create three sliders with default values - creatureSlider = new JStorageSlider(0, MAXIMUM, DeckGeneratorPool.DEFAULT_CREATURE_PERCENTAGE, true); - nonCreatureSlider = new JStorageSlider(0, MAXIMUM, DeckGeneratorPool.DEFAULT_NON_CREATURE_PERCENTAGE, true); - landSlider = new JStorageSlider(0, MAXIMUM, DeckGeneratorPool.DEFAULT_LAND_PERCENTAGE, false); + creatureSlider = new JStorageSlider(0, MAXIMUM, DeckGeneratorPool.DEFAULT_CREATURE_PERCENTAGE); + nonCreatureSlider = new JStorageSlider(0, MAXIMUM, DeckGeneratorPool.DEFAULT_NON_CREATURE_PERCENTAGE); + landSlider = new JStorageSlider(0, MAXIMUM, DeckGeneratorPool.DEFAULT_LAND_PERCENTAGE); sg = new AdjustingSliderGroup(creatureSlider, nonCreatureSlider, landSlider); From ae66db20adb0f2ab367d41ad8bcb0a00c67ccdac Mon Sep 17 00:00:00 2001 From: rkfg Date: Thu, 17 Mar 2016 15:13:00 +0300 Subject: [PATCH 36/44] Adjust sliders on reset. --- .../mage/client/deck/generator/RatioAdjustingSliderPanel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java index d7401fd06df..eac35c2647e 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/RatioAdjustingSliderPanel.java @@ -215,6 +215,7 @@ public class RatioAdjustingSliderPanel extends JPanel { for (JStorageSlider slider : sg.getSliders()) { slider.resetDefault(); } + sg.updateSliderPosition(landSlider); } public int getCreaturePercentage() { From eb9747d0d1cc008f1edef305c96a8f1e1d2faff9 Mon Sep 17 00:00:00 2001 From: drmDev Date: Thu, 17 Mar 2016 08:21:36 -0400 Subject: [PATCH 37/44] tests for reported bug Rabid Elephant. typo fixes --- .../src/mage/sets/odyssey/RabidElephant.java | 2 +- .../cards/triggers/BecomesBlockedTest.java | 45 +++++++++++++++++++ .../dynamicvalue/MultipliedValue.java | 12 ++--- .../common/BlockedCreatureCount.java | 3 +- 4 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesBlockedTest.java diff --git a/Mage.Sets/src/mage/sets/odyssey/RabidElephant.java b/Mage.Sets/src/mage/sets/odyssey/RabidElephant.java index 7d4dac87c25..27a3afff097 100644 --- a/Mage.Sets/src/mage/sets/odyssey/RabidElephant.java +++ b/Mage.Sets/src/mage/sets/odyssey/RabidElephant.java @@ -55,7 +55,7 @@ public class RabidElephant extends CardImpl { this.toughness = new MageInt(4); // Whenever Rabid Elephant becomes blocked, it gets +2/+2 until end of turn for each creature blocking it. - DynamicValue value = new MultipliedValue(new BlockedCreatureCount(),2); + DynamicValue value = new MultipliedValue(new BlockedCreatureCount(), 2); Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn); effect.setText("it gets +2/+2 until end of turn for each creature blocking it"); this.addAbility(new BecomesBlockedTriggeredAbility(effect, false)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesBlockedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesBlockedTest.java new file mode 100644 index 00000000000..653f55bf82f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesBlockedTest.java @@ -0,0 +1,45 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.triggers; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class BecomesBlockedTest extends CardTestPlayerBase { + + /** + * Reported bug: + * There's something wrong with how Rabid Elephant is getting his +2/+2 bonus, + * it doesn't last until end of turn, but seems to be removed right after the blockers step. + */ + @Test + public void testRabidElephant() { + + // {4}{G} + // Whenever Rabid Elephant becomes blocked, it gets +2/+2 until end of turn for each creature blocking it. + addCard(Zone.BATTLEFIELD, playerA, "Rabid Elephant", 1); // 3/4 + + addCard(Zone.BATTLEFIELD, playerB, "Savannah Lions", 1); // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Hill Giant", 1); // 3/3 + + attack(1, playerA, "Rabid Elephant"); + block(1, playerB, "Savannah Lions", "Rabid Elephant"); + //block(1, playerB, "Hill Giant", "Rabid Elephant"); + + // test passes if PhaseStep ends at DECLARE_BLOCKERS + //setStopAt(1, PhaseStep.DECLARE_BLOCKERS); + setStopAt(1, PhaseStep.COMBAT_DAMAGE); + execute(); + + // blocked by 2 creatures, so gets +2/+2 twice, making it 7/8 + assertPowerToughness(playerA, "Rabid Elephant", 3, 4); + } +} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/MultipliedValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/MultipliedValue.java index 66c92a0809a..ddcd1aef4f1 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/MultipliedValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/MultipliedValue.java @@ -38,21 +38,21 @@ import mage.game.Game; public class MultipliedValue implements DynamicValue { private final DynamicValue value; - private final int multplier; + private final int multiplier; public MultipliedValue(DynamicValue value, int multiplier) { this.value = value.copy(); - this.multplier = multiplier; + this.multiplier = multiplier; } MultipliedValue(final MultipliedValue dynamicValue) { this.value = dynamicValue.value.copy(); - this.multplier = dynamicValue.multplier; + this.multiplier = dynamicValue.multiplier; } @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - return multplier * value.calculate(game, sourceAbility, effect); + return multiplier * value.calculate(game, sourceAbility, effect); } @Override @@ -63,10 +63,10 @@ public class MultipliedValue implements DynamicValue { @Override public String toString() { StringBuilder sb = new StringBuilder(); - if (multplier == 2) { + if (multiplier == 2) { sb.append("twice "); } else { - sb.append(multplier).append(" * "); + sb.append(multiplier).append(" * "); } return sb.append(value.toString()).toString(); } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/BlockedCreatureCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/BlockedCreatureCount.java index 78ac659e793..ac2f1f1dd7d 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/BlockedCreatureCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/BlockedCreatureCount.java @@ -50,8 +50,9 @@ public class BlockedCreatureCount implements DynamicValue { this(message, false); } - public BlockedCreatureCount(String message, boolean beyondTheFist) { + public BlockedCreatureCount(String message, boolean beyondTheFirst) { this.message = message; + this.beyondTheFirst = beyondTheFirst; } public BlockedCreatureCount(final BlockedCreatureCount dynamicValue) { From ea961d3cfc389011b8bf8f2b1249b76c70f4428f Mon Sep 17 00:00:00 2001 From: drmDev Date: Thu, 17 Mar 2016 08:28:28 -0400 Subject: [PATCH 38/44] whoops... fix test --- .../java/org/mage/test/cards/triggers/BecomesBlockedTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesBlockedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesBlockedTest.java index 653f55bf82f..27d6285088a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesBlockedTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/BecomesBlockedTest.java @@ -32,7 +32,7 @@ public class BecomesBlockedTest extends CardTestPlayerBase { attack(1, playerA, "Rabid Elephant"); block(1, playerB, "Savannah Lions", "Rabid Elephant"); - //block(1, playerB, "Hill Giant", "Rabid Elephant"); + block(1, playerB, "Hill Giant", "Rabid Elephant"); // test passes if PhaseStep ends at DECLARE_BLOCKERS //setStopAt(1, PhaseStep.DECLARE_BLOCKERS); @@ -40,6 +40,6 @@ public class BecomesBlockedTest extends CardTestPlayerBase { execute(); // blocked by 2 creatures, so gets +2/+2 twice, making it 7/8 - assertPowerToughness(playerA, "Rabid Elephant", 3, 4); + assertPowerToughness(playerA, "Rabid Elephant", 7, 8); } } From 72fad81bd56bf6ac9dadc5f589a12c97593c98e1 Mon Sep 17 00:00:00 2001 From: drmDev Date: Thu, 17 Mar 2016 10:33:40 -0400 Subject: [PATCH 39/44] Test for Breaker of Armies with Menace, reported bug. --- .../requirement/BlockRequirementTest.java | 39 ++++++++++++++++++- .../dynamicvalue/MultipliedValue.java | 12 +++--- .../common/BlockedCreatureCount.java | 2 +- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java index c277096383f..864eb03d275 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/requirement/BlockRequirementTest.java @@ -29,6 +29,7 @@ package org.mage.test.cards.requirement; import mage.constants.PhaseStep; import mage.constants.Zone; +import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -178,9 +179,45 @@ public class BlockRequirementTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); - // Hill giant is still alive and Played B looses 3 lives + // Hill giant is still alive and Played B loses 3 lives assertPermanentCount(playerA, "Hill Giant", 1); assertLife(playerB, 17); + } + + /** + * Reported bug: + * When Breaker of Armies is granted Menace and there is only 1 valid blocker, the game enters a state + * that cannot be continued. He must be blocked by all creatures that are able, however, with menace + * the only valid blocks would be by more than one creature, so the expected behavior is no blocks can be made. + */ + @Ignore + @Test + public void testBreakerOfArmiesWithMenace() { + // {8} + // All creatures able to block Breaker of Armies do so. + addCard(Zone.BATTLEFIELD, playerA, "Breaker of Armies", 1); // 10/8 + + // 3/3 Vanilla creature + addCard(Zone.BATTLEFIELD, playerB, "Hill Giant", 1); + + + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 8); + // {2}{B} Enchanted creature gets +2/+1 and has menace. + addCard(Zone.HAND, playerA, "Untamed Hunger", 1); + castSpell(1,PhaseStep.PRECOMBAT_MAIN, playerA, "Untamed Hunger", "Breaker of Armies"); + + attack(1, playerA, "Breaker of Armies"); + + // not allowed due to Breaker of Armies having menace + block(1, playerB, "Hill Giant", "Breaker of Armies"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + // Hill giant is still alive + assertPermanentCount(playerA, "Hill Giant", 1); + // Player B was unable to block, so goes down to 10 life + assertLife(playerB, 10); } } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/MultipliedValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/MultipliedValue.java index ddcd1aef4f1..19b9a1e12c7 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/MultipliedValue.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/MultipliedValue.java @@ -38,21 +38,21 @@ import mage.game.Game; public class MultipliedValue implements DynamicValue { private final DynamicValue value; - private final int multiplier; + private final int multplier; // should be renamed to multiplier but don't want to break your stuff public MultipliedValue(DynamicValue value, int multiplier) { this.value = value.copy(); - this.multiplier = multiplier; + this.multplier = multiplier; } MultipliedValue(final MultipliedValue dynamicValue) { this.value = dynamicValue.value.copy(); - this.multiplier = dynamicValue.multiplier; + this.multplier = dynamicValue.multplier; } @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - return multiplier * value.calculate(game, sourceAbility, effect); + return multplier * value.calculate(game, sourceAbility, effect); } @Override @@ -63,10 +63,10 @@ public class MultipliedValue implements DynamicValue { @Override public String toString() { StringBuilder sb = new StringBuilder(); - if (multiplier == 2) { + if (multplier == 2) { sb.append("twice "); } else { - sb.append(multiplier).append(" * "); + sb.append(multplier).append(" * "); } return sb.append(value.toString()).toString(); } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/BlockedCreatureCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/BlockedCreatureCount.java index ac2f1f1dd7d..a18cfcd9e33 100644 --- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/BlockedCreatureCount.java +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/BlockedCreatureCount.java @@ -52,7 +52,7 @@ public class BlockedCreatureCount implements DynamicValue { public BlockedCreatureCount(String message, boolean beyondTheFirst) { this.message = message; - this.beyondTheFirst = beyondTheFirst; + //this.beyondTheFirst = beyondTheFirst; this was never set in the original, so not setting here just in case ?? } public BlockedCreatureCount(final BlockedCreatureCount dynamicValue) { From 4bf8226a43e0a2fea9c9b69e2258c6df41259316 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 17 Mar 2016 15:44:38 +0100 Subject: [PATCH 40/44] * Fixed bug with P/T boost triggered by blocking creatures not lasting until end of turn (e.g. Rabid Elephant). --- .../sets/guildpact/BeastmastersMagemark.java | 13 ++++++------ .../src/mage/sets/iceage/JohtullWurm.java | 2 +- .../src/mage/sets/invasion/SparringGolem.java | 4 ++-- .../mage/sets/legions/BerserkMurlodont.java | 9 +++++---- .../src/mage/sets/mirage/JungleWurm.java | 2 +- .../sets/ninthedition/ElvishBerserker.java | 2 +- .../src/mage/sets/odyssey/RabidElephant.java | 4 ++-- .../mage/sets/stronghold/SpinedSliver.java | 4 ++-- .../src/mage/sets/urzaslegacy/GangOfElk.java | 2 +- .../abilities/keyword/RampageAbility.java | 20 +++++++++---------- 10 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Mage.Sets/src/mage/sets/guildpact/BeastmastersMagemark.java b/Mage.Sets/src/mage/sets/guildpact/BeastmastersMagemark.java index 51659bd9544..5a72d7f0684 100644 --- a/Mage.Sets/src/mage/sets/guildpact/BeastmastersMagemark.java +++ b/Mage.Sets/src/mage/sets/guildpact/BeastmastersMagemark.java @@ -55,13 +55,14 @@ import mage.target.common.TargetCreaturePermanent; * @author Markedagain */ public class BeastmastersMagemark extends CardImpl { - + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Creatures you control that are enchanted"); + static { filter.add(new EnchantedPredicate()); filter.add(new ControllerPredicate(TargetController.YOU)); } - + public BeastmastersMagemark(UUID ownerId) { super(ownerId, 80, "Beastmaster's Magemark", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); this.expansionSetCode = "GPT"; @@ -74,13 +75,13 @@ public class BeastmastersMagemark extends CardImpl { Ability ability = new EnchantAbility(auraTarget.getTargetName()); this.addAbility(ability); // Creatures you control that are enchanted get +1/+1. - ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostAllEffect(1,1, Duration.WhileOnBattlefield, filter, false)); + ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostAllEffect(1, 1, Duration.WhileOnBattlefield, filter, false)); this.addAbility(ability); // Whenever a creature you control that's enchanted becomes blocked, it gets +1/+1 until end of turn for each creature blocking it. BlockedCreatureCount value = new BlockedCreatureCount(); - Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn, true); effect.setText("it gets +1/+1 until end of turn for each creature blocking it"); - this.addAbility(new BecomesBlockedAllTriggeredAbility(effect, false,filter,false)); + this.addAbility(new BecomesBlockedAllTriggeredAbility(effect, false, filter, false)); } public BeastmastersMagemark(final BeastmastersMagemark card) { @@ -91,4 +92,4 @@ public class BeastmastersMagemark extends CardImpl { public BeastmastersMagemark copy() { return new BeastmastersMagemark(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/iceage/JohtullWurm.java b/Mage.Sets/src/mage/sets/iceage/JohtullWurm.java index d1fd3b5b7af..d70517d6874 100644 --- a/Mage.Sets/src/mage/sets/iceage/JohtullWurm.java +++ b/Mage.Sets/src/mage/sets/iceage/JohtullWurm.java @@ -55,7 +55,7 @@ public class JohtullWurm extends CardImpl { // Whenever Johtull Wurm becomes blocked, it gets -2/-1 until end of turn for each creature blocking it beyond the first. DynamicValue blockedCreatureCount = new BlockedCreatureCount("each creature blocking it beyond the first", true); - Effect effect = new BoostSourceEffect(new MultipliedValue(blockedCreatureCount, -2), new MultipliedValue(blockedCreatureCount, -1), Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(new MultipliedValue(blockedCreatureCount, -2), new MultipliedValue(blockedCreatureCount, -1), Duration.EndOfTurn, true); effect.setText("it gets -2/-1 until end of turn for each creature blocking it beyond the first"); this.addAbility(new BecomesBlockedTriggeredAbility(effect, false)); } diff --git a/Mage.Sets/src/mage/sets/invasion/SparringGolem.java b/Mage.Sets/src/mage/sets/invasion/SparringGolem.java index 98304b10f88..90cdc61ad8a 100644 --- a/Mage.Sets/src/mage/sets/invasion/SparringGolem.java +++ b/Mage.Sets/src/mage/sets/invasion/SparringGolem.java @@ -53,7 +53,7 @@ public class SparringGolem extends CardImpl { // Whenever Sparring Golem becomes blocked, it gets +1/+1 until end of turn for each creature blocking it. BlockedCreatureCount value = new BlockedCreatureCount(); - Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn, true); effect.setText("it gets +1/+1 until end of turn for each creature blocking it"); this.addAbility(new BecomesBlockedTriggeredAbility(effect, false)); } @@ -66,4 +66,4 @@ public class SparringGolem extends CardImpl { public SparringGolem copy() { return new SparringGolem(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/legions/BerserkMurlodont.java b/Mage.Sets/src/mage/sets/legions/BerserkMurlodont.java index dcddb71fa2d..687d8be5a51 100644 --- a/Mage.Sets/src/mage/sets/legions/BerserkMurlodont.java +++ b/Mage.Sets/src/mage/sets/legions/BerserkMurlodont.java @@ -30,9 +30,9 @@ package mage.sets.legions; import java.util.UUID; import mage.MageInt; import mage.abilities.common.BecomesBlockedAllTriggeredAbility; +import mage.abilities.dynamicvalue.common.BlockedCreatureCount; import mage.abilities.effects.Effect; import mage.abilities.effects.common.continuous.BoostSourceEffect; -import mage.abilities.dynamicvalue.common.BlockedCreatureCount; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; @@ -47,10 +47,11 @@ import mage.filter.predicate.mageobject.SubtypePredicate; public class BerserkMurlodont extends CardImpl { private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a Beast"); + static { filter.add(new SubtypePredicate("Beast")); } - + public BerserkMurlodont(UUID ownerId) { super(ownerId, 117, "Berserk Murlodont", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{4}{G}"); this.expansionSetCode = "LGN"; @@ -60,9 +61,9 @@ public class BerserkMurlodont extends CardImpl { // Whenever a Beast becomes blocked, it gets +1/+1 until end of turn for each creature blocking it. BlockedCreatureCount value = new BlockedCreatureCount(); - Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn, true); effect.setText("it gets +1/+1 until end of turn for each creature blocking it"); - this.addAbility(new BecomesBlockedAllTriggeredAbility(effect, false,filter,false)); + this.addAbility(new BecomesBlockedAllTriggeredAbility(effect, false, filter, false)); } public BerserkMurlodont(final BerserkMurlodont card) { diff --git a/Mage.Sets/src/mage/sets/mirage/JungleWurm.java b/Mage.Sets/src/mage/sets/mirage/JungleWurm.java index 7a66ccdecad..4eda2a767bf 100644 --- a/Mage.Sets/src/mage/sets/mirage/JungleWurm.java +++ b/Mage.Sets/src/mage/sets/mirage/JungleWurm.java @@ -56,7 +56,7 @@ public class JungleWurm extends CardImpl { // Whenever Jungle Wurm becomes blocked, it gets -1/-1 until end of turn for each creature blocking it beyond the first. BlockedCreatureCount blockedCreatureCount = new BlockedCreatureCount("each creature blocking it beyond the first", true); DynamicValue value = new MultipliedValue(blockedCreatureCount, -1); - Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn, true); effect.setText("it gets -1/-1 until end of turn for each creature blocking it beyond the first"); this.addAbility(new BecomesBlockedTriggeredAbility(effect, false)); } diff --git a/Mage.Sets/src/mage/sets/ninthedition/ElvishBerserker.java b/Mage.Sets/src/mage/sets/ninthedition/ElvishBerserker.java index 924d718d4cd..a992950f502 100644 --- a/Mage.Sets/src/mage/sets/ninthedition/ElvishBerserker.java +++ b/Mage.Sets/src/mage/sets/ninthedition/ElvishBerserker.java @@ -54,7 +54,7 @@ public class ElvishBerserker extends CardImpl { // Whenever Elvish Berserker becomes blocked, it gets +1/+1 until end of turn for each creature blocking it. BlockedCreatureCount value = new BlockedCreatureCount(); - Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn, true); effect.setText("it gets +1/+1 until end of turn for each creature blocking it"); this.addAbility(new BecomesBlockedTriggeredAbility(effect, false)); } diff --git a/Mage.Sets/src/mage/sets/odyssey/RabidElephant.java b/Mage.Sets/src/mage/sets/odyssey/RabidElephant.java index 27a3afff097..4cb21b27265 100644 --- a/Mage.Sets/src/mage/sets/odyssey/RabidElephant.java +++ b/Mage.Sets/src/mage/sets/odyssey/RabidElephant.java @@ -56,7 +56,7 @@ public class RabidElephant extends CardImpl { // Whenever Rabid Elephant becomes blocked, it gets +2/+2 until end of turn for each creature blocking it. DynamicValue value = new MultipliedValue(new BlockedCreatureCount(), 2); - Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn, true); effect.setText("it gets +2/+2 until end of turn for each creature blocking it"); this.addAbility(new BecomesBlockedTriggeredAbility(effect, false)); } @@ -69,4 +69,4 @@ public class RabidElephant extends CardImpl { public RabidElephant copy() { return new RabidElephant(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/stronghold/SpinedSliver.java b/Mage.Sets/src/mage/sets/stronghold/SpinedSliver.java index cf3f572246f..990976bb93e 100644 --- a/Mage.Sets/src/mage/sets/stronghold/SpinedSliver.java +++ b/Mage.Sets/src/mage/sets/stronghold/SpinedSliver.java @@ -66,14 +66,14 @@ public class SpinedSliver extends CardImpl { // Whenever a Sliver becomes blocked, that Sliver gets +1/+1 until end of turn for each creature blocking it. BlockedCreatureCount value = new BlockedCreatureCount(); - Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn, true); effect.setText("it gets +1/+1 until end of turn for each creature blocking it"); Ability ability = new BecomesBlockedByCreatureTriggeredAbility(effect, false); this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GainAbilityAllEffect(ability, Duration.WhileOnBattlefield, filter, "Whenever a Sliver becomes blocked, that Sliver gets +1/+1 until end of turn for each creature blocking it."))); - } + } public SpinedSliver(final SpinedSliver card) { super(card); diff --git a/Mage.Sets/src/mage/sets/urzaslegacy/GangOfElk.java b/Mage.Sets/src/mage/sets/urzaslegacy/GangOfElk.java index c3ccea99283..7715bd8dc3d 100644 --- a/Mage.Sets/src/mage/sets/urzaslegacy/GangOfElk.java +++ b/Mage.Sets/src/mage/sets/urzaslegacy/GangOfElk.java @@ -57,7 +57,7 @@ public class GangOfElk extends CardImpl { // Whenever Gang of Elk becomes blocked, it gets +2/+2 until end of turn for each creature blocking it. DynamicValue value = new MultipliedValue(new BlockedCreatureCount(), 2); - Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn); + Effect effect = new BoostSourceEffect(value, value, Duration.EndOfTurn, true); effect.setText("it gets +2/+2 until end of turn for each creature blocking it"); this.addAbility(new BecomesBlockedTriggeredAbility(effect, false)); } diff --git a/Mage/src/main/java/mage/abilities/keyword/RampageAbility.java b/Mage/src/main/java/mage/abilities/keyword/RampageAbility.java index f399d80f7c3..29fde94fe9e 100644 --- a/Mage/src/main/java/mage/abilities/keyword/RampageAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/RampageAbility.java @@ -35,22 +35,21 @@ import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.constants.Duration; import mage.game.Game; import mage.game.combat.CombatGroup; -import mage.game.events.GameEvent; /** * * @author LoneFox */ - public class RampageAbility extends BecomesBlockedTriggeredAbility { private final String rule; public RampageAbility(int amount) { super(null, false); - rule = "rampage " + amount; + rule = "rampage " + amount + "(Whenever this creature becomes blocked, it gets +" + + amount + "/+" + amount + " until end of turn for each creature blocking it beyond the first.)"; RampageValue rv = new RampageValue(amount); - this.addEffect(new BoostSourceEffect(rv, rv, Duration.EndOfTurn)); + this.addEffect(new BoostSourceEffect(rv, rv, Duration.EndOfTurn, true)); } public RampageAbility(final RampageAbility ability) { @@ -69,7 +68,6 @@ public class RampageAbility extends BecomesBlockedTriggeredAbility { } } - class RampageValue implements DynamicValue { private final int amount; @@ -89,11 +87,10 @@ class RampageValue implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - int count = 0; - for(CombatGroup combatGroup : game.getCombat().getGroups()) { - if(combatGroup.getAttackers().contains(sourceAbility.getSourceId())) { - int blockers = combatGroup.getBlockers().size(); - return blockers > 1 ? (blockers - 1) * amount : 0; + for (CombatGroup combatGroup : game.getCombat().getGroups()) { + if (combatGroup.getAttackers().contains(sourceAbility.getSourceId())) { + int blockers = combatGroup.getBlockers().size(); + return blockers > 1 ? (blockers - 1) * amount : 0; } } return 0; @@ -101,6 +98,7 @@ class RampageValue implements DynamicValue { @Override public String getMessage() { - return "Rampage " + amount; + return "rampage " + amount + "(Whenever this creature becomes blocked, it gets +" + + amount + "/+" + amount + " until end of turn for each creature blocking it beyond the first.)"; } } From d7d9c31ff338f6c90f5d81207ce4250ad76fd7b4 Mon Sep 17 00:00:00 2001 From: fireshoes Date: Thu, 17 Mar 2016 11:35:53 -0500 Subject: [PATCH 41/44] [SOI] Updated 3/17 spoilers. --- Utils/mtg-cards-data.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 2419200f9a3..e61d1927ddc 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -57012,9 +57012,11 @@ Magmatic Chasm|Shadows over Innistrad|172|C|{1}{R}|Sorcery|||Creatures without f Ravenous Bloodseeker|Shadows over Innistrad|175|U|{1}{R}|Creature - Vampire Berserker|1|3|Discard a card: Ravenous Bloodseeker gets +2/-2 until end of turn.| Sanguinary Mage|Shadows over Innistrad|178|C|{1}{R}|Creature - Vampire Wizard|1|3|Prowess| Structural Distortion|Shadows over Innistrad|185|C|{3}{R}|Sorcery|||Exile target artifact or land. Structural Distortion deals 2 damage to that permanent's controller.| +Vessel of VOlatility|Shadows over Innistrad|189|C|{1}{R}|Enchantment|||{1}{R}, Sacrifice Vessel of Volatility: Add {R}{R}{R}{R} to your mana pool.| Voldaren Duelist|Shadows over Innistrad|191|C|{3}{R}|Creature - Vampire Warrior|3|2|Haste$When Voldaren Duelist enters the battlefield, target creature can't block this turn.| Wolf of Devil's Breach|Shadows over Innistrad|192|M|{3}{R}{R}|Creature - Elemental Wolf|5|5|Whenever Wolf of Devil's Breach attacks, you may pay {1}{R} and discard a card. If you do, Wolf of Devil's Breach deals damage to target creature or planeswalker equal to the discarded card's converted mana cost.| Clip Wings|Shadows over Innistrad|197|C|{1}{G}|Instant|||Each opponent sacrifices a creature with flying.| +Deathcap Cultivator|Shadows over Innistrad|202|R|{1}{G}|Creature - Human Druid|2|1|{T}: Add {B} or {G} to your mana pool.$Delirium — Deathcap Cultivator has deathtouch as long as there are four or more card types among cards in your graveyard.| Duskwatch Recruiter|Shadows over Innistrad|203a|U|{1}{G}|Creature - Human Warrior Werewolf|2|2|{2}{G}: Look at the top three cards of your library. You may reveal a creature card from among them and put it into your hand. Put the rest on the bottom of your library in any order.$At the beginning of each upkeep, if no spells were cast last turn, transform Duskwatch Recruiter.| Krallenhorde Howler|Shadows over Innistrad|203b|U||Creature - Werewolf|3|3|Creature spells you cast cost {1} less to cast.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Krallenhorde Howler.| Groundskeeper|Shadows over Innistrad|208|U|{G}|Creature - Human Druid|1|1|{1}{G}: Return target basic land card from your graveyard to your hand.| @@ -57026,6 +57028,7 @@ Sage of Ancient Lore|Shadows over Innistrad|225a|R|{4}{G}|Creature - Human Shama Werewolf of Ancient Hunger|Shadows over Innistrad|225b|R||Creature - Werewolf|0|0|Vigilance, trample$Werewolf of Ancient Hunger's power and toughness are each equal to the total number of cards in all players' hands.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Werewolf of Ancient Hunger.| Soul Swallower|Shadows over Innistrad|230|R|{2}{G}{G}|Creature - Wurm|3|3|Trample$Delirium — At the beginning of your upkeep, if there are four or more card types among cards in your graveyard, put three +1/+1 counters on Soul Swallower.| Stoic Builder|Shadows over Innistrad|231|C|{2}{G}|Creature - Human|2|3|When Stoic Builder enters the battlefield, you may return target land card from your graveyard to your hand.| +Traverse the Ulvenwald|Shadows over Innistrad|234|R|{G}|Sorcery|||Search your library for a basic land card, reveal it, put it into your hand, then shuffle your library.$Delirium — If there are four or more card types among cards in your graveyard, instead search your library for a creature or land card, reveal it, put it into your hand, then shuffle your library.| Watcher in the Web|Shadows over Innistrad|239|C|{4}{G}|Creature - Spider|2|5|Reach (This creature can block creature with flying.)$Watcher in the Web can block an additional seven creatures each combat.| Anguished Unmaking|Shadows over Innistrad|242|R|{1}{W}{B}|Instant|||Exile target nonland permanent. You lose 3 life.| Arlinn Kord|Shadows over Innistrad|243a|M|{2}{R}{G}|Planeswalker - Arlinn|||+1: Until end of turn, up to one target creature gets +2/+2 and gains vigilance and haste.$0: Put a 2/2 green Wolf creature token onto the battlefield. Transform Arlinn Kord.| @@ -57033,6 +57036,7 @@ Arlinn, Embraced by the Moon|Shadows over Innistrad|243b|M||Planeswalker - Arlin Nahiri, the Harbinger|Shadows over Innistrad|247|M|{2}{R}{W}|Planeswalker - Nahiri|||+2: You may discard a card. If you do, draw a card.$-2: Exile target enchantment, tapped artifact, or tapped creature.$-8: Search your library for an artifact or creature card, put it onto the battlefield, then shuffle your library. It gains haste. Return it to your hand at the beginning of the next end step.| Olivia, Mobilized for War|Shadows over Innistrad|248|M|{1}{B}{R}|Legendary Creature - Vampire Knight|3|3|Flying$Whenever another creature enters the battlefield under your control, you may discard a card. If you do, put a +1/+1 counter on that creature, it gains haste until end of turn, and it becomes a Vampire in addition to its other types.| Sigarda, Heron's Grace|Shadows over Innistrad|250|M|{3}{G}{W}|Legendary Creature - Angel|4|5|Flying$You and Humans you control have hexproof.${2}, Exile a card from your graveyard: Put a 1/1 white Human Soldier creature token onto the battlefield.| +Sorin, Grim Nemesis|Shadows over Innistrad|251|M|{4}{W}{B}|Planeswalker - Sorin|||+1: Reveal the top card of your library and put that card into your hand. Each opponent loses life equal to its converted mana cost.$-X: Sorin, Grim Nemesis deals X damage to target creature or planeswalker and you gain X life.$-9: Put a number of 1/1 black Vampire Knight creature tokens with lifelink onto the battlefield equal to the highest life total among all players.| Brain in a Jar|Shadows over Innistrad|252|R|{2}|Artifact|||{1}, {T}: Put a charge counter on Brain in a Jar, then you may cast an instant or sorcery card with converted mana costs equal to the number of charge counters on Brain in a Jar from your hand without paying its mana cost.${3}< {T}, Remove X charge counters from Brain in a Jar: Scry X.| Explosive Apparatus|Shadows over Innistrad|255|C|{1}|Artifact|||{3}, {T}, Sacrifice Explosive Apparatus: Explosive Apparatus deals 2 damage to target creature or player.| Magnifying Glass|Shadows over Innistrad|258|U|{3}|Artifact|||{T}: Add {C} to your mana pool.${4}, {T}: Investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| @@ -57040,11 +57044,14 @@ Neglected Heirloom|Shadows over Innistrad|260a|U|{1}|Artifact - Equipment|||Equi Ashmouth Blade|Shadows over Innistrad|260b|U||Artifact - Equipment|||Equipped creature gets +3/+3.$Equip {3}| Shard of Broken Glass|Shadows over Innistrad|262|C|{1}|Artifact - Equipment|||Equipped creature gets +1/+0.$Whenever equipped creature attacks, you may put the top two cards of your library into your graveyard.$Equip {1} ({1}: Attach to target creature you control. Equip only as a sorcery.)| Tamiyo's Journal|Shadows over Innistrad|265|R|{5}|Legendary Artifact|||At the beginning of your upkeep, investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")${T}, Sacrifice three Clues: Search your library for a card and put that card into your hand. Then shuffle your library.| +Drownyard Temple|Shadows over Innistrad|271|R||Land|||{T}: Add {C} to your mana pool.${3}: Return Drownyard Temple from your graveyard to the battlefield tapped.| Forsaken Sanctuary|Shadows over Innistrad|273|U||Land|||Forsaken Sanctuary enters the battlefield tapped.${T}: Add {W} or {B} to your mana pool.| Foul Orchard|Shadows over Innistrad|275|U||Land|||Foul Orchard enters the battlefield tapped.${T}: Add {B} or {G} to your mana pool.| Highland Lake|Shadows over Innistrad|277|U||Land|||Highland Lake enters the battlefield tapped.${T}: Add {U} or {R} to your mana pool.| Stone Quarry|Shadows over Innistrad|279|U||Land|||Stone Quarry enters the battlefield tapped.${T}: Add {R} or {W} to your mana pool.| Woodland Stream|Shadows over Innistrad|282|U||Land|||Woodland Stream enters the battlefield tapped.${T}: Add {G} or {U} to your mana pool.| Warped Landscape|Shadows over Innistrad|280|C||Land|||{T}: Add {C} to your mana pool.${2}, {T}, Sacrifice Warped Landscape: Search your library for a basic land card and put it onto the battlefield tapped. Then shuffle your library.| +Westvale Abbey|Shadows over Innistrad|281a|R||Land|||{T}: Add {C} to your mana pool.${5}, {T}, Pay 1 life: Put a 1/1 white and black Human Cleric creature token onto the battlefield.${5}, {T}, Sacrifice five creatures: Transform Westvale Abbey and untap it.| +Ormendahl, Profane Prince|Shadows over Innistrad|281b|R||Legendary Creature - Demon|9|7|Flying, lifelink, indestructible, haste| Force of Will|Eternal Masters|49|M|{3}{U}{U}|Instant|||You may pay 1 life and exile a blue card from your hand rather than pay Force of Will's mana cost.$Counter target spell.| Wasteland|Eternal Masters|248|R||Land|||{T}: Add {C} to your mana pool.${T}, Sacrifice Wasteland: Destroy target nonbasic land.| \ No newline at end of file From 418b7b1e8e0f6487f8f0a94c1c8eb13e2e2f019c Mon Sep 17 00:00:00 2001 From: fireshoes Date: Thu, 17 Mar 2016 16:26:41 -0500 Subject: [PATCH 42/44] [SOI] Updated additional 3/17 spoilers. Added some more cards. --- .../BearerOfOverwhelmingTruths.java | 72 ++++++ .../shadowsoverinnistrad/DaringSleuth.java | 105 +++++++++ .../DeathcapCultivator.java | 77 +++++++ .../DiregrafColossus.java | 125 ++++++++++ .../shadowsoverinnistrad/DrownyardTemple.java | 65 ++++++ .../shadowsoverinnistrad/JacesScrutiny.java | 65 ++++++ .../OngoingInvestigation.java | 138 +++++++++++ .../OrmendahlProfanePrince.java | 76 ++++++ .../SilverfurPartisan.java | 124 ++++++++++ .../SorinGrimNemesis.java | 218 ++++++++++++++++++ .../shadowsoverinnistrad/TamiyosJournal.java | 4 +- .../shadowsoverinnistrad/TrailOfEvidence.java | 70 ++++++ .../TraverseTheUlvenwald.java | 83 +++++++ .../VesselOfVolatility.java | 66 ++++++ .../shadowsoverinnistrad/WestvaleAbbey.java | 103 +++++++++ .../effects/keyword/InvestigateEffect.java | 1 + Utils/mtg-cards-data.txt | 10 +- 17 files changed, 1399 insertions(+), 3 deletions(-) create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/BearerOfOverwhelmingTruths.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/DaringSleuth.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/DeathcapCultivator.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/DiregrafColossus.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrownyardTemple.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/JacesScrutiny.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/OngoingInvestigation.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/OrmendahlProfanePrince.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/SilverfurPartisan.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/SorinGrimNemesis.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/TrailOfEvidence.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/TraverseTheUlvenwald.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/VesselOfVolatility.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/WestvaleAbbey.java diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/BearerOfOverwhelmingTruths.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/BearerOfOverwhelmingTruths.java new file mode 100644 index 00000000000..144b5c6c434 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/BearerOfOverwhelmingTruths.java @@ -0,0 +1,72 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.keyword.ProwessAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; + +/** + * + * @author fireshoes + */ +public class BearerOfOverwhelmingTruths extends CardImpl { + + public BearerOfOverwhelmingTruths(UUID ownerId) { + super(ownerId, 54, "Bearer of Overwhelming Truths", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, ""); + this.expansionSetCode = "SOI"; + this.subtype.add("Human"); + this.subtype.add("Wizard"); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + this.color.setBlue(true); + + // this card is the second face of double-faced card + this.nightCard = true; + + // Prowess + this.addAbility(new ProwessAbility()); + + // Whenever Bearer of Overwhelming Truths deals combat damage to a player, investigate. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new InvestigateEffect(), false)); + } + + public BearerOfOverwhelmingTruths(final BearerOfOverwhelmingTruths card) { + super(card); + } + + @Override + public BearerOfOverwhelmingTruths copy() { + return new BearerOfOverwhelmingTruths(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DaringSleuth.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DaringSleuth.java new file mode 100644 index 00000000000..f87e984175e --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DaringSleuth.java @@ -0,0 +1,105 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; + +/** + * + * @author fireshoes + */ +public class DaringSleuth extends CardImpl { + + public DaringSleuth(UUID ownerId) { + super(ownerId, 54, "Daring Sleuth", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{1}{U}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Human"); + this.subtype.add("Rogue"); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + this.canTransform = true; + this.secondSideCard = new BearerOfOverwhelmingTruths(ownerId); + + // When you sacrifice a Clue, transform Daring Sleuth. + this.addAbility(new TransformAbility()); + this.addAbility(new DaringSleuthTriggeredAbility()); + } + + public DaringSleuth(final DaringSleuth card) { + super(card); + } + + @Override + public DaringSleuth copy() { + return new DaringSleuth(this); + } +} + +class DaringSleuthTriggeredAbility extends TriggeredAbilityImpl { + + public DaringSleuthTriggeredAbility() { + super(Zone.BATTLEFIELD, new TransformSourceEffect(true)); + } + + public DaringSleuthTriggeredAbility(final DaringSleuthTriggeredAbility ability) { + super(ability); + } + + @Override + public DaringSleuthTriggeredAbility copy() { + return new DaringSleuthTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.SACRIFICED_PERMANENT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return event.getPlayerId().equals(this.getControllerId()) + && game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD).getSubtype().contains("Clue"); + } + + @Override + public String getRule() { + return "When you sacrifice a Clue, " + super.getRule(); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DeathcapCultivator.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DeathcapCultivator.java new file mode 100644 index 00000000000..ceaff0f3286 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DeathcapCultivator.java @@ -0,0 +1,77 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.mana.BlackManaAbility; +import mage.abilities.mana.GreenManaAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; + +/** + * + * @author fireshoes + */ +public class DeathcapCultivator extends CardImpl { + + public DeathcapCultivator(UUID ownerId) { + super(ownerId, 202, "Deathcap Cultivator", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{1}{G}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Human"); + this.subtype.add("Druid"); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // {T}: Add {B} or {G} to your mana pool. + this.addAbility(new BlackManaAbility()); + this.addAbility(new GreenManaAbility()); + + // Delirium — Deathcap Cultivator has deathtouch as long as there are four or more card types among cards in your graveyard. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, + new ConditionalContinuousEffect(new GainAbilitySourceEffect(DeathtouchAbility.getInstance(), Duration.WhileOnBattlefield), + new DeliriumCondition(), "Delirium — {this} has deathtouch as long as there are four or more card types among cards in your graveyard"))); + } + + public DeathcapCultivator(final DeathcapCultivator card) { + super(card); + } + + @Override + public DeathcapCultivator copy() { + return new DeathcapCultivator(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DiregrafColossus.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DiregrafColossus.java new file mode 100644 index 00000000000..f9fa8b4338b --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DiregrafColossus.java @@ -0,0 +1,125 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.counters.CounterType; +import mage.filter.FilterSpell; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.ZombieToken; +import mage.players.Player; + +/** + * + * @author fireshoes + */ +public class DiregrafColossus extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("a Zombie spell"); + + static { + filter.add(new SubtypePredicate("Zombie")); + } + + public DiregrafColossus(UUID ownerId) { + super(ownerId, 107, "Diregraf Colossus", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{2}{B}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Zombie"); + this.subtype.add("Giant"); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Diregraf Colossus enters the battlefield with a +1/+1 counter on it for each Zombie card in your graveyard. + this.addAbility(new EntersBattlefieldAbility(new DiregrafColossusEffect(), "with a +1/+1 counter on it for each Zombie card in your graveyard")); + + // Whenever you cast a Zombie spell, put a 2/2 black Zombie creature token onto the battlefield tapped. + this.addAbility(new SpellCastControllerTriggeredAbility(new CreateTokenEffect(new ZombieToken(), 1, true, false), filter, false)); + + } + + public DiregrafColossus(final DiregrafColossus card) { + super(card); + } + + @Override + public DiregrafColossus copy() { + return new DiregrafColossus(this); + } +} + +class DiregrafColossusEffect extends OneShotEffect { + + private static final FilterCreatureCard filter = new FilterCreatureCard(); + + static { + filter.add(new SubtypePredicate("Zombie")); + } + + public DiregrafColossusEffect() { + super(Outcome.BoostCreature); + staticText = "{this} enters the battlefield with a +1/+1 counter on it for each Zombie card in your graveyard"; + } + + public DiregrafColossusEffect(final DiregrafColossusEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanentEntering(source.getSourceId()); + if (permanent != null && player != null) { + int amount = 0; + amount += player.getGraveyard().count(filter, game); + if (amount > 0) { + permanent.addCounters(CounterType.P1P1.createInstance(amount), game); + } + return true; + } + return false; + } + + @Override + public DiregrafColossusEffect copy() { + return new DiregrafColossusEffect(this); + } + +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrownyardTemple.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrownyardTemple.java new file mode 100644 index 00000000000..33ed6106a2d --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/DrownyardTemple.java @@ -0,0 +1,65 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; + +/** + * + * @author fireshoes + */ +public class DrownyardTemple extends CardImpl { + + public DrownyardTemple(UUID ownerId) { + super(ownerId, 271, "Drownyard Temple", Rarity.RARE, new CardType[]{CardType.LAND}, ""); + this.expansionSetCode = "SOI"; + + // {T}: Add {C} to your mana pool. + this.addAbility(new ColorlessManaAbility()); + + // {3}: Return Drownyard Temple from your graveyard to the battlefield tapped. + this.addAbility(new SimpleActivatedAbility(Zone.GRAVEYARD, new ReturnSourceFromGraveyardToBattlefieldEffect(true), new GenericManaCost(3))); + } + + public DrownyardTemple(final DrownyardTemple card) { + super(card); + } + + @Override + public DrownyardTemple copy() { + return new DrownyardTemple(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/JacesScrutiny.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/JacesScrutiny.java new file mode 100644 index 00000000000..9522110db92 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/JacesScrutiny.java @@ -0,0 +1,65 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author fireshoes + */ +public class JacesScrutiny extends CardImpl { + + public JacesScrutiny(UUID ownerId) { + super(ownerId, 70, "Jace's Scrutiny", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{U}"); + this.expansionSetCode = "SOI"; + + // Target creature gets -4/-0 until end of turn. + getSpellAbility().addEffect(new BoostTargetEffect(-4, -0, Duration.EndOfTurn)); + getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // Investigate + getSpellAbility().addEffect(new InvestigateEffect()); + } + + public JacesScrutiny(final JacesScrutiny card) { + super(card); + } + + @Override + public JacesScrutiny copy() { + return new JacesScrutiny(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OngoingInvestigation.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OngoingInvestigation.java new file mode 100644 index 00000000000..6868c903295 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OngoingInvestigation.java @@ -0,0 +1,138 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.ExileFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author fireshoes + */ +public class OngoingInvestigation extends CardImpl { + + public OngoingInvestigation(UUID ownerId) { + super(ownerId, 77, "Ongoing Investigation", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + this.expansionSetCode = "SOI"; + + // Whenever one or more creatures you control deal combat damage to a player, investigate. + this.addAbility(new OngoingInvestigationTriggeredAbility()); + + // {1}{G}, Exile a creature card from your graveyard: Investigate. You gain 2 life. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new InvestigateEffect(), new ManaCostsImpl("{1}{G}")); + ability.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(new FilterCreatureCard("a creature card from your graveyard")))); + ability.addEffect(new GainLifeEffect(2)); + this.addAbility(ability); + } + + public OngoingInvestigation(final OngoingInvestigation card) { + super(card); + } + + @Override + public OngoingInvestigation copy() { + return new OngoingInvestigation(this); + } +} + +class OngoingInvestigationTriggeredAbility extends TriggeredAbilityImpl { + + private boolean madeDamage = false; + private Set damagedPlayers = new HashSet(); + + public OngoingInvestigationTriggeredAbility() { + super(Zone.BATTLEFIELD, new InvestigateEffect(), false); + } + + public OngoingInvestigationTriggeredAbility(final OngoingInvestigationTriggeredAbility ability) { + super(ability); + this.madeDamage = ability.madeDamage; + this.damagedPlayers = new HashSet(); + this.damagedPlayers.addAll(ability.damagedPlayers); + } + + @Override + public OngoingInvestigationTriggeredAbility copy() { + return new OngoingInvestigationTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.DAMAGED_PLAYER || event.getType() == EventType.COMBAT_DAMAGE_STEP_POST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == EventType.DAMAGED_PLAYER) { + DamagedPlayerEvent damageEvent = (DamagedPlayerEvent) event; + Permanent p = game.getPermanent(event.getSourceId()); + if (damageEvent.isCombatDamage() && p != null && p.getControllerId().equals(this.getControllerId())) { + madeDamage = true; + damagedPlayers.add(event.getPlayerId()); + } + } + if (event.getType().equals(EventType.COMBAT_DAMAGE_STEP_POST)) { + if (madeDamage) { + Set damagedPlayersCopy = new HashSet(); + damagedPlayersCopy.addAll(damagedPlayers); + for(Effect effect: this.getEffects()) { + effect.setValue("damagedPlayers", damagedPlayersCopy); + } + damagedPlayers.clear(); + madeDamage = false; + return true; + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever one or more creatures you control deal combat damage to a player, " + super.getRule(); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OrmendahlProfanePrince.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OrmendahlProfanePrince.java new file mode 100644 index 00000000000..4fbb8c0321a --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/OrmendahlProfanePrince.java @@ -0,0 +1,76 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; + +/** + * + * @author fireshoes + */ +public class OrmendahlProfanePrince extends CardImpl { + + public OrmendahlProfanePrince(UUID ownerId) { + super(ownerId, 281, "Ormendahl, Profane Prince", Rarity.RARE, new CardType[]{CardType.CREATURE}, ""); + this.expansionSetCode = "SOI"; + this.supertype.add("Legendary"); + this.subtype.add("Demon"); + this.power = new MageInt(9); + this.toughness = new MageInt(7); + this.color.setBlack(true); + + // this card is the second face of double-faced card + this.nightCard = true; + + // Flying + this.addAbility(FlyingAbility.getInstance()); + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + // Indestructible + this.addAbility(IndestructibleAbility.getInstance()); + // Haste + this.addAbility(HasteAbility.getInstance()); + } + + public OrmendahlProfanePrince(final OrmendahlProfanePrince card) { + super(card); + } + + @Override + public OrmendahlProfanePrince copy() { + return new OrmendahlProfanePrince(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SilverfurPartisan.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SilverfurPartisan.java new file mode 100644 index 00000000000..c5330e0b32d --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SilverfurPartisan.java @@ -0,0 +1,124 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.token.WolfToken; +import mage.game.stack.Spell; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author fireshoes + */ +public class SilverfurPartisan extends CardImpl { + + public SilverfurPartisan(UUID ownerId) { + super(ownerId, 226, "Silverfur Partisan", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{2}{G}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Wolf"); + this.subtype.add("Warrior"); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever a Wolf or Werewolf you control becomes the target of an instant or sorcery spell, put a 2/2 green Wolf creature token onto the battlefield. + this.addAbility(new CreaturesYouControlBecomesTargetTriggeredAbility(new CreateTokenEffect(new WolfToken()))); + } + + public SilverfurPartisan(final SilverfurPartisan card) { + super(card); + } + + @Override + public SilverfurPartisan copy() { + return new SilverfurPartisan(this); + } +} + +class CreaturesYouControlBecomesTargetTriggeredAbility extends TriggeredAbilityImpl { + + public CreaturesYouControlBecomesTargetTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect); + } + + public CreaturesYouControlBecomesTargetTriggeredAbility(final CreaturesYouControlBecomesTargetTriggeredAbility ability) { + super(ability); + } + + @Override + public CreaturesYouControlBecomesTargetTriggeredAbility copy() { + return new CreaturesYouControlBecomesTargetTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.TARGETED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null && permanent.getControllerId().equals(this.controllerId) && (permanent.getSubtype().contains("Wolf") || permanent.getSubtype().contains("Werewolf"))) { + MageObject object = game.getObject(event.getSourceId()); + if (object != null && object instanceof Spell) { + Card c = (Spell) object; + if (c.getCardType().contains(CardType.INSTANT) || c.getCardType().contains(CardType.SORCERY)) { + if (getTargets().size() == 0) { + for (Effect effect : getEffects()) { + effect.setTargetPointer(new FixedTarget(event.getTargetId())); + } + } + return true; + } + } + } + return false; + } + + @Override + public String getRule() { + return "Whenever a Wolf or Werewolf you control becomes the target of an instant or sorcery spell, put a 2/2 green Wolf creature token onto the battlefield."; + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SorinGrimNemesis.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SorinGrimNemesis.java new file mode 100644 index 00000000000..5dcf9c09972 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/SorinGrimNemesis.java @@ -0,0 +1,218 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlanswalkerEntersWithLoyalityCountersAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.PayVariableLoyaltyCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.token.Token; +import mage.players.Player; +import mage.players.PlayerList; +import mage.target.common.TargetCreatureOrPlaneswalker; + +/** + * + * @author fireshoes + */ +public class SorinGrimNemesis extends CardImpl { + + public SorinGrimNemesis(UUID ownerId) { + super(ownerId, 251, "Sorin, Grim Nemesis", Rarity.MYTHIC, new CardType[]{CardType.PLANESWALKER}, "{4}{W}{B}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Sorin"); + + this.addAbility(new PlanswalkerEntersWithLoyalityCountersAbility(6)); + + // +1: Reveal the top card of your library and put that card into your hand. Each opponent loses life equal to its converted mana cost. + this.addAbility(new LoyaltyAbility(new SorinGrimNemesisRevealEffect(), 1)); + + // -X: Sorin, Grim Nemesis deals X damage to target creature or planeswalker and you gain X life. + Ability ability = new LoyaltyAbility(new DamageTargetEffect(SorinXValue.getDefault())); + ability.addTarget(new TargetCreatureOrPlaneswalker()); + ability.addEffect(new GainLifeEffect(SorinXValue.getDefault())); + this.addAbility(ability); + + // -9: Put a number of 1/1 black Vampire Knight creature tokens with lifelink onto the battlefield equal to the highest life total among all players. + this.addAbility(new LoyaltyAbility(new SorinTokenEffect(), -9)); + } + + public SorinGrimNemesis(final SorinGrimNemesis card) { + super(card); + } + + @Override + public SorinGrimNemesis copy() { + return new SorinGrimNemesis(this); + } +} + +class SorinGrimNemesisRevealEffect extends OneShotEffect { + + public SorinGrimNemesisRevealEffect() { + super(Outcome.DrawCard); + this.staticText = "reveal the top card of your library and put that card into your hand. Each opponent loses life equal to that card's converted mana cost"; + } + + public SorinGrimNemesisRevealEffect(final SorinGrimNemesisRevealEffect effect) { + super(effect); + } + + @Override + public SorinGrimNemesisRevealEffect copy() { + return new SorinGrimNemesisRevealEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + + if (player.getLibrary().size() > 0) { + Card card = player.getLibrary().getFromTop(game); + Cards cards = new CardsImpl(); + cards.add(card); + player.revealCards("Sorin, Grim Nemesis", cards, game); + + if (card != null && + card.moveToZone(Zone.HAND, source.getSourceId(), game, false)) { + for (UUID playerId : game.getOpponents(source.getControllerId())) { + if (card.getManaCost().convertedManaCost() > 0) { + Player opponent = game.getPlayer(playerId); + if (opponent != null) { + opponent.loseLife(card.getManaCost().convertedManaCost(), game); + } + } + } + return true; + } + } + return false; + } +} + +class SorinXValue implements DynamicValue { + + private static final SorinXValue defaultValue = new SorinXValue(); + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + for (Cost cost : sourceAbility.getCosts()) { + if (cost instanceof PayVariableLoyaltyCost) { + return ((PayVariableLoyaltyCost) cost).getAmount(); + } + } + return 0; + } + + @Override + public DynamicValue copy() { + return defaultValue; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String toString() { + return "X"; + } + + public static SorinXValue getDefault() { + return defaultValue; + } +} + +class SorinTokenEffect extends OneShotEffect { + SorinTokenEffect() { + super(Outcome.GainLife); + staticText = "Put a number of 1/1 black Vampire Knight creature tokens with lifelink onto the battlefield equal to the highest life total among all players"; + } + + SorinTokenEffect(final SorinTokenEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + int maxLife = 0; + PlayerList playerList = game.getState().getPlayersInRange(source.getControllerId(), game); + for (UUID pid : playerList) { + Player p = game.getPlayer(pid); + if (p != null) { + if (maxLife < p.getLife()) { + maxLife = p.getLife(); + } + } + } + new CreateTokenEffect(new VampireKnightToken(), maxLife).apply(game, source); + return true; + } + + @Override + public SorinTokenEffect copy() { + return new SorinTokenEffect(this); + } +} + +class VampireKnightToken extends Token { + + public VampireKnightToken() { + super("Vampire Knight", "1/1 black Vampire Knight creature token with lifelink"); + cardType.add(CardType.CREATURE); + subtype.add("Vampire"); + subtype.add("Knight"); + color.setBlack(true); + power = new MageInt(1); + toughness = new MageInt(1); + addAbility(LifelinkAbility.getInstance()); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TamiyosJournal.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TamiyosJournal.java index 1b2b8562913..6f048f5815e 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TamiyosJournal.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TamiyosJournal.java @@ -41,7 +41,7 @@ import mage.constants.Rarity; import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.mageobject.NamePredicate; +import mage.filter.predicate.mageobject.SubtypePredicate; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetControlledPermanent; @@ -54,7 +54,7 @@ public class TamiyosJournal extends CardImpl { private static final FilterControlledPermanent filter = new FilterControlledPermanent("three Clues"); static { - filter.add(new NamePredicate("Clue")); + filter.add(new SubtypePredicate("Clue")); } public TamiyosJournal(UUID ownerId) { diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TrailOfEvidence.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TrailOfEvidence.java new file mode 100644 index 00000000000..d7357eb0963 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TrailOfEvidence.java @@ -0,0 +1,70 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; + +/** + * + * @author fireshoes + */ +public class TrailOfEvidence extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("an instant or sorcery spell"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.INSTANT), + new CardTypePredicate(CardType.SORCERY))); + } + + public TrailOfEvidence(UUID ownerId) { + super(ownerId, 93, "Trail of Evidence", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); + this.expansionSetCode = "SOI"; + + // Whenever you cast an instant or sorcery spell, investigate. + this.addAbility(new SpellCastControllerTriggeredAbility(new InvestigateEffect(), filter, false)); + } + + public TrailOfEvidence(final TrailOfEvidence card) { + super(card); + } + + @Override + public TrailOfEvidence copy() { + return new TrailOfEvidence(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TraverseTheUlvenwald.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TraverseTheUlvenwald.java new file mode 100644 index 00000000000..ecb6973b5b4 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/TraverseTheUlvenwald.java @@ -0,0 +1,83 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.abilities.condition.InvertCondition; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.FilterCard; +import mage.filter.common.FilterBasicLandCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author fireshoes + */ +public class TraverseTheUlvenwald extends CardImpl { + + private static final FilterCard filter = new FilterCard("a creature or land card"); + + static { + filter.add(Predicates.or(new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.LAND))); + } + + public TraverseTheUlvenwald(UUID ownerId) { + super(ownerId, 234, "Traverse the Ulvenwald", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{G}"); + this.expansionSetCode = "SOI"; + + // Search your library for a basic land card, reveal it, put it into your hand, then shuffle your library. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(1, 1, new FilterBasicLandCard()), true), + new InvertCondition(DeliriumCondition.getInstance()), + "Search your library for a basic land card, reveal it, put it into your hand, then shuffle your library.")); + + // Delirium — If there are four or more card types among cards in your graveyard, instead search your library + // for a creature or land card, reveal it, put it into your hand, then shuffle your library. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(1, 1, filter), true), + DeliriumCondition.getInstance(), + "
Delirium — If there are four or more card types among cards in your graveyard, instead search your library for a creature or land card, " + + "reveal it, put it into your hand, then shuffle your library.")); + } + + public TraverseTheUlvenwald(final TraverseTheUlvenwald card) { + super(card); + } + + @Override + public TraverseTheUlvenwald copy() { + return new TraverseTheUlvenwald(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/VesselOfVolatility.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/VesselOfVolatility.java new file mode 100644 index 00000000000..4fc7e2c3417 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/VesselOfVolatility.java @@ -0,0 +1,66 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.BasicManaEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; + +/** + * + * @author fireshoes + */ +public class VesselOfVolatility extends CardImpl { + + public VesselOfVolatility(UUID ownerId) { + super(ownerId, 189, "Vessel of Volatility", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); + this.expansionSetCode = "SOI"; + + // {1}{R}, Sacrifice Vessel of Volatility: Add {R}{R}{R}{R} to your mana pool. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BasicManaEffect(Mana.RedMana(4)), new ManaCostsImpl("{1}{R}")); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + public VesselOfVolatility(final VesselOfVolatility card) { + super(card); + } + + @Override + public VesselOfVolatility copy() { + return new VesselOfVolatility(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WestvaleAbbey.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WestvaleAbbey.java new file mode 100644 index 00000000000..e788d3b00e3 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/WestvaleAbbey.java @@ -0,0 +1,103 @@ +/* + * 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 mage.sets.shadowsoverinnistrad; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.UntapSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.abilities.mana.ColorlessManaAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.permanent.token.Token; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author fireshoes + */ +public class WestvaleAbbey extends CardImpl { + + public WestvaleAbbey(UUID ownerId) { + super(ownerId, 281, "Westvale Abbey", Rarity.RARE, new CardType[]{CardType.LAND}, ""); + this.expansionSetCode = "SOI"; + + this.canTransform = true; + this.secondSideCard = new OrmendahlProfanePrince(ownerId); + + // {T}: Add {C} to your mana pool. + this.addAbility(new ColorlessManaAbility()); + + // {5}, {T}, Pay 1 life: Put a 1/1 white and black Human Cleric creature token onto the battlefield. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new HumanClericToken()), new GenericManaCost(5)); + ability.addCost(new TapSourceCost()); + ability.addCost(new PayLifeCost(1)); + this.addAbility(ability); + + // {5}, {T}, Sacrifice five creatures: Transform Westvale Abbey and untap it. + this.addAbility(new TransformAbility()); + ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TransformSourceEffect(true), new GenericManaCost(5)); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(5, 5, new FilterControlledCreaturePermanent("five creatures"), true))); + ability.addEffect(new UntapSourceEffect()); + this.addAbility(ability); + } + + public WestvaleAbbey(final WestvaleAbbey card) { + super(card); + } + + @Override + public WestvaleAbbey copy() { + return new WestvaleAbbey(this); + } +} +class HumanClericToken extends Token { + + public HumanClericToken() { + super("Human Cleric", "1/1 white and black Human Cleric creature token"); + cardType.add(CardType.CREATURE); + subtype.add("Human"); + subtype.add("Cleric"); + color.setWhite(true); + color.setBlack(true); + power = new MageInt(1); + toughness = new MageInt(1); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java index bd29fc58200..b9b4a33b739 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java @@ -43,6 +43,7 @@ class ClueArtifactToken extends Token { super("Clue", "colorless Clue artifact token onto the battlefield with \"{2}, Sacrifice this artifact: Draw a card.\""); this.setOriginalExpansionSetCode("SOI"); this.cardType.add(CardType.ARTIFACT); + this.subtype.add("Clue"); // {2}, Sacrifice this artifact: Draw a card. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), new GenericManaCost(2)); diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index e61d1927ddc..46992f1bdfd 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -56957,15 +56957,19 @@ Topplegeist|Shadows over Innistrad|45|U|{W}|Creature - Spirit|1|1|Flying$When To Aberrant Researcher|Shadows over Innistrad|49a|U|{3}{U}|Creature - Human Insect|3|2|Flying$At the beginning of your upkeep, put the top card of your library into your graveyard. If it's an instant or sorcery card, transform Aberrant Researcher.| Perfected Form|Shadows over Innistrad|49b|U||Creature - Insect Horror|5|4|Flying| Compelling Deterrence|Shadows over Innistrad|52|U|{1}{U}|Instant|||Return target nonland permanent to its owner's hand. Then that player discards a card if you control a Zombie.| +Daring Sleuth|Shadows over Innistrad|54a|U|{1}{U}|Creature - Human Rogue|2|1|When you sacrifice a Clue, transform Daring Sleuth.| +Bearer of Overwhelming Truths|Shadows over Innistrad|54b|U||Creature - Human Wizard|3|2|Prowess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)$Whenever Bearer of Overwhelming Truths deals combat damage to a player, investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| Epiphany at the Drownyard|Shadows over Innistrad|59|R|{X}{U}|Instant|||Reveal the top X plus one cards of your library and separate them into two pilse. An opponent chooses one of those piles. Put that pile into your hand and the other into your graveyard.| Furtive Homunculus|Shadows over Innistrad|64|C|{1}{U}|Creature - Homunculus|2|1|Skulk (This creature can't be blocked by creatures with greater power.)| Geralf's Masterpiece|Shadows over Innistrad|65|M|{3}{U}{U}|Creature - Zombie Horror|7|7|Flying$Geralf's Masterpiece gets -1/-1 for each card in your hand.${3}{U}, Discard three cards: Return Geralf's Masterpiece from your graveyard to the battlefield tapped.| Invasive Surgery|Shadows over Innistrad|68|U|{U}|Instant|||Counter target sorcery spell.$Delirium — If there are four or more card types among cards in your graveyard, search the graveyard, hand, and library of that spell's controller for any number of cards with the same name as that spell, exile those cards, then that player shuffles his or her library.| Jace, Unraveler of Secrets|Shadows over Innistrad|69|M|{3}{U}{U}|Planeswalker - Jace|||+1: Scry 1, then draw a card.$-2: Return target creature to its owner's hand.$-8: You get an emblem with "Whenever an opponent casts his or her first spell each turn, counter that spell."| +Jace's Scrutiny|Shadows over Innistrad|70|C|{1}{U}|Instant|||Target creature gets -4/-0 until end of turn.$Investigate (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| Just the Wind|Shadows over Innistrad|71|C|{1}{U}|Instant|||Return target creature to its owner's hand.$Madness {U} (If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)| Lamplighter of Selhoff|Shadows over Innistrad|72|C|{4}{U}|Creature - Zombie Horror|3|5|When Lamplighter of Selhoff enters the battlefield, if you control another Zombie, you may draw a card. If you do, discard a card.| Nephalia Moondrakes|Shadows over Innistrad|75|R|{5}{U}{U}|Creature - Drake|5|5|Flying$When Nephalia Moondrakes enters the battlefield, target creature gains flying until end of turn.${4}{U}{U}, Exile Nephalia Moondrakes from your graveyard: Creatures you control gain flying until end of turn.| Niblis of Dusk|Shadows over Innistrad|76|C|{2}{U}|Creature - Spirit|2|1|Flying$Prowess| +Ongoing Investigation|Shadows over Innistrad|77|U|{1}{U}|Enchantment|||Whenever one or more creatures you control deal combat damage to a player, investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")${1}{G}, Exile a creature card from your graveyard: Investigate. You gain 2 life.| Pieces of the Puzzle|Shadows over Innistrad|78|C|{2}{U}|Sorcery|||Reveal the top five cards of your library. Put up to two instant and/or sorcery cards from among them into your hand and the rest into your graveyard.| Pore Over the Pages|Shadows over Innistrad|79|U|{3}{U}{U}|Sorcery|||Draw three cards, untap up to two lands, then discard a card.| Rattlechains|Shadows over Innistrad|81|R|{1}{U}|Creature - Spirit|2|1|Flash$Flying$When Rattlechains enters the battlefield, target spirit gains hexproof until end of turn.$You may cast spirit cards as though they had flash.| @@ -56975,8 +56979,10 @@ Persistent Nightmare|Shadows over Innistrad|88b|M||Creature - Nightmare|1|1|Skul Stitched Mangler|Shadows over Innistrad|89|C|{2}{U}|Creature - Zombie Horror|2|3|Stitched Mangler enters the battlefield tapped.$When Stitched Mangler enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.| Thing in the Ice|Shadows over Innistrad|92a|R|{1}{U}|Creature - Horror|0|4|Defender$Thing in the Ice enters the battlefield with four ice counters on it.$Whenever you cast an instant or sorcery spell, remove an ice counter from Thing in the Ice. Then if it has no ice counters on it, transform it.| Awoken Horror|Shadows over Innistrad|92b|R||Creature - Kraken Horror|7|8|When this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands.| +Trail of Evidence|Shadows over Innistrad|93|U|{2}{U}|Enchantment|||Whenever you cast an instant or sorcery spell, investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")| Crow of Dark Tidings|Shadows over Innistrad|105|C|{2}{B}|Creature - Zombie Bird|2|2|Flying$When Crow of Dark Tidings enters the battlefield or dies, put the top two cards of your library into your graveyard.| Dead Weight|Shadows over Innistrad|106|C|{B}|Enchantment - Aura|||Enchant creature$Enchanted creature gets -2/-2.| +Diregraf Colossus|Shadows over Innistrad|107|R|{2}{B}|Creature - Zombie Giant|2|2|Diregraf Colossus enters the battlefield with a +1/+1 counter on it for each Zombie card in your graveyard.$Whenever you cast a Zombie spell, put a 2/2 black Zombie creature token onto the battlefield tapped.| Elusive Tormentor|Shadows over Innistrad|108a|R|{2}{B}{B}|Creature - Vampire Wizard|4|4|{1}, Discard a card: Transform Elusive Tormentor.| Insidious Mist|Shadows over Innistrad|108b|R||Creature - Elemental|0|1|Hexproof, indestructible$Insideous Mist can't block and can't be blocked.$Whenever Insideous Mist attacks and isn't blocked, you may pay {2}{B}. If you do, transform it.| Farbog Revenant|Shadows over Innistrad|110|C|{2}{B}|Creature - Spirit|1|3|Skulk (This creature can't be blocked by creature with greater power.)$Lifelink (Damage dealt by this creature also causes you to gain that much life.)| @@ -57012,9 +57018,10 @@ Magmatic Chasm|Shadows over Innistrad|172|C|{1}{R}|Sorcery|||Creatures without f Ravenous Bloodseeker|Shadows over Innistrad|175|U|{1}{R}|Creature - Vampire Berserker|1|3|Discard a card: Ravenous Bloodseeker gets +2/-2 until end of turn.| Sanguinary Mage|Shadows over Innistrad|178|C|{1}{R}|Creature - Vampire Wizard|1|3|Prowess| Structural Distortion|Shadows over Innistrad|185|C|{3}{R}|Sorcery|||Exile target artifact or land. Structural Distortion deals 2 damage to that permanent's controller.| -Vessel of VOlatility|Shadows over Innistrad|189|C|{1}{R}|Enchantment|||{1}{R}, Sacrifice Vessel of Volatility: Add {R}{R}{R}{R} to your mana pool.| +Vessel of Volatility|Shadows over Innistrad|189|C|{1}{R}|Enchantment|||{1}{R}, Sacrifice Vessel of Volatility: Add {R}{R}{R}{R} to your mana pool.| Voldaren Duelist|Shadows over Innistrad|191|C|{3}{R}|Creature - Vampire Warrior|3|2|Haste$When Voldaren Duelist enters the battlefield, target creature can't block this turn.| Wolf of Devil's Breach|Shadows over Innistrad|192|M|{3}{R}{R}|Creature - Elemental Wolf|5|5|Whenever Wolf of Devil's Breach attacks, you may pay {1}{R} and discard a card. If you do, Wolf of Devil's Breach deals damage to target creature or planeswalker equal to the discarded card's converted mana cost.| +Briarbridge Patrol|Shadows over Innistrad|195|U|{3}{G}|Creature - Human Warrior|3|3|Whenever Briarbridge Patrol deals damage to one or more creatures, investigate. (Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")$At the beginning of each end step, if you sacrificed three or more Clues this turn, you may put a creature card from your hand onto the battlefield.|3:52 PM 3/17/2016 Clip Wings|Shadows over Innistrad|197|C|{1}{G}|Instant|||Each opponent sacrifices a creature with flying.| Deathcap Cultivator|Shadows over Innistrad|202|R|{1}{G}|Creature - Human Druid|2|1|{T}: Add {B} or {G} to your mana pool.$Delirium — Deathcap Cultivator has deathtouch as long as there are four or more card types among cards in your graveyard.| Duskwatch Recruiter|Shadows over Innistrad|203a|U|{1}{G}|Creature - Human Warrior Werewolf|2|2|{2}{G}: Look at the top three cards of your library. You may reveal a creature card from among them and put it into your hand. Put the rest on the bottom of your library in any order.$At the beginning of each upkeep, if no spells were cast last turn, transform Duskwatch Recruiter.| @@ -57026,6 +57033,7 @@ Pack Guardian|Shadows over Innistrad|221|U|{2}{G}{G}|Creature - Wolf Spirit|4|3| Quilled Wolf|Shadows over Innistrad|222|C|{1}{G}|Creature - Wolf|2|2|{5}{G}: Quilled Wolf gets +4/+4 until end of turn.| Sage of Ancient Lore|Shadows over Innistrad|225a|R|{4}{G}|Creature - Human Shaman Werewolf|0|0|Sage of Ancient Lore's power and toughness are each equal to the number of cards in your hand.$When Sage of Ancient Lore enters the battlefield, draw a card.$At the beginning of each upkeep, if no spells were cast last turn, transform Sage of Ancient Lore.| Werewolf of Ancient Hunger|Shadows over Innistrad|225b|R||Creature - Werewolf|0|0|Vigilance, trample$Werewolf of Ancient Hunger's power and toughness are each equal to the total number of cards in all players' hands.$At the beginning of each upkeep, if a player cast two or more spells last turn, transform Werewolf of Ancient Hunger.| +Silverfur Partisan|Shadows over Innistrad|226|R|{2}{G}|Creature - Wolf Warrior|2|2|Trample$Whenever a Wolf or Werewolf you control becomes the target of an instant or sorcery spell, put a 2/2 green Wolf creature token onto the battlefield.| Soul Swallower|Shadows over Innistrad|230|R|{2}{G}{G}|Creature - Wurm|3|3|Trample$Delirium — At the beginning of your upkeep, if there are four or more card types among cards in your graveyard, put three +1/+1 counters on Soul Swallower.| Stoic Builder|Shadows over Innistrad|231|C|{2}{G}|Creature - Human|2|3|When Stoic Builder enters the battlefield, you may return target land card from your graveyard to your hand.| Traverse the Ulvenwald|Shadows over Innistrad|234|R|{G}|Sorcery|||Search your library for a basic land card, reveal it, put it into your hand, then shuffle your library.$Delirium — If there are four or more card types among cards in your graveyard, instead search your library for a creature or land card, reveal it, put it into your hand, then shuffle your library.| From 35f8df408cb5546b1fb7b6767190feac19cf09da Mon Sep 17 00:00:00 2001 From: spjspj Date: Fri, 18 Mar 2016 23:42:21 +1100 Subject: [PATCH 43/44] spjspj - Implement Reconnaissance (EXO) --- .../src/mage/sets/exodus/Reconnaissance.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/exodus/Reconnaissance.java diff --git a/Mage.Sets/src/mage/sets/exodus/Reconnaissance.java b/Mage.Sets/src/mage/sets/exodus/Reconnaissance.java new file mode 100644 index 00000000000..c2010240dff --- /dev/null +++ b/Mage.Sets/src/mage/sets/exodus/Reconnaissance.java @@ -0,0 +1,119 @@ +/* + * 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 mage.sets.exodus; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author spjspj + */ +public class Reconnaissance extends CardImpl { + + public Reconnaissance(UUID ownerId) { + super(ownerId, 17, "Reconnaissance", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{W}"); + this.expansionSetCode = "EXO"; + + // {0}: Remove target attacking creature you control from combat and untap it. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReconnaissanceRemoveFromCombatEffect(), new ManaCostsImpl("{0}")); + this.addAbility(ability); + } + + public Reconnaissance(final Reconnaissance card) { + super(card); + } + + @Override + public Reconnaissance copy() { + return new Reconnaissance(this); + } +} + +class ReconnaissanceRemoveFromCombatEffect extends OneShotEffect { + + public ReconnaissanceRemoveFromCombatEffect() { + super(Outcome.Benefit); + this.staticText = "Remove target attacking creature from combat and untap it"; + } + + public ReconnaissanceRemoveFromCombatEffect(final ReconnaissanceRemoveFromCombatEffect effect) { + super(effect); + } + + @Override + public ReconnaissanceRemoveFromCombatEffect copy() { + return new ReconnaissanceRemoveFromCombatEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + List attackers = game.getCombat().getAttackers(); + Player player = game.getPlayer(source.getControllerId()); + + if (!attackers.isEmpty() && player != null) { + List uuidPredicates = new ArrayList<>(); + for (UUID creatureId : attackers) { + uuidPredicates.add(new PermanentIdPredicate(creatureId)); + } + + FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("attacking creature controlled by you"); + filter.add(Predicates.or(uuidPredicates)); + + Target target = new TargetControlledCreaturePermanent(0, 1, filter, false); + if (target.canChoose(source.getSourceId(), player.getId(), game)) { + player.choose(Outcome.Benefit, target, source.getSourceId(), game); + Permanent creature = game.getPermanent(target.getFirstTarget()); + if (creature != null) { + creature.removeFromCombat(game); + creature.untap(game); + return true; + } + } + } + return false; + } +} From 38f63d7fa0062c01e0817a60913b185e4369c87b Mon Sep 17 00:00:00 2001 From: jeff Date: Fri, 18 Mar 2016 16:31:54 -0500 Subject: [PATCH 44/44] - Added 2 requested cards: Jester's Scepter and Mana Maze. --- .../mage/sets/coldsnap/JestersScepter.java | 322 ++++++++++++++++++ .../src/mage/sets/invasion/ManaMaze.java | 153 +++++++++ 2 files changed, 475 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/coldsnap/JestersScepter.java create mode 100644 Mage.Sets/src/mage/sets/invasion/ManaMaze.java diff --git a/Mage.Sets/src/mage/sets/coldsnap/JestersScepter.java b/Mage.Sets/src/mage/sets/coldsnap/JestersScepter.java new file mode 100644 index 00000000000..aa447e4ccb3 --- /dev/null +++ b/Mage.Sets/src/mage/sets/coldsnap/JestersScepter.java @@ -0,0 +1,322 @@ +/* + * 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 mage.sets.coldsnap; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.Cards; +import mage.cards.SplitCard; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetPlayer; +import mage.target.TargetSpell; +import mage.target.common.TargetCardInExile; +import mage.util.CardUtil; + +/** + * + * @author jeffwadsworth + */ +public class JestersScepter extends CardImpl { + + public JestersScepter(UUID ownerId) { + super(ownerId, 137, "Jester's Scepter", Rarity.RARE, new CardType[]{CardType.ARTIFACT}, "{3}"); + this.expansionSetCode = "CSP"; + + // When Jester's Scepter enters the battlefield, exile the top five cards of target player's library face down. + Ability ability = new EntersBattlefieldTriggeredAbility(new JestersScepterEffect(), false); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + + // You may look at those cards for as long as they remain exiled. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new JestersScepterLookAtCardEffect())); + + // {2}, {tap}, Put a card exiled with Jester's Scepter into its owner's graveyard: Counter target spell if it has the same name as that card. + Ability ability2 = new SimpleActivatedAbility(Zone.BATTLEFIELD, new JestersScepterCounterEffect(), new ManaCostsImpl("{2}")); + ability2.addCost(new TapSourceCost()); + ability2.addCost(new JestersScepterCost()); + ability2.addTarget(new TargetSpell()); + this.addAbility(ability2); + + } + + public JestersScepter(final JestersScepter card) { + super(card); + } + + @Override + public JestersScepter copy() { + return new JestersScepter(this); + } +} + +class JestersScepterEffect extends OneShotEffect { + + public JestersScepterEffect() { + super(Outcome.DrawCard); + staticText = "exile the top five cards of target player's library face down"; + } + + public JestersScepterEffect(final JestersScepterEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Player targetedPlayer = game.getPlayer(source.getFirstTarget()); + MageObject sourceObject = game.getObject(source.getSourceId()); + if (controller != null + && targetedPlayer != null + && sourceObject != null) { + if (targetedPlayer.getLibrary().size() > 0) { + Set cardsToExile = targetedPlayer.getLibrary().getTopCards(game, 5); + for (Card card : cardsToExile) { + if (card.moveToExile(CardUtil.getCardExileZoneId(game, source), new StringBuilder(sourceObject.getName()).toString(), source.getSourceId(), game)) { + card.setFaceDown(true, game); + } + } + } + return true; + } + return false; + } + + @Override + public JestersScepterEffect copy() { + return new JestersScepterEffect(this); + } +} + +class TargetCardInJestersScepterExile extends TargetCard { + + public TargetCardInJestersScepterExile(UUID CardId) { + super(1, 1, Zone.EXILED, new FilterCard("card exiled with Jester's Scepter")); + } + + public TargetCardInJestersScepterExile(final TargetCardInJestersScepterExile target) { + super(target); + } + + @Override + public Set possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) { + Set possibleTargets = new HashSet<>(); + Card sourceCard = game.getCard(sourceId); + if (sourceCard != null) { + UUID exileId = CardUtil.getCardExileZoneId(game, sourceId); + ExileZone exile = game.getExile().getExileZone(exileId); + if (exile != null && exile.size() > 0) { + possibleTargets.addAll(exile); + } + } + return possibleTargets; + } + + @Override + public boolean canChoose(UUID sourceId, UUID sourceControllerId, Game game) { + Card sourceCard = game.getCard(sourceId); + if (sourceCard != null) { + UUID exileId = CardUtil.getCardExileZoneId(game, sourceId); + ExileZone exile = game.getExile().getExileZone(exileId); + if (exile != null && exile.size() > 0) { + return true; + } + } + return false; + } + + @Override + public boolean canTarget(UUID id, Ability source, Game game) { + Card card = game.getCard(id); + if (card != null && game.getState().getZone(card.getId()) == Zone.EXILED) { + ExileZone exile = null; + Card sourceCard = game.getCard(source.getSourceId()); + if (sourceCard != null) { + UUID exileId = CardUtil.getCardExileZoneId(game, source); + exile = game.getExile().getExileZone(exileId); + } + if (exile != null && exile.contains(id)) { + return filter.match(card, source.getControllerId(), game); + } + } + return false; + } + + @Override + public TargetCardInJestersScepterExile copy() { + return new TargetCardInJestersScepterExile(this); + } +} + +class JestersScepterLookAtCardEffect extends AsThoughEffectImpl { + + public JestersScepterLookAtCardEffect() { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + staticText = "You may look at cards exiled with {this}"; + } + + public JestersScepterLookAtCardEffect(final JestersScepterLookAtCardEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public JestersScepterLookAtCardEffect copy() { + return new JestersScepterLookAtCardEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); + if (card != null) { + MageObject sourceObject = game.getObject(source.getSourceId()); + if (sourceObject == null) { + return false; + } + UUID exileId = CardUtil.getCardExileZoneId(game, source); + ExileZone exile = game.getExile().getExileZone(exileId); + return exile != null + && exile.contains(objectId); + } + } + return false; + } +} + +class JestersScepterCost extends CostImpl { + + public JestersScepterCost() { + this.text = "Put a card exiled with {this} into its owner's graveyard"; + } + + public JestersScepterCost(JestersScepterCost cost) { + super(cost); + } + + @Override + public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) { + Player controller = game.getPlayer(controllerId); + if (controller != null) { + TargetCardInExile target = new TargetCardInExile(new FilterCard(), CardUtil.getCardExileZoneId(game, ability)); + target.setNotTarget(true); + Cards cards = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, ability)); + if (cards.size() > 0 + && controller.choose(Outcome.Benefit, cards, target, game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + if (controller.moveCardToGraveyardWithInfo(card, sourceId, game, Zone.EXILED)) { + // Split Card check + if (card instanceof SplitCard) { + game.getState().setValue(sourceId + "_nameOfExiledCardPayment", ((SplitCard)card).getLeftHalfCard().getName()); + game.getState().setValue(sourceId + "_nameOfExiledCardPayment2", ((SplitCard)card).getRightHalfCard().getName()); + paid = true; + return paid; + } + game.getState().setValue(sourceId + "_nameOfExiledCardPayment", card.getName()); + paid = true; + } + } + } + } + return paid; + } + + @Override + public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) { + Player player = game.getPlayer(controllerId); + return player != null; + } + + @Override + public JestersScepterCost copy() { + return new JestersScepterCost(this); + } +} + +class JestersScepterCounterEffect extends OneShotEffect { + + JestersScepterCounterEffect() { + super(Outcome.Detriment); + staticText = "Counter target spell if it has the same name as that card"; + } + + JestersScepterCounterEffect(final JestersScepterCounterEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source)); + if (spell != null) { + // In case of Split Card + String nameOfExiledCardPayment = (String) game.getState().getValue(source.getSourceId() + "_nameOfExiledCardPayment"); + String nameOfExiledCardPayment2 = (String) game.getState().getValue(source.getSourceId() + "_nameOfExiledCardPayment2"); + if (nameOfExiledCardPayment != null) { + if (nameOfExiledCardPayment.matches(spell.getName()) + || nameOfExiledCardPayment2.matches(spell.getName())) { + return game.getStack().counter(targetPointer.getFirst(game, source), source.getSourceId(), game); + } + } + } + return false; + } + + @Override + public JestersScepterCounterEffect copy() { + return new JestersScepterCounterEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/invasion/ManaMaze.java b/Mage.Sets/src/mage/sets/invasion/ManaMaze.java new file mode 100644 index 00000000000..4d3cfe593c4 --- /dev/null +++ b/Mage.Sets/src/mage/sets/invasion/ManaMaze.java @@ -0,0 +1,153 @@ +/* + * 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 mage.sets.invasion; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +/** + * + * @author jeffwadsworth + */ +public class ManaMaze extends CardImpl { + + public ManaMaze(UUID ownerId) { + super(ownerId, 59, "Mana Maze", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + this.expansionSetCode = "INV"; + + // Players can't cast spells that share a color with the spell most recently cast this turn. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ManaMazeEffect()), new LastSpellCastWatcher()); + + } + + public ManaMaze(final ManaMaze card) { + super(card); + } + + @Override + public ManaMaze copy() { + return new ManaMaze(this); + } +} + +class ManaMazeEffect extends ContinuousRuleModifyingEffectImpl { + + ManaMazeEffect() { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + staticText = "Players can't cast spells that share a color with the spell most recently cast this turn"; + } + + ManaMazeEffect(final ManaMazeEffect effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CAST_SPELL_LATE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Card card = game.getCard(event.getSourceId()); + if (card != null) { + LastSpellCastWatcher watcher = (LastSpellCastWatcher) game.getState().getWatchers().get(LastSpellCastWatcher.class.getName()); + if (watcher != null + && watcher.lastSpellCast != null) { + return card.getColor(game).contains(watcher.lastSpellCast.getColor(game)); + } + } + return false; + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public ManaMazeEffect copy() { + return new ManaMazeEffect(this); + } +} + +class LastSpellCastWatcher extends Watcher { + + Spell lastSpellCast = null; + + public LastSpellCastWatcher() { + super(LastSpellCastWatcher.class.getName(), WatcherScope.GAME); + } + + public LastSpellCastWatcher(final LastSpellCastWatcher watcher) { + super(watcher); + this.lastSpellCast = watcher.lastSpellCast; + } + + @Override + public LastSpellCastWatcher copy() { + return new LastSpellCastWatcher(this); + } + + @Override + public void watch(GameEvent event, Game game) { + if (EventType.SPELL_CAST.equals(event.getType())) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell == null) { + MageObject mageObject = game.getLastKnownInformation(event.getTargetId(), Zone.STACK); + if (mageObject instanceof Spell) { + spell = (Spell) mageObject; + } + } + if (spell != null) { + lastSpellCast = spell; + } + } + } + + @Override + public void reset() { + super.reset(); + lastSpellCast = null; + } +}