diff --git a/Mage.Client/src/main/java/mage/client/components/ColorPane.java b/Mage.Client/src/main/java/mage/client/components/ColorPane.java index 43a402ae0c0..dbdb0447722 100644 --- a/Mage.Client/src/main/java/mage/client/components/ColorPane.java +++ b/Mage.Client/src/main/java/mage/client/components/ColorPane.java @@ -35,6 +35,7 @@ public class ColorPane extends JEditorPane { HTMLEditorKit kit = new HTMLEditorKit(); HTMLDocument doc = new HTMLDocument(); private int tooltipDelay; + private int tooltipCounter; public ColorPane() { this.setEditorKit(kit); @@ -90,7 +91,8 @@ public class ColorPane extends JEditorPane { if (location != null) { container.setLocation(location); } - container.setVisible(show); + tooltipCounter += show ? 1 : -1; + container.setVisible(tooltipCounter > 0); c.repaint(); } }); @@ -119,7 +121,7 @@ public class ColorPane extends JEditorPane { public void append(String text) { try { - text = text.replaceAll("(]*>([^<]*)) (\\[[0-9a-fA-F]*\\])", "$1 $3"); + text = text.replaceAll("(]*>([^<]*)) (\\[[0-9a-fA-F]*\\])", "$1 $3"); setEditable(true); kit.insertHTML(doc, doc.getLength(), text, 0, 0, null); setEditable(false); 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 cb63a4d543a..5f654e18b75 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,7 @@ 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()); + genPool = new DeckGeneratorPool(deckSize, allowedColors, genDialog.isSingleton(), genDialog.isColorless()); final String[] sets = setsToUse.toArray(new String[setsToUse.size()]); 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 8e3770ce582..0c0ca158507 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 @@ -48,13 +48,13 @@ import java.util.Date; */ public class DeckGeneratorDialog { - private static JDialog dlg; - private static String selectedColors; - private static JComboBox cbSets; - private static JComboBox cbDeckSize; - private static JButton btnGenerate, btnCancel; - private static JCheckBox cArtifacts, cSingleton, cNonBasicLands; - private static SimpleDateFormat dateFormat; + 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; public DeckGeneratorDialog() { @@ -83,7 +83,7 @@ public class DeckGeneratorDialog { p0.add(Box.createVerticalStrut(5)); JPanel jPanel = new JPanel(); JLabel text3 = new JLabel("Choose sets:"); - cbSets = new JComboBox(ConstructedFormats.getTypes()); + cbSets = new JComboBox(ConstructedFormats.getTypes()); cbSets.setSelectedIndex(0); cbSets.setPreferredSize(new Dimension(300, 25)); cbSets.setMaximumSize(new Dimension(300, 25)); @@ -100,7 +100,7 @@ public class DeckGeneratorDialog { p0.add(Box.createVerticalStrut(5)); JPanel jPanel2 = new JPanel(); JLabel textDeckSize = new JLabel("Deck size:"); - cbDeckSize = new JComboBox(new String[]{"40","60"}); + cbDeckSize = new JComboBox(new String[] { "40", "60" }); cbDeckSize.setSelectedIndex(0); cbDeckSize.setPreferredSize(new Dimension(300, 25)); cbDeckSize.setMaximumSize(new Dimension(300, 25)); @@ -138,8 +138,15 @@ public class DeckGeneratorDialog { cNonBasicLands.setSelected(Boolean.valueOf(nonBasicEnabled)); jCheckBoxes.add(cNonBasicLands); - jCheckBoxes.setPreferredSize(new Dimension(300, 25)); - jCheckBoxes.setMaximumSize(new Dimension(300, 25)); + // Non-basic lands + 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); + + jCheckBoxes.setPreferredSize(new Dimension(450, 25)); + jCheckBoxes.setMaximumSize(new Dimension(450, 25)); p0.add(jCheckBoxes); btnGenerate = new JButton("Ok"); @@ -168,7 +175,7 @@ public class DeckGeneratorDialog { dlg.dispose(); } - public static void cleanUp() { + public void cleanUp() { for (ActionListener al: btnGenerate.getActionListeners()) { btnGenerate.removeActionListener(al); } @@ -187,7 +194,7 @@ public class DeckGeneratorDialog { tmp.createNewFile(); deck.setName(deckName); Sets.saveDeck(tmp.getAbsolutePath(), deck.getDeckCardLists()); - DeckGeneratorDialog.cleanUp(); + cleanUp(); return tmp.getAbsolutePath(); } catch (Exception e) { JOptionPane.showMessageDialog(null, "Couldn't generate deck. Try again."); @@ -205,6 +212,12 @@ public class DeckGeneratorDialog { return selected; } + public boolean isColorless() { + boolean selected = cColorless.isSelected(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORLESS, Boolean.toString(selected)); + return selected; + } + public boolean useArtifacts() { boolean selected = cArtifacts.isSelected(); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ARTIFACTS, Boolean.toString(selected)); 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 3e92fef086e..849a31997d1 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 @@ -51,6 +51,7 @@ public class DeckGeneratorPool private static final int NONCREATURE_COUNT_60 = 13; private final List allowedColors; + private boolean colorlessAllowed; private final List poolCMCs; private final int creatureCount; private final int nonCreatureCount; @@ -68,11 +69,12 @@ public class DeckGeneratorPool private List reserveSpells = new ArrayList<>(); private Deck deck; - public DeckGeneratorPool(final int deckSize, final List allowedColors, boolean isSingleton) + public DeckGeneratorPool(final int deckSize, final List allowedColors, boolean isSingleton, boolean colorlessAllowed) { this.deckSize = deckSize; this.allowedColors = allowedColors; this.isSingleton = isSingleton; + this.colorlessAllowed = colorlessAllowed; this.deck = new Deck(); @@ -207,6 +209,9 @@ public class DeckGeneratorPool return false; } } + if (symbol.equals("C") && !colorlessAllowed) { + return false; + } } return true; } 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 b6fafef6105..14219e5e2e4 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -235,6 +235,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_DECK_GENERATOR_SINGLETON = "newDeckGeneratorSingleton"; 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"; // used to save and restore the settings for the cardArea (draft, sideboarding, deck builder) public static final String KEY_DRAFT_VIEW = "draftView"; diff --git a/Mage.Client/src/main/java/mage/client/util/Config.java b/Mage.Client/src/main/java/mage/client/util/Config.java index 395879d1b8d..678c8fdb6e3 100644 --- a/Mage.Client/src/main/java/mage/client/util/Config.java +++ b/Mage.Client/src/main/java/mage/client/util/Config.java @@ -24,11 +24,9 @@ * 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.util; - import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -42,6 +40,7 @@ import org.apache.log4j.Logger; */ public class Config { + // TODO: Remove this class completely private static final Logger logger = Logger.getLogger(Config.class); public static final String remoteServer; @@ -65,33 +64,33 @@ public class Config { } catch (IOException ex) { logger.fatal("Config error ", ex); } - serverName = p.getProperty("server-name"); - port = Integer.parseInt(p.getProperty("port")); - remoteServer = p.getProperty("remote-server"); - cardScalingFactor = Double.valueOf(p.getProperty("card-scaling-factor")); - cardScalingFactorEnlarged = Double.valueOf(p.getProperty("card-scaling-factor-enlarged")); - handScalingFactor = Double.valueOf(p.getProperty("hand-scaling-factor")); - defaultGameType = p.getProperty("default-game-type", "Human"); - defaultDeckPath = p.getProperty("default-deck-path"); - defaultOtherPlayerIndex = p.getProperty("default-other-player-index"); - defaultComputerName = p.getProperty("default-computer-name"); - - dimensions = new CardDimensions(cardScalingFactor); - dimensionsEnlarged = new CardDimensions(cardScalingFactorEnlarged); -// activate instead this part, to run the UI editor for some panels without error -// serverName = "localhost"; -// port = 17171; -// remoteServer = "mage-server"; -// cardScalingFactor = Double.valueOf(0.4); -// cardScalingFactorEnlarged = Double.valueOf(0.5); -// handScalingFactor = Double.valueOf(1.3); +// serverName = p.getProperty("server-name"); +// port = Integer.parseInt(p.getProperty("port")); +// remoteServer = p.getProperty("remote-server"); +// cardScalingFactor = Double.valueOf(p.getProperty("card-scaling-factor")); +// cardScalingFactorEnlarged = Double.valueOf(p.getProperty("card-scaling-factor-enlarged")); +// handScalingFactor = Double.valueOf(p.getProperty("hand-scaling-factor")); // defaultGameType = p.getProperty("default-game-type", "Human"); -// defaultDeckPath = ""; -// defaultOtherPlayerIndex = "1"; -// defaultComputerName = "Computer"; +// defaultDeckPath = p.getProperty("default-deck-path"); +// defaultOtherPlayerIndex = p.getProperty("default-other-player-index"); +// defaultComputerName = p.getProperty("default-computer-name"); // // dimensions = new CardDimensions(cardScalingFactor); // dimensionsEnlarged = new CardDimensions(cardScalingFactorEnlarged); +// activate instead this part, to run the UI editor for some panels without error + serverName = "localhost"; + port = 17171; + remoteServer = "mage-server"; + cardScalingFactor = 0.4; + cardScalingFactorEnlarged = 0.5; + handScalingFactor = 1.3; + defaultGameType = p.getProperty("default-game-type", "Human"); + defaultDeckPath = ""; + defaultOtherPlayerIndex = "1"; + defaultComputerName = "Computer"; + + dimensions = new CardDimensions(cardScalingFactor); + dimensionsEnlarged = new CardDimensions(cardScalingFactorEnlarged); } diff --git a/Mage.Client/src/main/java/mage/client/util/EDTExceptionHandler.java b/Mage.Client/src/main/java/mage/client/util/EDTExceptionHandler.java index c119d39a2bb..8e78c928b54 100644 --- a/Mage.Client/src/main/java/mage/client/util/EDTExceptionHandler.java +++ b/Mage.Client/src/main/java/mage/client/util/EDTExceptionHandler.java @@ -47,8 +47,8 @@ public class EDTExceptionHandler implements Thread.UncaughtExceptionHandler { public void handle(Throwable throwable) { try { - logger.fatal(null, throwable); - JOptionPane.showMessageDialog(MageFrame.getDesktop(), throwable, "MAGE Client UI error", JOptionPane.ERROR_MESSAGE); + logger.fatal("MAGE Client UI error", throwable); + // JOptionPane.showMessageDialog(MageFrame.getDesktop(), throwable, "MAGE Client UI error", JOptionPane.ERROR_MESSAGE); } catch (Throwable t) {} } diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index cfd3ee6834b..2921db77a36 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -6,6 +6,16 @@ import java.awt.Image; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,6 +46,7 @@ public class ManaSymbols { "WP", "UP", "BP", "RP", "GP", "X", "C"}; public static void loadImages() { + renameSymbols(getSymbolsPath() + File.separator + "symbols"); smallSymbolsFound = loadSymbolsImages(15); mediumSymbolsFound = loadSymbolsImages(25); @@ -120,14 +131,14 @@ public class ManaSymbols { private static boolean loadSymbolsImages(int size) { boolean fileErrors = false; HashMap sizedSymbols = new HashMap<>(); + String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL; + if (size > 25) { + resourcePath = Constants.RESOURCE_PATH_MANA_LARGE; + } else if (size > 15) { + resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM; + } for (String symbol : symbols) { - String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL; - if (size > 25) { - resourcePath = Constants.RESOURCE_PATH_MANA_LARGE; - } else if (size > 15) { - resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM; - } - File file = new File(getSymbolsPath() + resourcePath + "/" + symbol + ".jpg"); + File file = new File(getSymbolsPath() + resourcePath + "/" + symbol + ".gif"); try { if (size == 15 || size == 25) { @@ -148,6 +159,24 @@ public class ManaSymbols { return !fileErrors; } + private static void renameSymbols(String path) { + final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg"); + try { + Files.walkFileTree(Paths.get(path), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (matcher.matches(file)) { + Path gifPath = file.resolveSibling(file.getFileName().toString().replaceAll("\\.jpg$", ".gif")); + Files.move(file, gifPath, StandardCopyOption.REPLACE_EXISTING); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + LOGGER.error("Couldn't rename mana symbols!"); + } + } + private static String getSymbolsPath() { return getSymbolsPath(false); } @@ -247,9 +276,9 @@ public class ManaSymbols { symbolFilesFound = mediumSymbolsFound; } if (symbolFilesFound) { - replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll("$1$2"); + replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll( + "$1$2"); } replaced = replaced.replace("|source|", "{source}"); replaced = replaced.replace("|this|", "{this}"); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java index bd25bd00594..10bd03ff7a9 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java @@ -71,7 +71,7 @@ public class GathererSymbols implements Iterable { return computeNext(); } String symbol = sym.replaceAll("/", ""); - File dst = new File(dir, symbol + ".jpg"); + File dst = new File(dir, symbol + ".gif"); switch (symbol) { case "T": diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/utils/RateCard.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/utils/RateCard.java index b3a64aac022..0a58ae76012 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/utils/RateCard.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/utils/RateCard.java @@ -52,7 +52,9 @@ public class RateCard { */ public static int rateCard(Card card, List allowedColors) { if (allowedColors == null && rated.containsKey(card.getName())) { - return rated.get(card.getName()); + int rate = rated.get(card.getName()); +// log.info(card.getName() + " rate: " + rate); + return rate; } int type; if (card.getCardType().contains(CardType.PLANESWALKER)) { @@ -136,7 +138,7 @@ public class RateCard { */ private synchronized static void readRatings() { if (ratings == null) { - ratings = new HashMap(); + ratings = new HashMap<>(); readFromFile("/m13.csv"); } } diff --git a/Mage.Server/config/log4j.properties b/Mage.Server/config/log4j.properties index 513f439d845..2bfdbce343f 100644 --- a/Mage.Server/config/log4j.properties +++ b/Mage.Server/config/log4j.properties @@ -1,16 +1,45 @@ #default levels -log4j.rootLogger=debug, console, logfile +#log4j.rootLogger=info, console, logfile +log4j.rootLogger=info, RollingAppender log4j.logger.com.j256.ormlite=warn -log4j.logger.mage.player.ai=warn -#log4j.logger.mage.player.ai.ComputerPlayer6=debug +log4j.logger.mage.game=debug +log4j.logger.mage.game.GameImpl=debug +log4j.logger.mage.players.PlayerImpl=debug +log4j.logger.mage.server=debug +#log4j.logger.mage.server.UserManager=debug +#log4j.logger.mage.server.User=debug +#log4j.logger.mage.server.ChatSession=debug +#log4j.logger.mage.server.ChatManager=debug +#log4j.logger.mage.server.TableController=debug +#log4j.logger.mage.server.TableManager=debug +#log4j.logger.mage.server.tournament.TournamentManager=debug +#log4j.logger.mage.server.game.GameSession=debug +log4j.logger.mage.abilities.AbilityImpl=debug +log4j.logger.mage.cards.decks=debug +log4j.logger.mage.abilities.effects.common.continious.CommanderManaReplacementEffect=debug + #console log -log4j.appender.console=org.apache.log4j.ConsoleAppender -log4j.appender.console.layout=org.apache.log4j.PatternLayout -log4j.appender.console.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M%n -log4j.appender.console.Threshold=info +#log4j.appender.console=org.apache.log4j.ConsoleAppender +#log4j.appender.console.layout=org.apache.log4j.PatternLayout +#log4j.appender.console.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm [ss:SSS]}] %C{1}[%t]: %m% +#log4j.appender.console.Threshold=info #file log -log4j.appender.logfile=org.apache.log4j.FileAppender -log4j.appender.logfile.File=mageserver.log -log4j.appender.logfile.layout=org.apache.log4j.PatternLayout -log4j.appender.logfile.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M%n \ No newline at end of file +#log4j.appender.logfile=org.apache.log4j.FileAppender +#log4j.appender.logfile.File=mageserver.log +#log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +#log4j.appender.logfile.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm [ss:SSS]}] %C{1}[%t]: %m%n + +log4j.appender.RollingAppender=org.apache.log4j.DailyRollingFileAppender +log4j.appender.RollingAppender.File=mageserver.log +log4j.appender.RollingAppender.DatePattern='.'yyyy-MM-dd +log4j.appender.RollingAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.RollingAppender.layout.ConversionPattern=[%p] %d %c %M - %m%n +log4j.appender.RollingAppender.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} %-90m =>[%t] %C{1}.%M%n +#diagnostic log for game core classes +#log4j.category.mage.server.game = INFO, diagfile +#log4j.additivity.mage.server.game = false +#log4j.appender.diagfile=org.apache.log4j.FileAppender +#log4j.appender.diagfile.File=magediag.log +#log4j.appender.diagfile.layout=org.apache.log4j.PatternLayout +#log4j.appender.diagfile.layout.ConversionPattern=%-5p [%d{yyyy-MM-dd HH:mm [ss:SSS]}] %C{1}[%t]: %m%n \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/EternalMasters.java b/Mage.Sets/src/mage/sets/EternalMasters.java index 7ca07d96fec..3936257b684 100644 --- a/Mage.Sets/src/mage/sets/EternalMasters.java +++ b/Mage.Sets/src/mage/sets/EternalMasters.java @@ -45,7 +45,7 @@ public class EternalMasters extends ExpansionSet { } private EternalMasters() { - super("Eternal Masters", "MMA", "mage.sets.eternalmasters", new GregorianCalendar(2016, 6, 10).getTime(), SetType.SUPPLEMENTAL); + super("Eternal Masters", "EMA", "mage.sets.eternalmasters", new GregorianCalendar(2016, 6, 10).getTime(), SetType.SUPPLEMENTAL); this.blockName = "Reprint"; this.hasBasicLands = false; this.hasBoosters = true; diff --git a/Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain8.java b/Mage.Sets/src/mage/sets/blessedvscursed/Island4.java similarity index 84% rename from Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain8.java rename to Mage.Sets/src/mage/sets/blessedvscursed/Island4.java index b3e68c691de..326bb5dbf39 100644 --- a/Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain8.java +++ b/Mage.Sets/src/mage/sets/blessedvscursed/Island4.java @@ -25,7 +25,7 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ -package mage.sets.izzetvsgolgari; +package mage.sets.blessedvscursed; import java.util.UUID; @@ -33,19 +33,19 @@ import java.util.UUID; * * @author fireshoes */ -public class Mountain8 extends mage.cards.basiclands.Mountain { +public class Island4 extends mage.cards.basiclands.Island { - public Mountain8(UUID ownerId) { - super(ownerId, 44); - this.expansionSetCode = "DDJ"; + public Island4(UUID ownerId) { + super(ownerId, 71); + this.expansionSetCode = "DDQ"; } - public Mountain8(final Mountain8 card) { + public Island4(final Island4 card) { super(card); } @Override - public Mountain8 copy() { - return new Mountain8(this); + public Island4 copy() { + return new Island4(this); } } diff --git a/Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain5.java b/Mage.Sets/src/mage/sets/blessedvscursed/Island5.java similarity index 84% rename from Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain5.java rename to Mage.Sets/src/mage/sets/blessedvscursed/Island5.java index 5b269183bd1..0bbb89bb5a6 100644 --- a/Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain5.java +++ b/Mage.Sets/src/mage/sets/blessedvscursed/Island5.java @@ -25,7 +25,7 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ -package mage.sets.izzetvsgolgari; +package mage.sets.blessedvscursed; import java.util.UUID; @@ -33,19 +33,19 @@ import java.util.UUID; * * @author fireshoes */ -public class Mountain5 extends mage.cards.basiclands.Mountain { +public class Island5 extends mage.cards.basiclands.Island { - public Mountain5(UUID ownerId) { - super(ownerId, 41); - this.expansionSetCode = "DDJ"; + public Island5(UUID ownerId) { + super(ownerId, 72); + this.expansionSetCode = "DDQ"; } - public Mountain5(final Mountain5 card) { + public Island5(final Island5 card) { super(card); } @Override - public Mountain5 copy() { - return new Mountain5(this); + public Island5 copy() { + return new Island5(this); } } diff --git a/Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain6.java b/Mage.Sets/src/mage/sets/blessedvscursed/Island6.java similarity index 84% rename from Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain6.java rename to Mage.Sets/src/mage/sets/blessedvscursed/Island6.java index 0de1f7846f4..7019b558883 100644 --- a/Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain6.java +++ b/Mage.Sets/src/mage/sets/blessedvscursed/Island6.java @@ -25,7 +25,7 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ -package mage.sets.izzetvsgolgari; +package mage.sets.blessedvscursed; import java.util.UUID; @@ -33,19 +33,19 @@ import java.util.UUID; * * @author fireshoes */ -public class Mountain6 extends mage.cards.basiclands.Mountain { +public class Island6 extends mage.cards.basiclands.Island { - public Mountain6(UUID ownerId) { - super(ownerId, 42); - this.expansionSetCode = "DDJ"; + public Island6(UUID ownerId) { + super(ownerId, 73); + this.expansionSetCode = "DDQ"; } - public Mountain6(final Mountain6 card) { + public Island6(final Island6 card) { super(card); } @Override - public Mountain6 copy() { - return new Mountain6(this); + public Island6 copy() { + return new Island6(this); } } diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfCleansingFire.java b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfCleansingFire.java index 17859d23648..4835f20c8bd 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfCleansingFire.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfCleansingFire.java @@ -35,7 +35,7 @@ import mage.MageInt; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -74,7 +74,7 @@ public class MyojinOfCleansingFire extends CardImpl { this.getSpellAbility().addWatcher(new CastFromHandWatcher()); // Myojin of Cleansing Fire enters the battlefield with a divinity counter on it if you cast it from your hand. - this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandSourceCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); // Myojin of Cleansing Fire is indestructible as long as it has a divinity counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), new SourceHasCounterCondition(CounterType.DIVINITY), "{this} is indestructible as long as it has a divinity counter on it"))); diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfInfiniteRage.java b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfInfiniteRage.java index ac250e6c060..7be1669a4b4 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfInfiniteRage.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfInfiniteRage.java @@ -35,7 +35,7 @@ import mage.MageInt; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -70,7 +70,7 @@ public class MyojinOfInfiniteRage extends CardImpl { this.getSpellAbility().addWatcher(new CastFromHandWatcher()); // Myojin of Infinite Rage enters the battlefield with a divinity counter on it if you cast it from your hand. - this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandSourceCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); // Myojin of Infinite Rage is indestructible as long as it has a divinity counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), new SourceHasCounterCondition(CounterType.DIVINITY), "{this} is indestructible as long as it has a divinity counter on it"))); diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfLifesWeb.java b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfLifesWeb.java index c4dac7d5dac..00c411cb2bf 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfLifesWeb.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfLifesWeb.java @@ -35,7 +35,7 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -75,7 +75,7 @@ public class MyojinOfLifesWeb extends CardImpl { this.getSpellAbility().addWatcher(new CastFromHandWatcher()); // Myojin of Life's Web enters the battlefield with a divinity counter on it if you cast it from your hand. - this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandSourceCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); // Myojin of Life's Web is indestructible as long as it has a divinity counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfNightsReach.java b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfNightsReach.java index ac83c62d9e5..9e2cdb46aad 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfNightsReach.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfNightsReach.java @@ -35,7 +35,7 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -68,7 +68,7 @@ public class MyojinOfNightsReach extends CardImpl { this.getSpellAbility().addWatcher(new CastFromHandWatcher()); // Myojin of Night's Reach enters the battlefield with a divinity counter on it if you cast it from your hand. - this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandSourceCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); // Myojin of Night's Reach is indestructible as long as it has a divinity counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), new SourceHasCounterCondition(CounterType.DIVINITY), "{this} is indestructible as long as it has a divinity counter on it"))); diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfSeeingWinds.java b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfSeeingWinds.java index c33b5db0d68..609bf3e2b4a 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfSeeingWinds.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfSeeingWinds.java @@ -35,7 +35,7 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -73,7 +73,7 @@ public class MyojinOfSeeingWinds extends CardImpl { this.getSpellAbility().addWatcher(new CastFromHandWatcher()); // Myojin of Seeing Winds enters the battlefield with a divinity counter on it if you cast it from your hand. - this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandSourceCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); // Myojin of Seeing Winds is indestructible as long as it has a divinity counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), new SourceHasCounterCondition(CounterType.DIVINITY), "{this} is indestructible as long as it has a divinity counter on it"))); diff --git a/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java b/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java index 0af6bfc333b..265e5ce1bab 100644 --- a/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java +++ b/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.effects.common.TapAllEffect; @@ -71,7 +71,7 @@ public class DreadCacodemon extends CardImpl { // if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new DestroyAllEffect(opponentsCreatures, false)); ability.addEffect(new TapAllEffect(otherCreaturesYouControl)); - this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandCondition(), + this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/commander2014/AEtherSnap.java b/Mage.Sets/src/mage/sets/commander2014/AEtherSnap.java index df8abfdccce..f9440c42088 100644 --- a/Mage.Sets/src/mage/sets/commander2014/AEtherSnap.java +++ b/Mage.Sets/src/mage/sets/commander2014/AEtherSnap.java @@ -53,7 +53,6 @@ public class AEtherSnap extends CardImpl { super(ownerId, 133, "AEther Snap", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{3}{B}{B}"); this.expansionSetCode = "C14"; - // Remove all counters from all permanents and exile all tokens. this.getSpellAbility().addEffect(new AEtherSnapEffect()); } @@ -88,13 +87,13 @@ class AEtherSnapEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - for (Permanent permanent :game.getBattlefield().getActivePermanents(new FilterPermanent(), controller.getId(), source.getSourceId(), game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterPermanent(), controller.getId(), source.getSourceId(), game)) { if (permanent instanceof PermanentToken) { controller.moveCardToExileWithInfo(permanent, null, "", source.getSourceId(), game, Zone.BATTLEFIELD, true); - } else if (!permanent.getCounters().isEmpty()){ + } else if (!permanent.getCounters().isEmpty()) { Counters counters = permanent.getCounters().copy(); - for (Counter counter: counters.values()) { - permanent.getCounters().removeCounter(counter.getName(), counter.getCount()); + for (Counter counter : counters.values()) { + permanent.removeCounters(counter, game); } } } diff --git a/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java b/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java index 04336e553d5..49a589e5b5a 100644 --- a/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java +++ b/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java @@ -30,7 +30,7 @@ package mage.sets.commander2014; import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ExileAllEffect; import mage.abilities.keyword.FlashAbility; @@ -62,7 +62,7 @@ public class AngelOfTheDireHour extends CardImpl { // When Angel of the Dire Hour enters the battlefield, if you cast it from your hand, exile all attacking creatures. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new ExileAllEffect(new FilterAttackingCreature("attacking creatures")), false), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, exile all attacking creatures."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java b/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java index e8e24c4d57c..4bc14d7bfca 100644 --- a/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java +++ b/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java @@ -32,7 +32,7 @@ import mage.MageInt; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; @@ -66,7 +66,7 @@ public class BreachingLeviathan extends CardImpl { // When Breaching Leviathan enters the battlefield, if you cast it from your hand, tap all nonblue creatures. Those creatures don't untap during their controllers' next untap steps. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new BreachingLeviathanEffect(), false), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, tap all nonblue creatures. Those creatures don't untap during their controllers' next untap steps."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/commander2015/ThiefOfBlood.java b/Mage.Sets/src/mage/sets/commander2015/ThiefOfBlood.java index 31d1c181cde..1d680880263 100644 --- a/Mage.Sets/src/mage/sets/commander2015/ThiefOfBlood.java +++ b/Mage.Sets/src/mage/sets/commander2015/ThiefOfBlood.java @@ -57,10 +57,10 @@ public class ThiefOfBlood extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(1); this.subtype.add("Vampire"); - + // Flying this.addAbility(FlyingAbility.getInstance()); - + // As Thief of Blood enters the battlefield, remove all counters from all permanents. Thief of Blood enters the battlefield with a +1/+1 counter on it for each counter removed this way. this.addAbility(new EntersBattlefieldAbility(new ThiefOfBloodEffect(), null, "As {this} enters the battlefield, remove all counters from all permanents. {this} enters the battlefield with a +1/+1 counter on it for each counter removed this way", null)); } @@ -76,33 +76,34 @@ public class ThiefOfBlood extends CardImpl { } class ThiefOfBloodEffect extends OneShotEffect { - + private static final FilterPermanent filter = new FilterPermanent("permanent with a counter"); + static { filter.add(new CounterPredicate(null)); } - + ThiefOfBloodEffect() { super(Outcome.BoostCreature); this.staticText = "remove all counters from all permanents. {this} enters the battlefield with a +1/+1 counter on it for each counter removed this way"; } - + ThiefOfBloodEffect(final ThiefOfBloodEffect effect) { super(effect); } - + @Override public ThiefOfBloodEffect copy() { return new ThiefOfBloodEffect(this); } - + @Override public boolean apply(Game game, Ability source) { int countersRemoved = 0; for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) { Counters counters = permanent.getCounters().copy(); for (Counter counter : counters.values()) { - permanent.getCounters().removeCounter(counter.getName(), counter.getCount()); + permanent.removeCounters(counter.getName(), counter.getCount(), game); countersRemoved += counter.getCount(); } } diff --git a/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java b/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java index 0e9d02637b8..77638cd428f 100644 --- a/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java +++ b/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java @@ -30,7 +30,7 @@ package mage.sets.darksteel; import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ExileAllEffect; import mage.abilities.keyword.AffinityForArtifactsAbility; @@ -70,7 +70,7 @@ public class FurnaceDragon extends CardImpl { // When Furnace Dragon enters the battlefield, if you cast it from your hand, exile all artifacts. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new ExileAllEffect(filter), false), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, exile all artifacts."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java b/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java index 1278473e692..f3eb54962bd 100644 --- a/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java +++ b/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.ObjectColor; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.keyword.FlyingAbility; @@ -70,7 +70,7 @@ public class ReiverDemon extends CardImpl { // When Reiver Demon enters the battlefield, if you cast it from your hand, destroy all nonartifact, nonblack creatures. They can't be regenerated. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new DestroyAllEffect(filter, true), false), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, destroy all nonartifact, nonblack creatures. They can't be regenerated."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java b/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java index 4c5ea06c015..feb3b83919e 100644 --- a/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java +++ b/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.PopulateEffect; @@ -57,7 +57,7 @@ public class ScionOfVituGhazi extends CardImpl { TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new BirdToken()), false); ability.addEffect(new PopulateEffect("then")); - this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandCondition(), + this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, put a 1/1 white Bird creature token with flying onto the battlefield, then populate."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java b/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java index b4b5e194f9c..acae240ee7f 100644 --- a/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java +++ b/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java @@ -32,7 +32,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.Condition; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.keyword.FlyingAbility; @@ -88,7 +88,7 @@ class DeathbringerRegentCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - return new CastFromHandCondition().apply(game, source) + return new CastFromHandSourceCondition().apply(game, source) && game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), game).size() >= 6; } } diff --git a/Mage.Sets/src/mage/sets/fridaynightmagic/BlightedFen.java b/Mage.Sets/src/mage/sets/fridaynightmagic/BlightedFen.java new file mode 100644 index 00000000000..dcff03d9cc6 --- /dev/null +++ b/Mage.Sets/src/mage/sets/fridaynightmagic/BlightedFen.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.fridaynightmagic; + +import java.util.UUID; + +/** + * + * @author fireshoes + */ +public class BlightedFen extends mage.sets.battleforzendikar.BlightedFen { + + public BlightedFen(UUID ownerId) { + super(ownerId); + this.cardNumber = 191; + this.expansionSetCode = "FNMP"; + } + + public BlightedFen(final BlightedFen card) { + super(card); + } + + @Override + public BlightedFen copy() { + return new BlightedFen(this); + } +} diff --git a/Mage.Sets/src/mage/sets/fridaynightmagic/GoblinWarchief.java b/Mage.Sets/src/mage/sets/fridaynightmagic/GoblinWarchief1.java similarity index 88% rename from Mage.Sets/src/mage/sets/fridaynightmagic/GoblinWarchief.java rename to Mage.Sets/src/mage/sets/fridaynightmagic/GoblinWarchief1.java index 3e61ef58364..28f5c15b1cd 100644 --- a/Mage.Sets/src/mage/sets/fridaynightmagic/GoblinWarchief.java +++ b/Mage.Sets/src/mage/sets/fridaynightmagic/GoblinWarchief1.java @@ -33,20 +33,20 @@ import java.util.UUID; * * @author fireshoes */ -public class GoblinWarchief extends mage.sets.scourge.GoblinWarchief { +public class GoblinWarchief1 extends mage.sets.scourge.GoblinWarchief { - public GoblinWarchief(UUID ownerId) { + public GoblinWarchief1(UUID ownerId) { super(ownerId); this.cardNumber = 72; this.expansionSetCode = "FNMP"; } - public GoblinWarchief(final GoblinWarchief card) { + public GoblinWarchief1(final GoblinWarchief1 card) { super(card); } @Override - public GoblinWarchief copy() { - return new GoblinWarchief(this); + public GoblinWarchief1 copy() { + return new GoblinWarchief1(this); } } diff --git a/Mage.Sets/src/mage/sets/fridaynightmagic/GoblinWarchief2.java b/Mage.Sets/src/mage/sets/fridaynightmagic/GoblinWarchief2.java new file mode 100644 index 00000000000..2335e7b8140 --- /dev/null +++ b/Mage.Sets/src/mage/sets/fridaynightmagic/GoblinWarchief2.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.fridaynightmagic; + +import java.util.UUID; + +/** + * + * @author fireshoes + */ +public class GoblinWarchief2 extends mage.sets.scourge.GoblinWarchief { + + public GoblinWarchief2(UUID ownerId) { + super(ownerId); + this.cardNumber = 192; + this.expansionSetCode = "FNMP"; + } + + public GoblinWarchief2(final GoblinWarchief2 card) { + super(card); + } + + @Override + public GoblinWarchief2 copy() { + return new GoblinWarchief2(this); + } +} diff --git a/Mage.Sets/src/mage/sets/innistrad/LudevicsTestSubject.java b/Mage.Sets/src/mage/sets/innistrad/LudevicsTestSubject.java index 173bf694c53..c714f73699e 100644 --- a/Mage.Sets/src/mage/sets/innistrad/LudevicsTestSubject.java +++ b/Mage.Sets/src/mage/sets/innistrad/LudevicsTestSubject.java @@ -28,9 +28,6 @@ package mage.sets.innistrad; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -41,7 +38,9 @@ import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.DefenderAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; +import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Rarity; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; @@ -83,6 +82,7 @@ public class LudevicsTestSubject extends CardImpl { } class LudevicsTestSubjectEffect extends OneShotEffect { + LudevicsTestSubjectEffect() { super(Outcome.Benefit); staticText = "Then if there are five or more hatchling counters on it, remove all of them and transform it"; @@ -97,7 +97,7 @@ class LudevicsTestSubjectEffect extends OneShotEffect { Permanent p = game.getPermanent(source.getSourceId()); if (p != null) { if (p.getCounters().getCount(CounterType.HATCHLING) >= 5) { - p.getCounters().removeCounter(CounterType.HATCHLING, p.getCounters().getCount(CounterType.HATCHLING)); + p.removeCounters(CounterType.HATCHLING.getName(), p.getCounters().getCount(CounterType.HATCHLING), game); TransformSourceEffect effect = new TransformSourceEffect(true); return effect.apply(game, source); } @@ -109,4 +109,4 @@ class LudevicsTestSubjectEffect extends OneShotEffect { public LudevicsTestSubjectEffect copy() { return new LudevicsTestSubjectEffect(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/magicorigins/AlhammarretHighArbiter.java b/Mage.Sets/src/mage/sets/magicorigins/AlhammarretHighArbiter.java index 62dbe2cb352..8c3e6cd9fc5 100644 --- a/Mage.Sets/src/mage/sets/magicorigins/AlhammarretHighArbiter.java +++ b/Mage.Sets/src/mage/sets/magicorigins/AlhammarretHighArbiter.java @@ -173,7 +173,7 @@ class AlhammarretHighArbiterCantCastEffect extends ContinuousRuleModifyingEffect @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == EventType.CAST_SPELL; + return event.getType() == EventType.CAST_SPELL_LATE; } @Override diff --git a/Mage.Sets/src/mage/sets/mirrodin/OblivionStone.java b/Mage.Sets/src/mage/sets/mirrodin/OblivionStone.java index d0ef92ef316..f272a735fcb 100644 --- a/Mage.Sets/src/mage/sets/mirrodin/OblivionStone.java +++ b/Mage.Sets/src/mage/sets/mirrodin/OblivionStone.java @@ -28,9 +28,6 @@ package mage.sets.mirrodin; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; @@ -39,7 +36,9 @@ import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.CardImpl; +import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Rarity; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; @@ -78,6 +77,7 @@ public class OblivionStone extends CardImpl { } class OblivionStoneEffect extends OneShotEffect { + OblivionStoneEffect() { super(Outcome.DestroyPermanent); staticText = "Destroy each nonland permanent without a fate counter on it, then remove all fate counters from all permanents"; @@ -96,7 +96,7 @@ class OblivionStoneEffect extends OneShotEffect { } for (Permanent p : game.getBattlefield().getAllActivePermanents()) { if (p.getCounters().containsKey(CounterType.FATE)) { - p.getCounters().removeCounter(CounterType.FATE, p.getCounters().getCount(CounterType.FATE)); + p.removeCounters(CounterType.FATE.getName(), p.getCounters().getCount(CounterType.FATE), game); } } return true; diff --git a/Mage.Sets/src/mage/sets/modernmasters/Epochrasite.java b/Mage.Sets/src/mage/sets/modernmasters/Epochrasite.java index 6f652e82b4e..42ca701acdd 100644 --- a/Mage.Sets/src/mage/sets/modernmasters/Epochrasite.java +++ b/Mage.Sets/src/mage/sets/modernmasters/Epochrasite.java @@ -34,7 +34,7 @@ import mage.abilities.Ability; import mage.abilities.common.DiesTriggeredAbility; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.InvertCondition; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.GainSuspendEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; @@ -67,7 +67,7 @@ public class Epochrasite extends CardImpl { // Epochrasite enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand. this.addAbility(new EntersBattlefieldAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance(3)), - new InvertCondition(new CastFromHandCondition()), + new InvertCondition(new CastFromHandSourceCondition()), "{this} enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand",""), new CastFromHandWatcher()); diff --git a/Mage.Sets/src/mage/sets/newphyrexia/ExclusionRitual.java b/Mage.Sets/src/mage/sets/newphyrexia/ExclusionRitual.java index 1477e7020d9..48f068bf5ca 100644 --- a/Mage.Sets/src/mage/sets/newphyrexia/ExclusionRitual.java +++ b/Mage.Sets/src/mage/sets/newphyrexia/ExclusionRitual.java @@ -28,8 +28,6 @@ package mage.sets.newphyrexia; import java.util.UUID; - -import mage.constants.*; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -37,6 +35,7 @@ import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; @@ -50,6 +49,7 @@ import mage.target.TargetPermanent; * @author Loki */ public class ExclusionRitual extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent("nonland permanent"); static { @@ -60,7 +60,6 @@ public class ExclusionRitual extends CardImpl { super(ownerId, 10, "Exclusion Ritual", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{4}{W}{W}"); this.expansionSetCode = "NPH"; - // Imprint - When Exclusion Ritual enters the battlefield, exile target nonland permanent. Ability ability = new EntersBattlefieldTriggeredAbility(new ExclusionRitualImprintEffect(), false); ability.addTarget(new TargetPermanent(filter)); @@ -80,6 +79,7 @@ public class ExclusionRitual extends CardImpl { } class ExclusionRitualImprintEffect extends OneShotEffect { + ExclusionRitualImprintEffect() { super(Outcome.Exile); staticText = "exile target nonland permanent"; @@ -108,6 +108,7 @@ class ExclusionRitualImprintEffect extends OneShotEffect { } class ExclusionRitualReplacementEffect extends ContinuousRuleModifyingEffectImpl { + ExclusionRitualReplacementEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); staticText = "Players can't cast spells with the same name as the exiled card"; @@ -116,12 +117,12 @@ class ExclusionRitualReplacementEffect extends ContinuousRuleModifyingEffectImpl ExclusionRitualReplacementEffect(final ExclusionRitualReplacementEffect effect) { super(effect); } - + @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.CAST_SPELL; + return event.getType() == GameEvent.EventType.CAST_SPELL_LATE; } - + @Override public boolean applies(GameEvent event, Ability source, Game game) { Permanent sourcePermanent = game.getPermanent(source.getSourceId()); diff --git a/Mage.Sets/src/mage/sets/newphyrexia/HexParasite.java b/Mage.Sets/src/mage/sets/newphyrexia/HexParasite.java index dd9bf4bfea6..7993a204415 100644 --- a/Mage.Sets/src/mage/sets/newphyrexia/HexParasite.java +++ b/Mage.Sets/src/mage/sets/newphyrexia/HexParasite.java @@ -103,13 +103,13 @@ class HexParasiteEffect extends OneShotEffect { for (String counterName : counterNames) { if (player.chooseUse(Outcome.Neutral, "Do you want to remove " + counterName + " counters?", source, game)) { if (permanent.getCounters().get(counterName).getCount() == 1 || toRemove == 1) { - permanent.getCounters().removeCounter(counterName, 1); + permanent.removeCounters(counterName, 1, game); removed++; } else { int amount = player.getAmount(1, Math.min(permanent.getCounters().get(counterName).getCount(), toRemove - removed), "How many?", game); if (amount > 0) { removed += amount; - permanent.getCounters().removeCounter(counterName, amount); + permanent.removeCounters(counterName, amount, game); } } } diff --git a/Mage.Sets/src/mage/sets/oathofthegatewatch/ReflectorMage.java b/Mage.Sets/src/mage/sets/oathofthegatewatch/ReflectorMage.java index c7b05e1e41d..e4a42df569e 100644 --- a/Mage.Sets/src/mage/sets/oathofthegatewatch/ReflectorMage.java +++ b/Mage.Sets/src/mage/sets/oathofthegatewatch/ReflectorMage.java @@ -47,6 +47,7 @@ import mage.filter.predicate.permanent.ControllerPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; import mage.game.turn.Step; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; @@ -110,7 +111,9 @@ class ReflectorMageEffect extends OneShotEffect { Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature != null) { controller.moveCards(targetCreature, Zone.HAND, source, game); - game.addEffect(new ExclusionRitualReplacementEffect(targetCreature.getName(), targetCreature.getOwnerId()), source); + if (!targetCreature.getName().isEmpty()) { // if the creature had no name, no restrict effect will be created + game.addEffect(new ExclusionRitualReplacementEffect(targetCreature.getName(), targetCreature.getOwnerId()), source); + } } return true; } @@ -138,13 +141,17 @@ class ExclusionRitualReplacementEffect extends ContinuousRuleModifyingEffectImpl @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.CAST_SPELL; + 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) { + Spell spell = game.getState().getStack().getSpell(event.getSourceId()); + if (spell != null && spell.isFaceDown(game)) { + return false; // Face Down cast spell (Morph creature) has no name + } return card.getName().equals(creatureName); } return false; diff --git a/Mage.Sets/src/mage/sets/pdsslivers/WildPair.java b/Mage.Sets/src/mage/sets/pdsslivers/WildPair.java new file mode 100644 index 00000000000..700d6dbf9fb --- /dev/null +++ b/Mage.Sets/src/mage/sets/pdsslivers/WildPair.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.pdsslivers; + +import java.util.UUID; + +/** + * + * @author fenhl + */ +public class WildPair extends mage.sets.planarchaos.WildPair { + + public WildPair(UUID ownerId) { + super(ownerId); + this.cardNumber = 30; + this.expansionSetCode = "H09"; + } + + public WildPair(final WildPair card) { + super(card); + } + + @Override + public WildPair copy() { + return new WildPair(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/planarchaos/WildPair.java b/Mage.Sets/src/mage/sets/planarchaos/WildPair.java new file mode 100644 index 00000000000..0f917e69404 --- /dev/null +++ b/Mage.Sets/src/mage/sets/planarchaos/WildPair.java @@ -0,0 +1,175 @@ +/* + * 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.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardsImpl; +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.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.IntComparePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; +import mage.watchers.common.CastFromHandWatcher; + +/** + * + * @author fenhl + */ +public class WildPair extends CardImpl { + + public WildPair(UUID ownerID) { + super(ownerID, 30, "Wild Pair", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}{G}"); + this.expansionSetCode = "PLC"; + + // Whenever a creature enters the battlefield, if you cast it from your hand, you may search your library for a creature card with the same total power and toughness and put it onto the battlefield. If you do, shuffle your library. + this.addAbility(new ConditionalTriggeredAbility( + new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new WildPairEffect(), new FilterCreaturePermanent("a creature"), true, SetTargetPointer.PERMANENT, ""), + new CastFromHandTargetCondition(), + "Whenever a creature enters the battlefield, if you cast it from your hand, you may search your library for a creature card with the same total power and toughness and put it onto the battlefield. If you do, shuffle your library." + ), new CastFromHandWatcher()); + } + + public WildPair(final WildPair card) { + super(card); + } + + @Override + public WildPair copy() { + return new WildPair(this); + } +} + +class WildPairEffect extends OneShotEffect { + + public WildPairEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "search your library for a creature card with the same total power and toughness and put it onto the battlefield"; + } + + public WildPairEffect(final WildPairEffect effect) { + super(effect); + } + + @Override + public WildPairEffect copy() { + return new WildPairEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent permanent = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); + if (permanent != null) { + int totalPT = permanent.getPower().getValue() + permanent.getToughness().getValue(); + FilterCreatureCard filter = new FilterCreatureCard("creature card with total power and toughness " + totalPT); + filter.add(new TotalPowerAndToughnessPredicate(Filter.ComparisonType.Equal, totalPT)); + TargetCardInLibrary target = new TargetCardInLibrary(1, filter); + if (controller.searchLibrary(target, game)) { + if (target.getTargets().size() > 0) { + controller.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); + } + } + controller.shuffleLibrary(game); + return true; + } + } + return false; + } +} + +/** + * + * @author fenhl + */ +class TotalPowerAndToughnessPredicate extends IntComparePredicate { + + public TotalPowerAndToughnessPredicate(Filter.ComparisonType type, int value) { + super(type, value); + } + + @Override + protected int getInputValue(MageObject input) { + return input.getPower().getValue() + input.getToughness().getValue(); + } + + @Override + public String toString() { + return "TotalPowerAndToughness" + super.toString(); + } +} + +class CastFromHandTargetCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + UUID targetId = source.getEffects().get(0).getTargetPointer().getFirst(game, source); + Permanent permanent = game.getPermanentEntering(targetId); + int zccDiff = 0; + if (permanent == null) { + permanent = game.getPermanentOrLKIBattlefield(targetId); // can be alredy again removed from battlefield so also check LKI + zccDiff = -1; + } + if (permanent != null) { + // check that the spell is still in the LKI + Spell spell = game.getStack().getSpell(targetId); + if (spell == null || spell.getZoneChangeCounter(game) != permanent.getZoneChangeCounter(game) + zccDiff) { + if (game.getLastKnownInformation(targetId, Zone.STACK, permanent.getZoneChangeCounter(game) + zccDiff) == null) { + return false; + } + } + CastFromHandWatcher watcher = (CastFromHandWatcher) game.getState().getWatchers().get(CastFromHandWatcher.class.getName()); + if (watcher != null && watcher.spellWasCastFromHand(targetId)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return "you cast it from your hand"; + } + +} diff --git a/Mage.Sets/src/mage/sets/saviorsofkamigawa/DescendantOfMasumaro.java b/Mage.Sets/src/mage/sets/saviorsofkamigawa/DescendantOfMasumaro.java index 30fe71593a1..46403355e84 100644 --- a/Mage.Sets/src/mage/sets/saviorsofkamigawa/DescendantOfMasumaro.java +++ b/Mage.Sets/src/mage/sets/saviorsofkamigawa/DescendantOfMasumaro.java @@ -101,8 +101,8 @@ class DescendantOfMasumaroEffect extends OneShotEffect { } Player targetOpponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetOpponent != null && targetOpponent.getHand().size() > 0) { - sourcePermanent.getCounters().removeCounter(CounterType.P1P1, targetOpponent.getHand().size()); - game.informPlayers(controller.getLogName() + " removes " + targetOpponent.getHand().size() + " +1/+1 counters from " + sourcePermanent.getLogName()); + sourcePermanent.removeCounters(CounterType.P1P1.getName(), targetOpponent.getHand().size(), game); + game.informPlayers(controller.getLogName() + " removes " + targetOpponent.getHand().size() + " +1/+1 counters from " + sourcePermanent.getLogName()); } return true; } diff --git a/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java b/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java index 769130d982f..e8ac8053b99 100644 --- a/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java +++ b/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java @@ -33,7 +33,7 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.DiesTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; @@ -77,7 +77,7 @@ public class InameAsOne extends CardImpl { // When Iname as One enters the battlefield, if you cast it from your hand, you may search your library for a Spirit permanent card, put it onto the battlefield, then shuffle your library. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 1, filter)), true), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, you may search your library for a Spirit permanent card, put it onto the battlefield, then shuffle your library."), new CastFromHandWatcher()); diff --git a/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java b/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java index 403d9c7a95c..a8bae0da416 100644 --- a/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java +++ b/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.Mana; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.BasicManaEffect; import mage.cards.CardImpl; @@ -55,7 +55,7 @@ public class CoalStoker extends CardImpl { // When Coal Stoker enters the battlefield, if you cast it from your hand, add {R}{R}{R} to your mana pool. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new BasicManaEffect(new Mana(3, 0, 0, 0, 0, 0, 0, 0)), false), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, add {R}{R}{R} to your mana pool."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain7.java b/Mage.Sets/src/mage/sets/sorinvstibalt/Swamp4.java similarity index 84% rename from Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain7.java rename to Mage.Sets/src/mage/sets/sorinvstibalt/Swamp4.java index 3cbee622f1f..e3168f22e36 100644 --- a/Mage.Sets/src/mage/sets/izzetvsgolgari/Mountain7.java +++ b/Mage.Sets/src/mage/sets/sorinvstibalt/Swamp4.java @@ -25,7 +25,7 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ -package mage.sets.izzetvsgolgari; +package mage.sets.sorinvstibalt; import java.util.UUID; @@ -33,19 +33,19 @@ import java.util.UUID; * * @author fireshoes */ -public class Mountain7 extends mage.cards.basiclands.Mountain { +public class Swamp4 extends mage.cards.basiclands.Swamp { - public Mountain7(UUID ownerId) { - super(ownerId, 43); - this.expansionSetCode = "DDJ"; + public Swamp4(UUID ownerId) { + super(ownerId, 78); + this.expansionSetCode = "DDK"; } - public Mountain7(final Mountain7 card) { + public Swamp4(final Swamp4 card) { super(card); } @Override - public Mountain7 copy() { - return new Mountain7(this); + public Swamp4 copy() { + return new Swamp4(this); } } diff --git a/Mage.Sets/src/mage/sets/stronghold/CrovaxTheCursed.java b/Mage.Sets/src/mage/sets/stronghold/CrovaxTheCursed.java index e52f5a3b318..907e6cfd8c5 100644 --- a/Mage.Sets/src/mage/sets/stronghold/CrovaxTheCursed.java +++ b/Mage.Sets/src/mage/sets/stronghold/CrovaxTheCursed.java @@ -73,10 +73,9 @@ public class CrovaxTheCursed extends CardImpl { Ability ability = new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new CrovaxTheCursedEffect(), TargetController.YOU, false); this.addAbility(ability); - // {B}: Crovax gains flying until end of turn. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), new ManaCostsImpl("{B}"))); - + } public CrovaxTheCursed(final CrovaxTheCursed card) { @@ -118,11 +117,9 @@ class CrovaxTheCursedEffect extends OneShotEffect { game.informPlayers(controller.getLogName() + " puts a +1/+1 counter on " + sourceObject.getName()); } } - } else { - if (sourceObject != null && sourceObject.getCounters().containsKey(CounterType.P1P1)) { - sourceObject.getCounters().removeCounter(CounterType.P1P1, 1); - game.informPlayers(controller.getLogName() + " removes a +1/+1 counter from " + sourceObject.getName()); - } + } else if (sourceObject != null && sourceObject.getCounters().containsKey(CounterType.P1P1)) { + sourceObject.removeCounters(CounterType.P1P1.getName(), 1, game); + game.informPlayers(controller.getLogName() + " removes a +1/+1 counter from " + sourceObject.getName()); } return true; } diff --git a/Mage.Sets/src/mage/sets/tempest/Magmasaur.java b/Mage.Sets/src/mage/sets/tempest/Magmasaur.java index c305535b675..5d3b1d014f7 100644 --- a/Mage.Sets/src/mage/sets/tempest/Magmasaur.java +++ b/Mage.Sets/src/mage/sets/tempest/Magmasaur.java @@ -107,10 +107,10 @@ class MagmasaurEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent sourceObject = (Permanent)source.getSourceObjectIfItStillExists(game); + Permanent sourceObject = (Permanent) source.getSourceObjectIfItStillExists(game); if (sourceObject != null && controller != null) { if (controller.chooseUse(outcome, "Remove a +1/+1 counter from " + sourceObject.getLogName() + "?", source, game)) { - sourceObject.getCounters().removeCounter(CounterType.P1P1, 1); + sourceObject.removeCounters(CounterType.P1P1.getName(), 1, game); } else { int counters = sourceObject.getCounters().getCount(CounterType.P1P1); sourceObject.sacrifice(source.getSourceId(), game); diff --git a/Mage.Sets/src/mage/sets/tenthedition/PhageTheUntouchable.java b/Mage.Sets/src/mage/sets/tenthedition/PhageTheUntouchable.java index 61e5130d2e8..ce4651de8fa 100644 --- a/Mage.Sets/src/mage/sets/tenthedition/PhageTheUntouchable.java +++ b/Mage.Sets/src/mage/sets/tenthedition/PhageTheUntouchable.java @@ -33,7 +33,7 @@ import mage.abilities.common.DealsCombatDamageToACreatureTriggeredAbility; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.InvertCondition; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.LoseGameSourceControllerEffect; @@ -62,7 +62,7 @@ public class PhageTheUntouchable extends CardImpl { // When Phage the Untouchable enters the battlefield, if you didn't cast it from your hand, you lose the game. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new LoseGameSourceControllerEffect(), false), - new InvertCondition(new CastFromHandCondition()), + new InvertCondition(new CastFromHandSourceCondition()), "When {this} enters the battlefield, if you didn't cast it from your hand, you lose the game" ), new CastFromHandWatcher()); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java index 276f8ee4384..b373a20616f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java @@ -159,7 +159,83 @@ public class BloodMoonTest extends CardTestPlayerBase { assertHandCount(playerA, 1); // Check that the Steam Vents produces only {R} Assert.assertTrue("The mana the land can produce should be [{U}] but it's " + playerB.getManaAvailable(currentGame).toString(), playerB.getManaAvailable(currentGame).toString().equals("[{U}]")); - } + /** + * Possible bug reported: Blood Moon effects no longer appearing with + * Pithing Needle naming Blood Moon. + * + * Testing Blood Moon on battlefield before Pithing Needle naming it. + * Non-basics should still only produce {R} + */ + @Test + public void testBloodMoonBeforePithingNeedle() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // Blood Moon 2R + // Enchantment + // Nonbasic lands are Mountains + addCard(Zone.HAND, playerA, "Blood Moon", 1); + + // Artifact (1) + // As Pithing Needle enters the battlefield, name a card. + // Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. + addCard(Zone.HAND, playerB, "Pithing Needle"); // {1} + addCard(Zone.HAND, playerB, "Ghost Quarter", 1); + // {T}: Add {C} to your mana pool. + // {T}, Sacrifice Ghost Quarter: Destroy target land. Its controller may search his or her library for a basic land card, put it onto the battlefield, then shuffle his or her library. + addCard(Zone.BATTLEFIELD, playerB, "Ghost Quarter", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blood Moon"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Pithing Needle"); + setChoice(playerB, "Blood Moon"); + playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Ghost Quarter"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Blood Moon", 1); + assertPermanentCount(playerB, "Pithing Needle", 1); + assertPermanentCount(playerB, "Ghost Quarter", 2); + + Assert.assertTrue("The mana Ghost Quarter can produce should be [{R}] but it's " + playerB.getManaAvailable(currentGame).toString(), playerB.getManaAvailable(currentGame).toString().equals("[{R}]")); + } + + /** + * Possible bug reported: Blood Moon effects no longer appearing with + * Pithing Needle naming Blood Moon. + * + * Testing Pithing Needle on the battlefield naming Blood Moon, then playing + * Blood Moon after. Non-basics should still only produce {R} + */ + @Test + public void testBloodMoonAfterPithingNeedle() { + + // Artifact (1) + // As Pithing Needle enters the battlefield, name a card. + // Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. + addCard(Zone.HAND, playerA, "Pithing Needle"); // {1} + addCard(Zone.BATTLEFIELD, playerA, "Ghost Quarter", 1); + + // Blood Moon 2R + // Enchantment + // Nonbasic lands are Mountains + addCard(Zone.HAND, playerB, "Blood Moon", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle"); + setChoice(playerA, "Blood Moon"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Blood Moon"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + + execute(); + + assertPermanentCount(playerB, "Blood Moon", 1); + assertPermanentCount(playerA, "Pithing Needle", 1); + assertPermanentCount(playerA, "Ghost Quarter", 1); + + Assert.assertTrue("The mana the land can produce should be [{R}] but it's " + playerA.getManaAvailable(currentGame).toString(), playerA.getManaAvailable(currentGame).toString().equals("[{R}]")); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java new file mode 100644 index 00000000000..2dad85bb0a6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java @@ -0,0 +1,81 @@ +/* + * 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.abilities.enters; + +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 SkylineCascadeTest extends CardTestPlayerBase { + + /** + * Reported bug on Skyline Cascade not working properly. + * + * Test the typical situation - tapped creature not being able to untap during next untap step. + */ + @Test + public void testPreventsTappedCreatureUntapping() { + + // {W} 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Savannah Lions"); + + /** + * Skyline Cascade enters the battlefield tapped. + * When Skyline Cascade enters the battlefield, target creature an opponent controls doesn't untap during its controller's next untap step. + * Tap: Add {U} to your mana pool. + */ + addCard(Zone.HAND, playerB, "Skyline Cascade"); + + attack(1, playerA, "Savannah Lions"); + + playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Skyline Cascade"); + addTarget(playerB, "Savannah Lions"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + + execute(); + + assertTapped("Savannah Lions", true); + assertTapped("Skyline Cascade", true); + } + + /** + * Reported bug on Skyline Cascade not working properly. + * + * "Skyline Cascade’s triggered ability doesn't tap the creature. It can target any creature, tapped or untapped. + * If that creature is already untapped at the beginning of its controller’s next untap step, the effect won’t do anything." + * http://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid=402038 + * + * An untapped creature will remain untapped. + */ + @Test + public void testDoesNotStopUntappedCreatureUntapping() { + + // {W} 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Savannah Lions"); + + /** + * Skyline Cascade enters the battlefield tapped. + * When Skyline Cascade enters the battlefield, target creature an opponent controls doesn't untap during its controller's next untap step. + * Tap: Add {U} to your mana pool. + */ + addCard(Zone.HAND, playerB, "Skyline Cascade"); + + playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Skyline Cascade"); + addTarget(playerB, "Savannah Lions"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + + execute(); + + assertTapped("Savannah Lions", false); + assertTapped("Skyline Cascade", true); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java index 7b35f5ef6a2..00f770a05f8 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java @@ -640,6 +640,91 @@ public class MorphTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Pine Walker", 1); assertPowerToughness(playerA, "Pine Walker", 5, 5); assertTapped("Pine Walker", false); + } + + /** + * Reflector Mage bouncing a creature that can be played as a morph should not prevent the card + * from being replayed as a morph. Morph creatures are nameless. + * + * Reported bug: + * Face-up morph creatures that are bounced by Reflector Mage should be able to be replayed as morphs + * without the "until the next turn" restriction." + */ + @Test + public void testReflectorMageBouncesFaceupCreatureReplayAsMorph() { + + // {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); + + //Tap: Add {G}, {U}, or {R} to your mana pool. + // Morph 2 (You may cast this card face down as a 2/2 creature for 3. Turn it face up any time for its morph cost.) + // When Rattleclaw Mystic is turned face up, add {G}{U}{R} to your mana pool. + addCard(Zone.BATTLEFIELD, playerB, "Rattleclaw Mystic"); // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Forest"); + addCard(Zone.BATTLEFIELD, playerB, "Island"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reflector Mage"); + addTarget(playerA, "Rattleclaw Mystic"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Rattleclaw Mystic"); + setChoice(playerB, "Yes"); // cast it face down as 2/2 creature + setStopAt(2, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertPermanentCount(playerA, "Reflector Mage", 1); + assertPermanentCount(playerB, "Rattleclaw Mystic", 0); + assertHandCount(playerB, "Rattleclaw Mystic", 0); // should have been replayed + assertPermanentCount(playerB, "", 1); // Rattleclaw played as a morph + } + + /** + * Reflector Mage bouncing a creature that can be played as a morph should not prevent the card + * from being replayed as a morph. Morph creatures are nameless. + * + * Reported bug: + * Face-up morph creatures that are bounced by Reflector Mage should be able to be replayed as morphs + * without the "until the next turn" restriction." + * + * Testing bouncing a face-down creature played next turn face-up. + */ + @Test + public void testReflectorMageBouncesMorphCreatureReplayAsFaceup() { + + //Tap: Add {G}, {U}, or {R} to your mana pool. + // Morph 2 (You may cast this card face down as a 2/2 creature for 3. Turn it face up any time for its morph cost.) + // When Rattleclaw Mystic is turned face up, add {G}{U}{R} to your mana pool. + addCard(Zone.HAND, playerA, "Rattleclaw Mystic"); // 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + // {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, playerB, "Reflector Mage"); // 2/3 + addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rattleclaw Mystic"); + setChoice(playerA, "Yes"); // cast it face down as 2/2 creature + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Reflector Mage"); + addTarget(playerB, ""); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Rattleclaw Mystic"); + setChoice(playerA, "No"); // cast it face down as 2/2 creature + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertPermanentCount(playerB, "Reflector Mage", 1); + assertPermanentCount(playerA, "Rattleclaw Mystic", 1); + assertHandCount(playerA, "Rattleclaw Mystic", 0); // should have been replayed faceup } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java index c1b2a801a94..5900a917886 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java @@ -75,8 +75,112 @@ public class DisruptingShoalTest extends CardTestPlayerBase { assertHandCount(playerA, "Spell Snare", 1); // Can't be cast -> no valid target assertGraveyardCount(playerA, "Pillarfield Ox", 1); - - - } + + /** + * Test that Disrupting Shoal can be played with alternate casting costs + * And the X Value can be equal to either half of a fuse card. + * + * Reported bug: "Casting Disrupting Shoal pitching Far // Away does not counter spells with converted mana cost 2 or 3, + * which it should. Instead it does counter spells with converted mana cost 5, which it shouldn't". + */ + @Test + public void testWithFuseCardCounterCMCTwo() { + + // CMC 2 and CMC 3 + addCard(Zone.HAND, playerA, "Grizzly Bears"); // 2/2 {1}{G} + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + /** + * Far {1}{U} Instant Return target creature to its owner's hand. Away + * {2}{B} Instant Target player sacrifices a creature. Fuse (You may + * cast one or both halves of this card from your hand.) + */ + addCard(Zone.HAND, playerB, "Disrupting Shoal", 1); + addCard(Zone.HAND, playerB, "Far // Away", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Disrupting Shoal", "Grizzly Bears", "Grizzly Bears"); + playerB.addChoice("Yes"); // use alternate costs = 2 CMC = Far + + setStopAt(1, PhaseStep.CLEANUP); + execute(); + + assertExileCount(playerB, 1); // Far // Away should be exiled as part of Disrupting alternative cost + assertGraveyardCount(playerB,"Disrupting Shoal", 1); + assertPermanentCount(playerA, "Grizzly Bears", 0); // should have been countered by Shoal + assertGraveyardCount(playerA, "Grizzly Bears", 1); + } + + /** + * Test that Disrupting Shoal can be played with alternate casting costs + * And the X Value can be equal to either half of a fuse card. + * + * Reported bug: "Casting Disrupting Shoal pitching Far // Away does not counter spells with converted mana cost 2 or 3, + * which it should. Instead it does counter spells with converted mana cost 5, which it shouldn't". + */ + @Test + public void testWithFuseCardCounterCMCThree() { + + addCard(Zone.HAND, playerA, "Centaur Courser"); // 3/3 {2}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + /** + * Far {1}{U} Instant Return target creature to its owner's hand. Away + * {2}{B} Instant Target player sacrifices a creature. Fuse (You may + * cast one or both halves of this card from your hand.) + */ + addCard(Zone.HAND, playerB, "Disrupting Shoal", 1); + addCard(Zone.HAND, playerB, "Far // Away", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Centaur Courser"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Disrupting Shoal", "Centaur Courser", "Centaur Courser"); + playerB.addChoice("Yes"); // use alternate costs = 3 CMC = Away + + setStopAt(1, PhaseStep.CLEANUP); + execute(); + + assertExileCount(playerB, 1); // Far // Away should be exiled as part of Disrupting alternative cost + assertGraveyardCount(playerB,"Disrupting Shoal", 1); + assertPermanentCount(playerA, "Centaur Courser", 0); // should have been countered by Shoal + assertGraveyardCount(playerA, "Centaur Courser", 1); + } + + /** + * Test that Disrupting Shoal can be played with alternate casting costs + * And the X Value can be equal to either half of a fuse card. Not the combined cost of both. + * + * Reported bug: "Casting Disrupting Shoal pitching Far // Away does not counter spells with converted mana cost 2 or 3, + * which it should. Instead it does counter spells with converted mana cost 5, which it shouldn't". + */ + @Test + public void testWithFuseCardShouldNotCounterCMCFive() { + + addCard(Zone.HAND, playerA, "Air Elemental"); // 4/4 Flying {3}{U}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + /** + * Far {1}{U} Instant Return target creature to its owner's hand. Away + * {2}{B} Instant Target player sacrifices a creature. Fuse (You may + * cast one or both halves of this card from your hand.) + */ + addCard(Zone.HAND, playerB, "Disrupting Shoal", 1); + addCard(Zone.HAND, playerB, "Far // Away", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Air Elemental"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Disrupting Shoal", "Air Elemental", "Air Elemental"); + playerB.addChoice("Yes"); // use alternate costs = 2 or 3 CMC = Far // Away, not the combined cost! + + setStopAt(1, PhaseStep.CLEANUP); + execute(); + + assertExileCount(playerB, 1); // Far // Away should be exiled as part of Disrupting alternative cost + assertGraveyardCount(playerB,"Disrupting Shoal", 1); + assertPermanentCount(playerA, "Air Elemental", 1); // should NOT have been countered by Shoal + assertGraveyardCount(playerA, "Air Elemental", 0); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java index 1fe8be8bb1a..123ce672da4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java @@ -130,72 +130,73 @@ public class EntersTheBattlefieldTriggerTest extends CardTestPlayerBase { assertLife(playerA, 15); assertLife(playerB, 20); } - + /** * Dread Cacodemon's abilities should only trigger when cast from hand. - * - * Testing when cast from hand abilities take effect. - * Cast from hand destroys opponents creatures and taps all other creatures owner controls. + * + * Testing when cast from hand abilities take effect. Cast from hand + * destroys opponents creatures and taps all other creatures owner controls. */ @Test public void testDreadCacodemonConditionTrue() { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); - + // When Dread Cacodemon enters the battlefield, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. addCard(Zone.HAND, playerA, "Dread Cacodemon", 1); // 8/8 - {7}{B}{B}{B} - + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - + // Protection from white, first strike addCard(Zone.BATTLEFIELD, playerA, "Black Knight", 2); // {B}{B} // Deathtouch addCard(Zone.BATTLEFIELD, playerB, "Typhoid Rats", 2); // {B} - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dread Cacodemon"); setStopAt(1, PhaseStep.END_TURN); - + execute(); assertPermanentCount(playerB, "Typhoid Rats", 0); assertPermanentCount(playerA, "Dread Cacodemon", 1); - assertPermanentCount(playerA, "Black Knight", 2); + assertPermanentCount(playerA, "Black Knight", 2); assertTappedCount("Black Knight", true, 2); assertTapped("Dread Cacodemon", false); } - - /** + + /** * Dread Cacodemon's abilities should only trigger when cast from hand. - * + * * Testing when card is not cast from hand, abilities do not take effect. - * All opponents creatures remain alive and owner's creatures are not tapped. + * All opponents creatures remain alive and owner's creatures are not + * tapped. */ @Test public void testDreadCacodemonConditionFalse() { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); - + // When Dread Cacodemon enters the battlefield, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. addCard(Zone.GRAVEYARD, playerA, "Dread Cacodemon", 1); // 8/8 - {7}{B}{B}{B} // Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost. addCard(Zone.HAND, playerA, "Reanimate", 1); // {B} - + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - + // Protection from white, first strike addCard(Zone.BATTLEFIELD, playerA, "Black Knight", 2); // {B}{B} // Deathtouch addCard(Zone.BATTLEFIELD, playerB, "Typhoid Rats", 2); // {B} - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate", "Dread Cacodemon"); setStopAt(1, PhaseStep.END_TURN); - + execute(); assertPermanentCount(playerB, "Typhoid Rats", 2); assertGraveyardCount(playerA, "Reanimate", 1); assertPermanentCount(playerA, "Dread Cacodemon", 1); - assertPermanentCount(playerA, "Black Knight", 2); + assertPermanentCount(playerA, "Black Knight", 2); assertTappedCount("Black Knight", false, 2); assertTapped("Dread Cacodemon", false); @@ -203,4 +204,27 @@ public class EntersTheBattlefieldTriggerTest extends CardTestPlayerBase { assertLife(playerB, 20); } + /** + * Test that the cast from hand condition works for target permanent + * + */ + @Test + public void testWildPair() { + + // Whenever a creature enters the battlefield, if you cast it from your hand, you may search your library for a creature card with the same total power and toughness and put it onto the battlefield. If you do, shuffle your library. + addCard(Zone.BATTLEFIELD, playerA, "Wild Pair"); + addCard(Zone.HAND, playerA, "Silvercoat Lion", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + setChoice(playerA, "Silvercoat Lion"); + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertPermanentCount(playerA, "Silvercoat Lion", 2); + + } + } diff --git a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandSourceCondition.java similarity index 93% rename from Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java rename to Mage/src/main/java/mage/abilities/condition/common/CastFromHandSourceCondition.java index d14fc200ac8..99fddbba139 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandSourceCondition.java @@ -13,7 +13,7 @@ import mage.watchers.common.CastFromHandWatcher; * * @author Loki */ -public class CastFromHandCondition implements Condition { +public class CastFromHandSourceCondition implements Condition { @Override public boolean apply(Game game, Ability source) { diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 6d35aad001e..5b39cac2a6d 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1192,7 +1192,7 @@ public abstract class GameImpl implements Game, Serializable { @Override public synchronized void concede(UUID playerId) { Player player = state.getPlayer(playerId); - if (player != null) { + if (player != null && !player.hasLost()) { logger.debug("Player " + player.getName() + " concedes game " + this.getId()); fireInformEvent(player.getLogName() + " has conceded."); player.concede(this); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 9f3612fb827..b3cc247fb5f 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2580,9 +2580,17 @@ public abstract class PlayerImpl implements Player, Serializable { playable.add(ability); } } - } else if (card.getCardType().contains(CardType.LAND) && ability instanceof AlternativeSourceCosts) { - if (canLandPlayAlternateSourceCostsAbility(card, availableMana, ability, game)) { // e.g. Land with Morph - playable.add(ability); + } else if (ability instanceof AlternativeSourceCosts) { + if (card.getCardType().contains(CardType.LAND)) { + if (canLandPlayAlternateSourceCostsAbility(card, availableMana, ability, game)) { // e.g. Land with Morph + playable.add(ability); + } + } else if (card.getCardType().contains(CardType.CREATURE)) { // e.g. makes a card available for play by Morph if the card may not be cast normally + if (!playable.contains(card.getSpellAbility())) { + if (((AlternativeSourceCosts) ability).isAvailable(card.getSpellAbility(), game)) { + playable.add(card.getSpellAbility()); + } + } } } } @@ -2701,7 +2709,8 @@ public abstract class PlayerImpl implements Player, Serializable { * */ @Override - public Set getPlayableInHand(Game game) { + public Set getPlayableInHand(Game game + ) { Set playable = new HashSet<>(); if (!shouldSkipGettingPlayable(game)) { ManaOptions available = getManaAvailable(game); diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index c0b3a4189cb..c7460751686 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -5777,7 +5777,7 @@ Elves of Deep Shadow|Friday Night Magic|68|U|{G}|Creature Elf Druid|1|1|{tap}: Armadillo Cloak|Friday Night Magic|69|U|{1}{W}{G}|Enchantment Aura|||Enchant creature$Enchanted creature gets +2/+2 and has trample.$Whenever enchanted creature deals damage, you gain that much life.| Terminate|Friday Night Magic|70|U|{B}{R}|Instant|||Destroy target creature. It can't be regenerated.| Lobotomy|Friday Night Magic|71|U|{2}{U}{B}|Sorcery|||Target player reveals his or her hand, then you choose a card other than a basic land card from it. Search that player's graveyard, hand, and library for all cards with the same name as the chosen card and exile them. Then that player shuffles his or her library.| -Goblim Warchief|Friday Night Magic|72|U|{1}{R}{R}|Creature Goblin|2|2|Goblin spells you cast cost {1} less to cast.$Goblin creatures you control have haste.| +Goblin Warchief|Friday Night Magic|72|U|{1}{R}{R}|Creature Goblin|2|2|Goblin spells you cast cost {1} less to cast.$Goblin creatures you control have haste.| Wild Mongrel|Friday Night Magic|73|U|{1}{G}|Creature Hound|2|2|Discard a card: Wild Mongrel gets +1/+1 and becomes the color of your choice until end of turn.| Chainer's Edict|Friday Night Magic|74|U|{1}{B}|Sorcery|||Target player sacrifices a creature.$Flashback {5}{B}{B} (You may cast this card from your graveyard for its flashback cost. Then exile it.)| Circular Logic|Friday Night Magic|75|U|{2}{U}|Instant|||Counter target spell unless its controller pays {1} for each card in your graveyard.$Madness {U} (If you discard this card, you may cast it for its madness cost instead of putting it into your graveyard.)| @@ -5898,6 +5898,8 @@ Anticipate|Friday Night Magic|187|C|{1}{U}|Instant|||Look at the top three cards Nissa's Pilgrimage|Friday Night Magic|188|C|{2}{G}|Sorcery|||Search your library for up to two basic Forest cards, reveal those cards, and put one onto the battlefield tapped and the rest into your hand. Then shuffle your library.$Spell Mastery — If there are two or more instant and/or sorcery cards in your graveyard, search your library for up to three basic Forest cards instead of two.| Clash of Wills|Friday Night Magic|189|U|{X}{U}|Instant|||Counter target spell unless its controller pays {X}.| Smash to Smithereens|Friday Night Magic|190|C|{1}{R}|Instant|||Destroy target artifact. Smash to Smithereens deals 3 damage to that artifact's controller.| +Blighted Fen|Friday Night Magic|191|U||Land|||{T}: Add {C} to your mana pool.${4}{B}, {T}, Sacrifice Blighted Fen: Target opponent sacrifices a creature.| +Goblin Warchief|Friday Night Magic|192|U|{1}{R}{R}|Creature - Goblin|2|2|Goblin spells you cast cost {1} less to cast.$Goblin creatures you control have haste.| Angel of Salvation|Future Sight|1|R|{6}{W}{W}|Creature - Angel|5|5|Flash; convoke (Each creature you tap while casting this spell reduces its cost by {1} or by one mana of that creature's color.)$Flying$When Angel of Salvation enters the battlefield, prevent the next 5 damage that would be dealt this turn to any number of target creatures and/or players, divided as you choose.| Knight of Sursi|Future Sight|10|C|{3}{W}|Creature - Human Knight|2|2|Flying; flanking (Whenever a creature without flanking blocks this creature, the blocking creature gets -1/-1 until end of turn.)$Suspend 3-{W} (Rather than cast this card from your hand, you may pay {W} and exile it with three time counters on it. At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost. It has haste.)| Haze of Rage|Future Sight|100|U|{1}{R}|Sorcery|||Buyback {2} (You may pay an additional {2} as you cast this spell. If you do, put this card into your hand as it resolves.)$Creatures you control get +1/+0 until end of turn.$Storm (When you cast this spell, copy it for each spell cast before it this turn.)| @@ -56934,11 +56936,13 @@ 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.| +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.")| Eerie Interlude|Shadows over Innistrad|994|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.")| Topplegeist|Shadows over Innistrad|995|U|{W}|Creature - Spirit|1|1|Flying$When Topplegeist enters the battlefield, tap target creature an opponent controls.$Delirium — At the beginning of each opponent's upkeep, if there are four or more card types among cards in your graveyard, tap target creature that player controls.| Compelling Deterrence|Shadows over Innistrad|996|U|{1}{U}|Instant|||Return target nonland permanent to its owner's hand. Then that player discards a card if you control a Zombie.| 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.| 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.| Niblis of Dusk|Shadows over Innistrad|76|C|{2}{U}|Creature - Spirit|2|1|Flying$Prowess| @@ -56947,25 +56951,32 @@ Pore Over the Pages|Shadows over Innistrad|79|U|{3}{U}{U}|Sorcery|||Draw three c Stiched 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.| 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.| +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.)| Mindwrack Demon|Shadows over Innistrad|998|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.| Tooth Collector|Shadows over Innistrad|997|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.| 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.)| 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.| 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.| +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.| 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.| 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.| +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.| 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