Merge pull request #1 from magefree/master

merge
This commit is contained in:
GitHubMage 2016-03-04 22:05:27 +01:00
commit b39d85a341
57 changed files with 1020 additions and 214 deletions

View file

@ -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("(<font color=[^>]*>([^<]*)) (\\[[0-9a-fA-F]*\\])</font>", "<a href='#$2'>$1</a> $3");
text = text.replaceAll("(<font color=[^>]*>([^<]*)) (\\[[0-9a-fA-F]*\\])</font>", "<a href=\"#$2\">$1</a> $3");
setEditable(true);
kit.insertHTML(doc, doc.getLength(), text, 0, 0, null);
setEditable(false);

View file

@ -155,7 +155,7 @@ public class DeckGenerator {
* @return the final deck to use.
*/
private static Deck generateDeck(int deckSize, List<ColoredManaSymbol> allowedColors, List<String> 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()]);

View file

@ -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<String> cbSets;
private JComboBox<String> 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<String>(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<String>(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));

View file

@ -51,6 +51,7 @@ public class DeckGeneratorPool
private static final int NONCREATURE_COUNT_60 = 13;
private final List<ColoredManaSymbol> allowedColors;
private boolean colorlessAllowed;
private final List<DeckGeneratorCMC> poolCMCs;
private final int creatureCount;
private final int nonCreatureCount;
@ -68,11 +69,12 @@ public class DeckGeneratorPool
private List<Card> reserveSpells = new ArrayList<>();
private Deck deck;
public DeckGeneratorPool(final int deckSize, final List<ColoredManaSymbol> allowedColors, boolean isSingleton)
public DeckGeneratorPool(final int deckSize, final List<ColoredManaSymbol> 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;
}

View file

@ -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";

View file

@ -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);
}

View file

@ -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) {}
}

View file

@ -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<String, BufferedImage> 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<Path>() {
@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("<img src='file:" + getSymbolsPath(true)
+ "/symbols/" + resourcePath + "/$1$2.jpg' alt='$1$2' width="
+ symbolSize + " height=" + symbolSize + ">");
replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll(
"<img src='file:" + getSymbolsPath(true) + "/symbols/" + resourcePath + "/$1$2.gif' alt='$1$2' width=" + symbolSize
+ " height=" + symbolSize + ">");
}
replaced = replaced.replace("|source|", "{source}");
replaced = replaced.replace("|this|", "{this}");

View file

@ -71,7 +71,7 @@ public class GathererSymbols implements Iterable<DownloadJob> {
return computeNext();
}
String symbol = sym.replaceAll("/", "");
File dst = new File(dir, symbol + ".jpg");
File dst = new File(dir, symbol + ".gif");
switch (symbol) {
case "T":

View file

@ -52,7 +52,9 @@ public class RateCard {
*/
public static int rateCard(Card card, List<ColoredManaSymbol> 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<String, Integer>();
ratings = new HashMap<>();
readFromFile("/m13.csv");
}
}

View file

@ -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
#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

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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")));

View file

@ -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")));

View file

@ -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),

View file

@ -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")));

View file

@ -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")));

View file

@ -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());
}

View file

@ -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);
}
}
}

View file

@ -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());
}

View file

@ -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());
}

View file

@ -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();
}
}

View file

@ -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());
}

View file

@ -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());
}

View file

@ -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());
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -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;

View file

@ -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());

View file

@ -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());

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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<MageObject> {
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";
}
}

View file

@ -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;
}

View file

@ -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());

View file

@ -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());
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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());

View file

@ -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}]"));
}
}

View file

@ -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 Cascades 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 controllers next untap step, the effect wont 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);
}
}

View file

@ -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
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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) {

View file

@ -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);

View file

@ -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<UUID> getPlayableInHand(Game game) {
public Set<UUID> getPlayableInHand(Game game
) {
Set<UUID> playable = new HashSet<>();
if (!shouldSkipGettingPlayable(game)) {
ManaOptions available = getManaAvailable(game);

View file

@ -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} <i>(You may cast this card from your graveyard for its flashback cost. Then exile it.)</i>|
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} <i>(If you discard this card, you may cast it for its madness cost instead of putting it into your graveyard.)</i>|
@ -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.$<i>Spell Mastery</i> — 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 <i>(Each creature you tap while casting this spell reduces its cost by {1} or by one mana of that creature's color.)</i>$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 <i>(Whenever a creature without flanking blocks this creature, the blocking creature gets -1/-1 until end of turn.)</i>$Suspend 3-{W} <i>(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.)</i>|
Haze of Rage|Future Sight|100|U|{1}{R}|Sorcery|||Buyback {2} <i>(You may pay an additional {2} as you cast this spell. If you do, put this card into your hand as it resolves.)</i>$Creatures you control get +1/+0 until end of turn.$Storm <i>(When you cast this spell, copy it for each spell cast before it this turn.)</i>|
@ -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. <i>(Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")</i>|
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. <i>(Put a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card.")</i>|
Topplegeist|Shadows over Innistrad|995|U|{W}|Creature - Spirit|1|1|Flying$When Topplegeist enters the battlefield, tap target creature an opponent controls.$<i>Delirium</i> &mdash; 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 <i>(This creature can't be blocked by creatures with greater power.)</i>|
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.$<i>Delirium</i> &mdash; 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|<i>Delirium</i> &mdash; Hound of the Farborgs has menace as long as there are four or more card types among cards in your graveyard. <i>(A creature with menace can't be blocked except by two or more creatures.)</i>|
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.$<i>Delirium</i> &mdash; 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.$<i>Delirium</i> &mdash; 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 <i>(This creature can't be blocked except by two or more creatures.)</i>$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.${<i>Delirium</i> &mdash; 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} <i>(If you discard this card, you may play it for its madness cost instead of putting it into your graveyard.)</i>|
Incorrigible Youths|Shadows over Innistrad|166|U|{3}{R}{R}|Creature - Vampire|4|3|Haste$Madness {2}{R} <i>(If you discard this card, discard it into exile. When you do, cast it for its madness cost or put it into your graveyard.)</i>|
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 <i>(This creature can block creature with flying.)</i>$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.|