This commit is contained in:
Grimm
2021-11-09 00:50:42 +01:00
459 changed files with 10587 additions and 456 deletions

View File

@@ -714,7 +714,24 @@ public class AiController {
// Check a predefined condition // Check a predefined condition
if (sa.hasParam("AICheckSVar")) { if (sa.hasParam("AICheckSVar")) {
if (!checkAISpecificSVarCondition(sa, sa.getHostCard())) { final Card host = sa.getHostCard();
final String svarToCheck = sa.getParam("AICheckSVar");
String comparator = "GE";
int compareTo = 1;
if (sa.hasParam("AISVarCompare")) {
final String fullCmp = sa.getParam("AISVarCompare");
comparator = fullCmp.substring(0, 2);
final String strCmpTo = fullCmp.substring(2);
try {
compareTo = Integer.parseInt(strCmpTo);
} catch (final Exception ignored) {
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa);
}
}
int left = AbilityUtils.calculateAmount(host, svarToCheck, sa);
if (!Expressions.compare(left, comparator, compareTo)) {
return AiPlayDecision.AnotherTime; return AiPlayDecision.AnotherTime;
} }
} }
@@ -753,6 +770,7 @@ public class AiController {
// one is warded and can't be paid for. // one is warded and can't be paid for.
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
for (Card tgt : sa.getTargets().getTargetCards()) { for (Card tgt : sa.getTargets().getTargetCards()) {
// TODO some older cards don't use the keyword, so check for trigger instead
if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) { if (tgt.hasKeyword(Keyword.WARD) && tgt.isInPlay() && tgt.getController().isOpponentOf(sa.getHostCard().getController())) {
int amount = 0; int amount = 0;
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt); Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
@@ -1809,7 +1827,35 @@ public class AiController {
} }
} }
if (effect.hasParam("AICheckSVar")) { if (effect.hasParam("AICheckSVar")) {
return checkAISpecificSVarCondition(effect, hostCard); System.out.println("aiShouldRun?" + sa);
final String svarToCheck = effect.getParam("AICheckSVar");
String comparator = "GE";
int compareTo = 1;
if (effect.hasParam("AISVarCompare")) {
final String fullCmp = effect.getParam("AISVarCompare");
comparator = fullCmp.substring(0, 2);
final String strCmpTo = fullCmp.substring(2);
try {
compareTo = Integer.parseInt(strCmpTo);
} catch (final Exception ignored) {
if (sa == null) {
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect);
} else {
compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa);
}
}
}
int left = 0;
if (sa == null) {
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect);
} else {
left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa);
}
System.out.println("aiShouldRun?" + left + comparator + compareTo);
return Expressions.compare(left, comparator, compareTo);
} else if (effect.hasParam("AICheckDredge")) { } else if (effect.hasParam("AICheckDredge")) {
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac"); return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
} else return sa != null && doTrigger(sa, false); } else return sa != null && doTrigger(sa, false);
@@ -2315,37 +2361,4 @@ public class AiController {
return Iterables.getFirst(list, null); return Iterables.getFirst(list, null);
} }
private static boolean checkAISpecificSVarCondition(CardTraitBase ab, Card host) {
if (ab.hasParam("AICheckSVar")) {
final String svarToCheck = ab.getParam("AICheckSVar");
String comparator = "GE";
int compareTo = 1;
if (ab.hasParam("AISVarCompare")) {
final String fullCmp = ab.getParam("AISVarCompare");
comparator = fullCmp.substring(0, 2);
final String strCmpTo = fullCmp.substring(2);
try {
compareTo = Integer.parseInt(strCmpTo);
} catch (final Exception ignored) {
if (ab == null) {
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), ab);
} else {
compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), ab);
}
}
}
int left = 0;
if (ab == null) {
left = AbilityUtils.calculateAmount(host, svarToCheck, ab);
} else {
left = AbilityUtils.calculateAmount(host, svarToCheck, ab);
}
return Expressions.compare(left, comparator, compareTo);
}
return false;
}
} }

View File

@@ -2497,4 +2497,27 @@ public class ComputerUtilCombat {
} }
return poison; return poison;
} }
public static GameEntity addAttackerToCombat(SpellAbility sa, Card attacker, FCollection<GameEntity> defenders) {
Combat combat = sa.getHostCard().getGame().getCombat();
if (combat != null) {
// 1. If the card that spawned the attacker was sent at a planeswalker, attack the same. Consider improving.
GameEntity def = combat.getDefenderByAttacker(sa.getHostCard());
if (def != null && def instanceof Card) {
if (((Card)def).isPlaneswalker()) {
return def;
}
}
// 2. Otherwise, go through the list of options one by one, choose the first one that can't be blocked profitably.
for (GameEntity p : defenders) {
if (p instanceof Player && !ComputerUtilCard.canBeBlockedProfitably((Player)p, attacker)) {
return p;
}
if (p instanceof Card && !ComputerUtilCard.canBeBlockedProfitably(((Card)p).getController(), attacker)) {
return p;
}
}
}
return Iterables.getFirst(defenders, null);
}
} }

View File

@@ -452,21 +452,15 @@ public class AttachAi extends SpellAbilityAi {
/** /**
* Attach to player ai preferences. * Attach to player ai preferences.
*
* @param sa * @param sa
* the sa * the sa
* @param mandatory * @param mandatory
* the mandatory * the mandatory
* @param newParam TODO
*
* @return the player * @return the player
*/ */
public static Player attachToPlayerAIPreferences(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) { public static Player attachToPlayerAIPreferences(final Player aiPlayer, final SpellAbility sa, final boolean mandatory, List<Player> targetable) {
List<Player> targetable = new ArrayList<>();
for (final Player player : aiPlayer.getGame().getPlayers()) {
if (sa.canTarget(player)) {
targetable.add(player);
}
}
if ("Curse".equals(sa.getParam("AILogic"))) { if ("Curse".equals(sa.getParam("AILogic"))) {
if (!mandatory) { if (!mandatory) {
targetable.removeAll(aiPlayer.getAllies()); targetable.removeAll(aiPlayer.getAllies());
@@ -1020,7 +1014,13 @@ public class AttachAi extends SpellAbilityAi {
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) { private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
GameObject o; GameObject o;
if (tgt.canTgtPlayer()) { if (tgt.canTgtPlayer()) {
o = attachToPlayerAIPreferences(sa.getActivatingPlayer(), sa, mandatory); List<Player> targetable = new ArrayList<>();
for (final Player player : sa.getHostCard().getGame().getPlayers()) {
if (sa.canTarget(player)) {
targetable.add(player);
}
}
o = attachToPlayerAIPreferences(sa.getActivatingPlayer(), sa, mandatory, targetable);
} else { } else {
o = attachToCardAIPreferences(sa.getActivatingPlayer(), sa, mandatory); o = attachToCardAIPreferences(sa.getActivatingPlayer(), sa, mandatory);
} }
@@ -1459,10 +1459,12 @@ public class AttachAi extends SpellAbilityAi {
*/ */
public static Card attachGeneralAI(final Player ai, final SpellAbility sa, final List<Card> list, final boolean mandatory, public static Card attachGeneralAI(final Player ai, final SpellAbility sa, final List<Card> list, final boolean mandatory,
final Card attachSource, final String logic) { final Card attachSource, final String logic) {
Player prefPlayer = AiAttackController.choosePreferredDefenderPlayer(ai); Player prefPlayer;
if ("Pump".equals(logic) || "Animate".equals(logic) || "Curiosity".equals(logic) || "MoveTgtAura".equals(logic) if ("Pump".equals(logic) || "Animate".equals(logic) || "Curiosity".equals(logic) || "MoveTgtAura".equals(logic)
|| "MoveAllAuras".equals(logic)) { || "MoveAllAuras".equals(logic)) {
prefPlayer = ai; prefPlayer = ai;
} else {
prefPlayer = AiAttackController.choosePreferredDefenderPlayer(ai);
} }
// Some ChangeType cards are beneficial, and PrefPlayer should be // Some ChangeType cards are beneficial, and PrefPlayer should be
// changed to represent that // changed to represent that
@@ -1745,6 +1747,10 @@ public class AttachAi extends SpellAbilityAi {
sa.getTargets().add(tgt); sa.getTargets().add(tgt);
} }
return sa.isTargetNumberValid(); return sa.isTargetNumberValid();
} else if ("Remembered".equals(sa.getParam("Defined")) && sa.getParent() != null
&& sa.getParent().getApi() == ApiType.Token && sa.getParent().hasParam("RememberTokens")) {
// Living Weapon or similar
return true;
} }
return false; return false;
} }
@@ -1761,6 +1767,6 @@ public class AttachAi extends SpellAbilityAi {
@Override @Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) { protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
return attachToPlayerAIPreferences(ai, sa, true); return attachToPlayerAIPreferences(ai, sa, true, (List<Player>)options);
} }
} }

View File

@@ -2,6 +2,8 @@ package forge.ai.ability;
import java.util.*; import java.util.*;
import forge.game.card.*;
import forge.game.keyword.Keyword;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
@@ -27,19 +29,13 @@ import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi; import forge.ai.SpellApiToAi;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil;
import forge.game.card.CounterEnumType;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.cost.CostDiscard; import forge.game.cost.CostDiscard;
@@ -55,6 +51,7 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityMustTarget; import forge.game.staticability.StaticAbilityMustTarget;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.collect.FCollection;
public class ChangeZoneAi extends SpellAbilityAi { public class ChangeZoneAi extends SpellAbilityAi {
/* /*
@@ -428,7 +425,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
} }
return canBouncePermanent(ai, sa, list) != null; return canBouncePermanent(ai, sa, list) != null;
} }
} }
if (ComputerUtil.playImmediately(ai, sa)) { if (ComputerUtil.playImmediately(ai, sa)) {
@@ -1397,6 +1393,57 @@ public class ChangeZoneAi extends SpellAbilityAi {
} }
} }
} }
// Reload Undying and Persist, get rid of -1/-1 counters, get rid of enemy auras if able
Card bestChoice = null;
int bestEval = 0;
for (Card c : aiPermanents) {
if (c.isCreature()) {
boolean hasValuableAttachments = false;
boolean hasOppAttachments = false;
int numNegativeCounters = 0;
int numTotalCounters = 0;
for (Card attached : c.getAttachedCards()) {
if (attached.isAura()) {
if (attached.getController() == c.getController()) {
hasValuableAttachments = true;
} else if (attached.getController().isOpponentOf(c.getController())) {
hasOppAttachments = true;
}
}
}
Map<CounterType, Integer> counters = c.getCounters();
for (CounterType ct : counters.keySet()) {
int amount = counters.get(ct);
if (ComputerUtil.isNegativeCounter(ct, c)) {
numNegativeCounters += amount;
}
numTotalCounters += amount;
}
if (hasValuableAttachments || (ComputerUtilCard.isUselessCreature(ai, c) && !hasOppAttachments)) {
continue;
}
Card considered = null;
if ((c.hasKeyword(Keyword.PERSIST) || c.hasKeyword(Keyword.UNDYING))
&& !ComputerUtilCard.hasActiveUndyingOrPersist(c)) {
considered = c;
} else if (hasOppAttachments || (numTotalCounters > 0 && numNegativeCounters > numTotalCounters / 2)) {
considered = c;
}
if (considered != null) {
int eval = ComputerUtilCard.evaluateCreature(c);
if (eval > bestEval) {
bestEval = eval;
bestChoice = considered;
}
}
}
}
if (bestChoice != null) {
return bestChoice;
}
return null; return null;
} }
@@ -1738,8 +1785,20 @@ public class ChangeZoneAi extends SpellAbilityAi {
*/ */
@Override @Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) { public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
// Called when attaching Aura to player // Called when attaching Aura to player or adding creature to combat
return AttachAi.attachToPlayerAIPreferences(ai, sa, true); if (params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
return AttachAi.attachToPlayerAIPreferences(ai, sa, true, (List<Player>)options);
}
@Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
// should not be reached
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
} }
private boolean doSacAndReturnFromGraveLogic(final Player ai, final SpellAbility sa) { private boolean doSacAndReturnFromGraveLogic(final Player ai, final SpellAbility sa) {

View File

@@ -12,10 +12,12 @@ import forge.ai.AiPlayDecision;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.SpecialCardAi; import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
@@ -32,6 +34,7 @@ import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection; import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.collect.FCollection;
public class CopyPermanentAi extends SpellAbilityAi { public class CopyPermanentAi extends SpellAbilityAi {
@Override @Override
@@ -244,9 +247,21 @@ public class CopyPermanentAi extends SpellAbilityAi {
@Override @Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) { protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay(); final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
Card chosen = ComputerUtilCard.getBestCreatureAI(cards); Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null); return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
} }
@Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
// should not be reached
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
}
} }

View File

@@ -8,10 +8,12 @@ import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.SpecialCardAi; import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
@@ -25,6 +27,7 @@ import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.util.collect.FCollection;
public class DigAi extends SpellAbilityAi { public class DigAi extends SpellAbilityAi {
@@ -180,10 +183,22 @@ public class DigAi extends SpellAbilityAi {
*/ */
@Override @Override
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) { public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) {
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
// an opponent choose a card from // an opponent choose a card from
return Iterables.getFirst(options, null); return Iterables.getFirst(options, null);
} }
@Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
if (params.containsKey("Attacker")) {
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
}
// should not be reached
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
}
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
*/ */

View File

@@ -9,6 +9,7 @@ import forge.ai.AiController;
import forge.ai.AiProps; import forge.ai.AiProps;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana; import forge.ai.ComputerUtilMana;
import forge.ai.PlayerControllerAi; import forge.ai.PlayerControllerAi;
@@ -37,6 +38,7 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.collect.FCollection;
/** /**
* <p> * <p>
@@ -312,15 +314,8 @@ public class TokenAi extends SpellAbilityAi {
*/ */
@Override @Override
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) { protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
Combat combat = ai.getGame().getCombat(); if (params.containsKey("Attacker")) {
// TokenAttacking return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
if (combat != null && sa.hasParam("TokenAttacking")) {
Card attacker = spawnToken(ai, sa);
for (Player p : options) {
if (!ComputerUtilCard.canBeBlockedProfitably(p, attacker)) {
return p;
}
}
} }
return Iterables.getFirst(options, null); return Iterables.getFirst(options, null);
} }
@@ -330,28 +325,11 @@ public class TokenAi extends SpellAbilityAi {
*/ */
@Override @Override
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) { protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
Combat combat = ai.getGame().getCombat(); if (params.containsKey("Attacker")) {
// TokenAttacking return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
if (combat != null && sa.hasParam("TokenAttacking")) {
// 1. If the card that spawned the token was sent at a planeswalker, attack the same planeswalker with the token. Consider improving.
GameEntity def = combat.getDefenderByAttacker(sa.getHostCard());
if (def != null && def instanceof Card) {
if (((Card)def).isPlaneswalker()) {
return def;
}
}
// 2. Otherwise, go through the list of options one by one, choose the first one that can't be blocked profitably.
Card attacker = spawnToken(ai, sa);
for (GameEntity p : options) {
if (p instanceof Player && !ComputerUtilCard.canBeBlockedProfitably((Player)p, attacker)) {
return p;
}
if (p instanceof Card && !ComputerUtilCard.canBeBlockedProfitably(((Card)p).getController(), attacker)) {
return p;
}
}
} }
return Iterables.getFirst(options, null); // should not be reached
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
} }
/** /**

View File

@@ -66,6 +66,10 @@ public final class ImageKeys {
} }
private static final Map<String, File> cachedCards = new HashMap<>(50000); private static final Map<String, File> cachedCards = new HashMap<>(50000);
private static HashSet<String> missingCards = new HashSet<>();
public static void clearMissingCards() {
missingCards.clear();
}
public static File getCachedCardsFile(String key) { public static File getCachedCardsFile(String key) {
return cachedCards.get(key); return cachedCards.get(key);
} }
@@ -101,6 +105,9 @@ public final class ImageKeys {
dir = CACHE_CARD_PICS_DIR; dir = CACHE_CARD_PICS_DIR;
} }
if (missingCards.contains(filename))
return null;
File cachedFile = cachedCards.get(filename); File cachedFile = cachedCards.get(filename);
if (cachedFile != null) { if (cachedFile != null) {
return cachedFile; return cachedFile;
@@ -237,6 +244,8 @@ public final class ImageKeys {
} }
// System.out.println("File not found, no image created: " + key); // System.out.println("File not found, no image created: " + key);
//add missing cards
missingCards.add(filename);
return null; return null;
} }

View File

@@ -354,7 +354,7 @@ public class StaticData {
} }
/** /**
* Determins whether the input String corresponds to an MTG Card Name (in any available card database) * Determines whether the input String corresponds to an MTG Card Name (in any available card database)
* @param cardName Name of the Card to verify (CASE SENSITIVE) * @param cardName Name of the Card to verify (CASE SENSITIVE)
* @return True if a card with the given input string can be found. False otherwise. * @return True if a card with the given input string can be found. False otherwise.
*/ */
@@ -455,7 +455,6 @@ public class StaticData {
* *
* Note: if input card is Foil, and an alternative card art is found, it will be returned foil too! * Note: if input card is Foil, and an alternative card art is found, it will be returned foil too!
* *
* @see StaticData#getAlternativeCardPrint(forge.item.PaperCard, java.util.Date)
* @param card Input Reference Card * @param card Input Reference Card
* @param setReleaseDate reference set release date * @param setReleaseDate reference set release date
* @return Alternative Card Art (from a different edition) of input card, or null if not found. * @return Alternative Card Art (from a different edition) of input card, or null if not found.
@@ -464,7 +463,7 @@ public class StaticData {
boolean isCardArtPreferenceLatestArt = this.cardArtPreferenceIsLatest(); boolean isCardArtPreferenceLatestArt = this.cardArtPreferenceIsLatest();
boolean cardArtPreferenceHasFilter = this.isCoreExpansionOnlyFilterSet(); boolean cardArtPreferenceHasFilter = this.isCoreExpansionOnlyFilterSet();
return this.getAlternativeCardPrint(card, setReleaseDate, isCardArtPreferenceLatestArt, return this.getAlternativeCardPrint(card, setReleaseDate, isCardArtPreferenceLatestArt,
cardArtPreferenceHasFilter); cardArtPreferenceHasFilter, null);
} }
/** /**
@@ -486,25 +485,25 @@ public class StaticData {
* @param setReleaseDate The reference release date used to control the search for alternative card print. * @param setReleaseDate The reference release date used to control the search for alternative card print.
* The chose candidate will be gathered from an edition printed before (upper bound) or * The chose candidate will be gathered from an edition printed before (upper bound) or
* after (lower bound) the reference set release date. * after (lower bound) the reference set release date.
* @param isCardArtPreferenceLatestArt Determines whether or not "Latest Art" Card Art preference should be used * @param isCardArtPreferenceLatestArt Determines whether "Latest Art" Card Art preference should be used
* when looking for an alternative candidate print. * when looking for an alternative candidate print.
* @param cardArtPreferenceHasFilter Determines whether or not the search should only consider * @param cardArtPreferenceHasFilter Determines whether the search should only consider
* Core, Expansions, or Reprints sets when looking for alternative candidates. * Core, Expansions, or Reprints sets when looking for alternative candidates.
* @return an instance of <code>PaperCard</code> that is the selected alternative candidate, or <code>null</code> * @return an instance of <code>PaperCard</code> that is the selected alternative candidate, or <code>null</code>
* if None could be found. * if None could be found.
*/ */
public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate, public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate,
boolean isCardArtPreferenceLatestArt, boolean isCardArtPreferenceLatestArt,
boolean cardArtPreferenceHasFilter) { boolean cardArtPreferenceHasFilter, List<String> allowedSetCodes) {
Date searchReferenceDate = getReferenceDate(setReleaseDate, isCardArtPreferenceLatestArt); Date searchReferenceDate = getReferenceDate(setReleaseDate, isCardArtPreferenceLatestArt);
CardDb.CardArtPreference searchCardArtStrategy = getSearchStrategyForAlternativeCardArt(isCardArtPreferenceLatestArt, CardDb.CardArtPreference searchCardArtStrategy = getSearchStrategyForAlternativeCardArt(isCardArtPreferenceLatestArt,
cardArtPreferenceHasFilter); cardArtPreferenceHasFilter);
return searchAlternativeCardCandidate(card, isCardArtPreferenceLatestArt, searchReferenceDate, return searchAlternativeCardCandidate(card, isCardArtPreferenceLatestArt, searchReferenceDate,
searchCardArtStrategy); searchCardArtStrategy, allowedSetCodes);
} }
/** /**
* This method extends the defatult <code>getAlternativeCardPrint</code> with extra settings to be used for * This method extends the default <code>getAlternativeCardPrint</code> with extra settings to be used for
* alternative card print. * alternative card print.
* *
* <p> * <p>
@@ -531,18 +530,54 @@ public class StaticData {
public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate, boolean isCardArtPreferenceLatestArt, public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate, boolean isCardArtPreferenceLatestArt,
boolean cardArtPreferenceHasFilter, boolean cardArtPreferenceHasFilter,
boolean preferCandidatesFromExpansionSets, boolean preferModernFrame) { boolean preferCandidatesFromExpansionSets, boolean preferModernFrame) {
return getAlternativeCardPrint(card, setReleaseDate, isCardArtPreferenceLatestArt, cardArtPreferenceHasFilter,
preferCandidatesFromExpansionSets, preferModernFrame, null);
}
/**
* This method extends the default <code>getAlternativeCardPrint</code> with extra settings to be used for
* alternative card print.
*
* <p>
* These options for Alternative Card Print make sense as part of the harmonisation/theme-matching process for
* cards in Deck Sections (i.e. CardPool). In fact, the values of the provided flags for alternative print
* for a single card will be determined according to whole card pool (Deck section) the card appears in.
*
* @param card The instance of <code>PaperCard</code> to look for an alternative print
* @param setReleaseDate The reference release date used to control the search for alternative card print.
* The chose candidate will be gathered from an edition printed before (upper bound) or
* after (lower bound) the reference set release date.
* @param isCardArtPreferenceLatestArt Determines whether or not "Latest Art" Card Art preference should be used
* when looking for an alternative candidate print.
* @param cardArtPreferenceHasFilter Determines whether or not the search should only consider
* Core, Expansions, or Reprints sets when looking for alternative candidates.
* @param preferCandidatesFromExpansionSets Whenever the selected Card Art Preference has filter, try to get
* prefer candidates from Expansion Sets over those in Core or Reprint
* Editions (whenever possible)
* e.g. Necropotence from Ice Age rather than 5th Edition (w/ Latest=false)
* @param preferModernFrame If True, Modern Card Frame will be preferred over Old Frames.
* @param allowedSetCodes The list of the allowed set codes to consider when looking for alternative card art
* candidates. If the list is not null and not empty, will be used in combination with the
* <code>isLegal</code> predicate.
* @see CardDb#isLegal(List<String>)
* @return an instance of <code>PaperCard</code> that is the selected alternative candidate, or <code>null</code>
* if None could be found.
*/
public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate, boolean isCardArtPreferenceLatestArt,
boolean cardArtPreferenceHasFilter,
boolean preferCandidatesFromExpansionSets, boolean preferModernFrame,
List<String> allowedSetCodes){
PaperCard altCard = this.getAlternativeCardPrint(card, setReleaseDate, isCardArtPreferenceLatestArt, PaperCard altCard = this.getAlternativeCardPrint(card, setReleaseDate, isCardArtPreferenceLatestArt,
cardArtPreferenceHasFilter); cardArtPreferenceHasFilter, allowedSetCodes);
if (altCard == null) if (altCard == null)
return altCard; return altCard;
// from here on, we're sure we do have a candidate already! // from here on, we're sure we do have a candidate already!
/* Try to refine selection by getting one candidate with frame matching current /* Try to refine selection by getting one candidate with frame matching current
Card Art Preference (that is NOT the lookup strategy!)*/ Card Art Preference (that is NOT the lookup strategy!)*/
PaperCard refinedAltCandidate = this.tryToGetCardPrintWithMatchingFrame(altCard, PaperCard refinedAltCandidate = this.tryToGetCardPrintWithMatchingFrame(altCard, isCardArtPreferenceLatestArt,
isCardArtPreferenceLatestArt, cardArtPreferenceHasFilter,
cardArtPreferenceHasFilter, preferModernFrame, allowedSetCodes);
preferModernFrame);
if (refinedAltCandidate != null) if (refinedAltCandidate != null)
altCard = refinedAltCandidate; altCard = refinedAltCandidate;
@@ -551,7 +586,7 @@ public class StaticData {
NOTE: At this stage, any future selection should be already compliant with previous filter on NOTE: At this stage, any future selection should be already compliant with previous filter on
Card Frame (if applied) given that we'll be moving either UP or DOWN the timeline of Card Edition */ Card Frame (if applied) given that we'll be moving either UP or DOWN the timeline of Card Edition */
refinedAltCandidate = this.tryToGetCardPrintFromExpansionSet(altCard, isCardArtPreferenceLatestArt, refinedAltCandidate = this.tryToGetCardPrintFromExpansionSet(altCard, isCardArtPreferenceLatestArt,
preferModernFrame); preferModernFrame, allowedSetCodes);
if (refinedAltCandidate != null) if (refinedAltCandidate != null)
altCard = refinedAltCandidate; altCard = refinedAltCandidate;
} }
@@ -560,21 +595,29 @@ public class StaticData {
private PaperCard searchAlternativeCardCandidate(PaperCard card, boolean isCardArtPreferenceLatestArt, private PaperCard searchAlternativeCardCandidate(PaperCard card, boolean isCardArtPreferenceLatestArt,
Date searchReferenceDate, Date searchReferenceDate,
CardDb.CardArtPreference searchCardArtStrategy) { CardDb.CardArtPreference searchCardArtStrategy,
List<String> allowedSetCodes) {
// Note: this won't apply to Custom Nor Variant Cards, so won't bother including it! // Note: this won't apply to Custom Nor Variant Cards, so won't bother including it!
CardDb cardDb = this.commonCards; CardDb cardDb = this.commonCards;
String cardName = card.getName(); String cardName = card.getName();
int artIndex = card.getArtIndex(); int artIndex = card.getArtIndex();
PaperCard altCard = null; PaperCard altCard = null;
Predicate<PaperCard> filter = null;
if (allowedSetCodes != null && !allowedSetCodes.isEmpty())
filter = (Predicate<PaperCard>) cardDb.isLegal(allowedSetCodes);
if (isCardArtPreferenceLatestArt) { // RELEASED AFTER REFERENCE DATE if (isCardArtPreferenceLatestArt) { // RELEASED AFTER REFERENCE DATE
altCard = cardDb.getCardFromEditionsReleasedAfter(cardName, searchCardArtStrategy, artIndex, searchReferenceDate); altCard = cardDb.getCardFromEditionsReleasedAfter(cardName, searchCardArtStrategy, artIndex,
searchReferenceDate, filter);
if (altCard == null) // relax artIndex condition if (altCard == null) // relax artIndex condition
altCard = cardDb.getCardFromEditionsReleasedAfter(cardName, searchCardArtStrategy, searchReferenceDate); altCard = cardDb.getCardFromEditionsReleasedAfter(cardName, searchCardArtStrategy,
searchReferenceDate, filter);
} else { // RELEASED BEFORE REFERENCE DATE } else { // RELEASED BEFORE REFERENCE DATE
altCard = cardDb.getCardFromEditionsReleasedBefore(cardName, searchCardArtStrategy, artIndex, searchReferenceDate); altCard = cardDb.getCardFromEditionsReleasedBefore(cardName, searchCardArtStrategy, artIndex,
searchReferenceDate, filter);
if (altCard == null) // relax artIndex constraint if (altCard == null) // relax artIndex constraint
altCard = cardDb.getCardFromEditionsReleasedBefore(cardName, searchCardArtStrategy, searchReferenceDate); altCard = cardDb.getCardFromEditionsReleasedBefore(cardName, searchCardArtStrategy,
searchReferenceDate, filter);
} }
if (altCard == null) if (altCard == null)
return null; return null;
@@ -611,7 +654,8 @@ public class StaticData {
private PaperCard tryToGetCardPrintFromExpansionSet(PaperCard altCard, private PaperCard tryToGetCardPrintFromExpansionSet(PaperCard altCard,
boolean isCardArtPreferenceLatestArt, boolean isCardArtPreferenceLatestArt,
boolean preferModernFrame) { boolean preferModernFrame,
List<String> allowedSetCodes) {
CardEdition altCardEdition = editions.get(altCard.getEdition()); CardEdition altCardEdition = editions.get(altCard.getEdition());
if (altCardEdition.getType() == CardEdition.Type.EXPANSION) if (altCardEdition.getType() == CardEdition.Type.EXPANSION)
return null; // Nothing to do here! return null; // Nothing to do here!
@@ -624,7 +668,7 @@ public class StaticData {
while (altCandidate != null) { while (altCandidate != null) {
Date referenceDate = editions.get(altCandidate.getEdition()).getDate(); Date referenceDate = editions.get(altCandidate.getEdition()).getDate();
altCandidate = this.searchAlternativeCardCandidate(altCandidate, preferModernFrame, altCandidate = this.searchAlternativeCardCandidate(altCandidate, preferModernFrame,
referenceDate, searchStrategy); referenceDate, searchStrategy, allowedSetCodes);
if (altCandidate != null) { if (altCandidate != null) {
CardEdition altCandidateEdition = editions.get(altCandidate.getEdition()); CardEdition altCandidateEdition = editions.get(altCandidate.getEdition());
if (altCandidateEdition.getType() == CardEdition.Type.EXPANSION) if (altCandidateEdition.getType() == CardEdition.Type.EXPANSION)
@@ -638,7 +682,7 @@ public class StaticData {
private PaperCard tryToGetCardPrintWithMatchingFrame(PaperCard altCard, private PaperCard tryToGetCardPrintWithMatchingFrame(PaperCard altCard,
boolean isCardArtPreferenceLatestArt, boolean isCardArtPreferenceLatestArt,
boolean cardArtHasFilter, boolean cardArtHasFilter,
boolean preferModernFrame) { boolean preferModernFrame, List<String> allowedSetCodes) {
CardEdition altCardEdition = editions.get(altCard.getEdition()); CardEdition altCardEdition = editions.get(altCard.getEdition());
boolean frameIsCompliantAlready = (altCardEdition.isModern() == preferModernFrame); boolean frameIsCompliantAlready = (altCardEdition.isModern() == preferModernFrame);
if (frameIsCompliantAlready) if (frameIsCompliantAlready)
@@ -650,7 +694,7 @@ public class StaticData {
while (altCandidate != null) { while (altCandidate != null) {
Date referenceDate = editions.get(altCandidate.getEdition()).getDate(); Date referenceDate = editions.get(altCandidate.getEdition()).getDate();
altCandidate = this.searchAlternativeCardCandidate(altCandidate, preferModernFrame, altCandidate = this.searchAlternativeCardCandidate(altCandidate, preferModernFrame,
referenceDate, searchStrategy); referenceDate, searchStrategy, allowedSetCodes);
if (altCandidate != null) { if (altCandidate != null) {
CardEdition altCandidateEdition = editions.get(altCandidate.getEdition()); CardEdition altCandidateEdition = editions.get(altCandidate.getEdition());
if (altCandidateEdition.isModern() == preferModernFrame) if (altCandidateEdition.isModern() == preferModernFrame)

View File

@@ -266,7 +266,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
} }
deferredSections = null; // set to null, just in case! deferredSections = null; // set to null, just in case!
if (includeCardsFromUnspecifiedSet && smartCardArtSelection) if (includeCardsFromUnspecifiedSet && smartCardArtSelection)
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition, true); optimiseCardArtSelectionInDeckSections(cardsWithNoEdition);
} }
private void validateDeferredSections() { private void validateDeferredSections() {
@@ -351,15 +351,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
return String.format("%d %s", amount, originalRequestCandidate); return String.format("%d %s", amount, originalRequestCandidate);
return String.format("%d %s", amount, poolCardRequest); return String.format("%d %s", amount, poolCardRequest);
} }
public void optimizeMainCardArt() {
Map<DeckSection, ArrayList<String>> cardsWithNoEdition = new EnumMap<>(DeckSection.class);
List<String> mainCards = new ArrayList<>();
for (Entry<PaperCard, Integer> e: getMain())
mainCards.add(e.getKey().getName());
cardsWithNoEdition.put(DeckSection.Main, getAllCardNamesWithNoSpecifiedEdition(mainCards));
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition, false);
}
private ArrayList<String> getAllCardNamesWithNoSpecifiedEdition(List<String> cardsInSection) { private ArrayList<String> getAllCardNamesWithNoSpecifiedEdition(List<String> cardsInSection) {
ArrayList<String> cardNamesWithNoEdition = new ArrayList<>(); ArrayList<String> cardNamesWithNoEdition = new ArrayList<>();
List<Pair<String, Integer>> cardRequests = CardPool.processCardList(cardsInSection); List<Pair<String, Integer>> cardRequests = CardPool.processCardList(cardsInSection);
@@ -372,7 +364,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
return cardNamesWithNoEdition; return cardNamesWithNoEdition;
} }
private void optimiseCardArtSelectionInDeckSections(Map<DeckSection, ArrayList<String>> cardsWithNoEdition, boolean multiArtPrint) { private void optimiseCardArtSelectionInDeckSections(Map<DeckSection, ArrayList<String>> cardsWithNoEdition) {
StaticData data = StaticData.instance(); StaticData data = StaticData.instance();
// Get current Card Art Preference Settings // Get current Card Art Preference Settings
boolean isCardArtPreferenceLatestArt = data.cardArtPreferenceIsLatest(); boolean isCardArtPreferenceLatestArt = data.cardArtPreferenceIsLatest();
@@ -405,13 +397,13 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
int totalToAddToPool = cp.getValue(); int totalToAddToPool = cp.getValue();
// A. Skip cards not requiring any update, because they add the edition specified! // A. Skip cards not requiring any update, because they add the edition specified!
if (!cardNamesWithNoEditionInSection.contains(card.getName())) { if (!cardNamesWithNoEditionInSection.contains(card.getName())) {
addCardToPool(newPool, card, totalToAddToPool, card.isFoil(), multiArtPrint); addCardToPool(newPool, card, totalToAddToPool, card.isFoil());
continue; continue;
} }
// B. Determine if current card requires update // B. Determine if current card requires update
boolean cardArtNeedsOptimisation = this.isCardArtUpdateRequired(card, releaseDatePivotEdition); boolean cardArtNeedsOptimisation = this.isCardArtUpdateRequired(card, releaseDatePivotEdition);
if (!cardArtNeedsOptimisation) { if (!cardArtNeedsOptimisation) {
addCardToPool(newPool, card, totalToAddToPool, card.isFoil(), multiArtPrint); addCardToPool(newPool, card, totalToAddToPool, card.isFoil());
continue; continue;
} }
PaperCard alternativeCardPrint = data.getAlternativeCardPrint(card, releaseDatePivotEdition, PaperCard alternativeCardPrint = data.getAlternativeCardPrint(card, releaseDatePivotEdition,
@@ -420,22 +412,20 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
isExpansionTheMajorityInThePool, isExpansionTheMajorityInThePool,
isPoolModernFramed); isPoolModernFramed);
if (alternativeCardPrint == null) // no alternative found, add original card in Pool if (alternativeCardPrint == null) // no alternative found, add original card in Pool
addCardToPool(newPool, card, totalToAddToPool, card.isFoil(), multiArtPrint); addCardToPool(newPool, card, totalToAddToPool, card.isFoil());
else else
addCardToPool(newPool, alternativeCardPrint, totalToAddToPool, card.isFoil(), multiArtPrint); addCardToPool(newPool, alternativeCardPrint, totalToAddToPool, card.isFoil());
} }
parts.put(deckSection, newPool); parts.put(deckSection, newPool);
} }
} }
private void addCardToPool(CardPool pool, PaperCard card, int totalToAdd, boolean isFoil, boolean multiArtPrint) { private void addCardToPool(CardPool pool, PaperCard card, int totalToAdd, boolean isFoil) {
StaticData data = StaticData.instance(); StaticData data = StaticData.instance();
if (card.getArtIndex() != IPaperCard.NO_ART_INDEX && card.getArtIndex() != IPaperCard.DEFAULT_ART_INDEX) if (card.getArtIndex() != IPaperCard.NO_ART_INDEX && card.getArtIndex() != IPaperCard.DEFAULT_ART_INDEX)
pool.add(isFoil ? card.getFoiled() : card, totalToAdd); // art index requested, keep that way! pool.add(isFoil ? card.getFoiled() : card, totalToAdd); // art index requested, keep that way!
else { else {
int artCount = 1; int artCount = data.getCardArtCount(card);
if (multiArtPrint)
artCount = data.getCardArtCount(card);
if (artCount > 1) if (artCount > 1)
addAlternativeCardPrintInPoolWithMultipleArt(card, pool, totalToAdd, artCount); addAlternativeCardPrintInPoolWithMultipleArt(card, pool, totalToAdd, artCount);
else else

View File

@@ -83,23 +83,29 @@ public class DeckRecognizer {
// only used for card tokens // only used for card tokens
private PaperCard card = null; private PaperCard card = null;
private DeckSection tokenSection = null; private DeckSection tokenSection = null;
// Flag used to mark whether original card request had any specified set code
// This will be used to mark tokens that could be further processed by
// card art optimisation (if enabled)
private boolean cardRequestHasSetCode = true;
public static Token LegalCard(final PaperCard card, final int count, public static Token LegalCard(final PaperCard card, final int count,
final DeckSection section) { final DeckSection section, final boolean cardRequestHasSetCode) {
return new Token(TokenType.LEGAL_CARD, count, card, section); return new Token(TokenType.LEGAL_CARD, count, card, section, cardRequestHasSetCode);
} }
public static Token LimitedCard(final PaperCard card, final int count, public static Token LimitedCard(final PaperCard card, final int count,
final DeckSection section, final LimitedCardType limitedType){ final DeckSection section, final LimitedCardType limitedType,
return new Token(TokenType.LIMITED_CARD, count, card, section, limitedType); final boolean cardRequestHasSetCode){
return new Token(TokenType.LIMITED_CARD, count, card, section, limitedType, cardRequestHasSetCode);
} }
public static Token NotAllowedCard(final PaperCard card, final int count) { public static Token NotAllowedCard(final PaperCard card, final int count, final boolean cardRequestHasSetCode) {
return new Token(TokenType.CARD_FROM_NOT_ALLOWED_SET, count, card); return new Token(TokenType.CARD_FROM_NOT_ALLOWED_SET, count, card, cardRequestHasSetCode);
} }
public static Token CardInInvalidSet(final PaperCard card, final int count) { public static Token CardInInvalidSet(final PaperCard card, final int count, final boolean cardRequestHasSetCode) {
return new Token(TokenType.CARD_FROM_INVALID_SET, count, card); return new Token(TokenType.CARD_FROM_INVALID_SET, count, card, cardRequestHasSetCode);
} }
// WARNING MESSAGES // WARNING MESSAGES
@@ -154,26 +160,27 @@ public class DeckRecognizer {
return new Token(TokenType.DECK_SECTION_NAME, matchedSection.name()); return new Token(TokenType.DECK_SECTION_NAME, matchedSection.name());
} }
private Token(final TokenType type1, final int count, final PaperCard tokenCard) { private Token(final TokenType type1, final int count, final PaperCard tokenCard, boolean cardRequestHasSetCode) {
this.number = count; this.number = count;
this.type = type1; this.type = type1;
this.text = String.format("%s [%s] #%s", this.text = "";
tokenCard.getName(), tokenCard.getEdition(), tokenCard.getCollectorNumber());
this.card = tokenCard; this.card = tokenCard;
this.tokenSection = null; this.tokenSection = null;
this.limitedCardType = null; this.limitedCardType = null;
this.cardRequestHasSetCode = cardRequestHasSetCode;
} }
private Token(final TokenType type1, final int count, final PaperCard tokenCard, private Token(final TokenType type1, final int count, final PaperCard tokenCard,
final DeckSection section) { final DeckSection section, boolean cardRequestHasSetCode) {
this(type1, count, tokenCard); this(type1, count, tokenCard, cardRequestHasSetCode);
this.tokenSection = section; this.tokenSection = section;
this.limitedCardType = null; this.limitedCardType = null;
} }
private Token(final TokenType type1, final int count, final PaperCard tokenCard, private Token(final TokenType type1, final int count, final PaperCard tokenCard,
final DeckSection section, final LimitedCardType limitedCardType1) { final DeckSection section, final LimitedCardType limitedCardType1,
this(type1, count, tokenCard); boolean cardRequestHasSetCode) {
this(type1, count, tokenCard, cardRequestHasSetCode);
this.tokenSection = section; this.tokenSection = section;
this.limitedCardType = limitedCardType1; this.limitedCardType = limitedCardType1;
} }
@@ -189,6 +196,9 @@ public class DeckRecognizer {
} }
public final String getText() { public final String getText() {
if (this.isCardToken())
return String.format("%s [%s] #%s",
this.card.getName(), this.card.getEdition(), this.card.getCollectorNumber());
return this.text; return this.text;
} }
@@ -204,16 +214,26 @@ public class DeckRecognizer {
return this.number; return this.number;
} }
public final boolean cardRequestHasNoCode() {
return !(this.cardRequestHasSetCode);
}
public final DeckSection getTokenSection() { return this.tokenSection; } public final DeckSection getTokenSection() { return this.tokenSection; }
public void resetTokenSection(DeckSection referenceDeckSection) { public void resetTokenSection(DeckSection referenceDeckSection) {
this.tokenSection = referenceDeckSection != null ? referenceDeckSection : DeckSection.Main; this.tokenSection = referenceDeckSection != null ? referenceDeckSection : DeckSection.Main;
} }
public void replaceTokenCard(PaperCard replacementCard){
if (!this.isCardToken())
return;
this.card = replacementCard;
}
public final LimitedCardType getLimitedCardType() { return this.limitedCardType; } public final LimitedCardType getLimitedCardType() { return this.limitedCardType; }
/** /**
* Filters all tokens types that have a PaperCard instance set (not null) * Filters all token types that have a PaperCard instance set (not null)
* @return true for tokens of type: * @return true for tokens of type:
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET. * LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET.
* False otherwise. * False otherwise.
@@ -236,6 +256,15 @@ public class DeckRecognizer {
this.type == TokenType.DECK_NAME); this.type == TokenType.DECK_NAME);
} }
/**
* Filters all tokens for deck that are also Card Token..
* @return true for tokens of type: LEGAL_CARD, LIMITED_CARD.
* False otherwise.
*/
public boolean isCardTokenForDeck() {
return (this.type == TokenType.LEGAL_CARD || this.type == TokenType.LIMITED_CARD);
}
/** /**
* Determines whether current token is a placeholder token for card categories, * Determines whether current token is a placeholder token for card categories,
* only used for Decklist formatting. * only used for Decklist formatting.
@@ -635,7 +664,8 @@ public class DeckRecognizer {
PaperCard pc = data.getCardFromSet(cardName, edition, collectorNumber, artIndex, isFoil); PaperCard pc = data.getCardFromSet(cardName, edition, collectorNumber, artIndex, isFoil);
if (pc != null) if (pc != null)
// ok so the card has been found - let's see if there's any restriction on the set // ok so the card has been found - let's see if there's any restriction on the set
return checkAndSetCardToken(pc, edition, cardCount, deckSecFromCardLine, currentDeckSection); return checkAndSetCardToken(pc, edition, cardCount, deckSecFromCardLine,
currentDeckSection, true);
// UNKNOWN card as in the Counterspell|FEM case // UNKNOWN card as in the Counterspell|FEM case
return Token.UnknownCard(cardName, setCode, cardCount); return Token.UnknownCard(cardName, setCode, cardCount);
} }
@@ -654,7 +684,8 @@ public class DeckRecognizer {
if (pc != null) { if (pc != null) {
CardEdition edition = StaticData.instance().getCardEdition(pc.getEdition()); CardEdition edition = StaticData.instance().getCardEdition(pc.getEdition());
return checkAndSetCardToken(pc, edition, cardCount, deckSecFromCardLine, currentDeckSection); return checkAndSetCardToken(pc, edition, cardCount, deckSecFromCardLine,
currentDeckSection, false);
} }
} }
return unknownCardToken; // either null or unknown card return unknownCardToken; // either null or unknown card
@@ -677,25 +708,26 @@ public class DeckRecognizer {
return null; return null;
} }
private Token checkAndSetCardToken(PaperCard pc, CardEdition edition, int cardCount, private Token checkAndSetCardToken(final PaperCard pc, final CardEdition edition, final int cardCount,
String deckSecFromCardLine, DeckSection referenceSection) { final String deckSecFromCardLine, final DeckSection referenceSection,
final boolean cardRequestHasSetCode) {
// Note: Always Check Allowed Set First to avoid accidentally importing invalid cards // Note: Always Check Allowed Set First to avoid accidentally importing invalid cards
// e.g. Banned Cards from not-allowed sets! // e.g. Banned Cards from not-allowed sets!
if (IsIllegalInFormat(edition.getCode())) if (IsIllegalInFormat(edition.getCode()))
// Mark as illegal card // Mark as illegal card
return Token.NotAllowedCard(pc, cardCount); return Token.NotAllowedCard(pc, cardCount, cardRequestHasSetCode);
if (isNotCompliantWithReleaseDateRestrictions(edition)) if (isNotCompliantWithReleaseDateRestrictions(edition))
return Token.CardInInvalidSet(pc, cardCount); return Token.CardInInvalidSet(pc, cardCount, cardRequestHasSetCode);
DeckSection tokenSection = getTokenSection(deckSecFromCardLine, referenceSection, pc); DeckSection tokenSection = getTokenSection(deckSecFromCardLine, referenceSection, pc);
if (isBannedInFormat(pc)) if (isBannedInFormat(pc))
return Token.LimitedCard(pc, cardCount, tokenSection, LimitedCardType.BANNED); return Token.LimitedCard(pc, cardCount, tokenSection, LimitedCardType.BANNED, cardRequestHasSetCode);
if (isRestrictedInFormat(pc, cardCount)) if (isRestrictedInFormat(pc, cardCount))
return Token.LimitedCard(pc, cardCount, tokenSection, LimitedCardType.RESTRICTED); return Token.LimitedCard(pc, cardCount, tokenSection, LimitedCardType.RESTRICTED, cardRequestHasSetCode);
return Token.LegalCard(pc, cardCount, tokenSection); return Token.LegalCard(pc, cardCount, tokenSection, cardRequestHasSetCode);
} }
// This would save tons of time in parsing Input + would also allow to return UnsupportedCardTokens beforehand // This would save tons of time in parsing Input + would also allow to return UnsupportedCardTokens beforehand

View File

@@ -412,7 +412,7 @@ public class GameAction {
CardCollection cards = new CardCollection(c.getMergedCards()); CardCollection cards = new CardCollection(c.getMergedCards());
// replace top card with copied card for correct name for human to choose. // replace top card with copied card for correct name for human to choose.
cards.set(cards.indexOf(c), copied); cards.set(cards.indexOf(c), copied);
// 721.3b // 723.3b
if (cause != null && zoneTo.getZoneType() == ZoneType.Exile) { if (cause != null && zoneTo.getZoneType() == ZoneType.Exile) {
cards = (CardCollection) cause.getHostCard().getController().getController().orderMoveToZoneList(cards, zoneTo.getZoneType(), cause); cards = (CardCollection) cause.getHostCard().getController().getController().orderMoveToZoneList(cards, zoneTo.getZoneType(), cause);
} else { } else {

View File

@@ -179,7 +179,7 @@ public final class GameActionUtil {
final Cost disturbCost = new Cost(k[1], true); final Cost disturbCost = new Cost(k[1], true);
SpellAbility newSA; SpellAbility newSA;
if (source.getAlternateState().getType().isEnchantment()) { if (source.getAlternateState().getType().hasSubtype("Aura")) {
newSA = source.getAlternateState().getFirstAbility().copyWithManaCostReplaced(activator, newSA = source.getAlternateState().getFirstAbility().copyWithManaCostReplaced(activator,
disturbCost); disturbCost);
} else { } else {

View File

@@ -575,14 +575,14 @@ public abstract class SpellAbilityEffect {
FCollection<GameEntity> defs = null; FCollection<GameEntity> defs = null;
// important to update defenders here, maybe some PW got removed // important to update defenders here, maybe some PW got removed
combat.initConstraints(); combat.initConstraints();
if ("True".equalsIgnoreCase(attacking)) { if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
defs = (FCollection<GameEntity>) combat.getDefenders();
} else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
PlayerCollection defendingPlayers = AbilityUtils.getDefinedPlayers(host, attacking, sa); PlayerCollection defendingPlayers = AbilityUtils.getDefinedPlayers(host, attacking, sa);
defs = new FCollection<>(); defs = new FCollection<>();
for (Player p : defendingPlayers) { for (Player p : defendingPlayers) {
defs.addAll(game.getCombat().getDefendersControlledBy(p)); defs.addAll(game.getCombat().getDefendersControlledBy(p));
} }
} else if ("True".equalsIgnoreCase(attacking)) {
defs = (FCollection<GameEntity>) combat.getDefenders();
} else { } else {
defs = AbilityUtils.getDefinedEntities(host, attacking, sa); defs = AbilityUtils.getDefinedEntities(host, attacking, sa);
} }
@@ -593,8 +593,7 @@ public abstract class SpellAbilityEffect {
Player chooser; Player chooser;
if (sa.hasParam("Chooser")) { if (sa.hasParam("Chooser")) {
chooser = Iterables.getFirst(AbilityUtils.getDefinedPlayers(host, sa.getParam("Chooser"), sa), null); chooser = Iterables.getFirst(AbilityUtils.getDefinedPlayers(host, sa.getParam("Chooser"), sa), null);
} } else {
else {
chooser = controller; chooser = controller;
} }
defender = chooser.getController().chooseSingleEntityForEffect(defs, sa, defender = chooser.getController().chooseSingleEntityForEffect(defs, sa,

View File

@@ -137,15 +137,17 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
final int libraryPos = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : 0; final int libraryPos = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : 0;
if ((destination == ZoneType.Library || destination == ZoneType.PlanarDeck) if (!random) {
&& !sa.hasParam("Shuffle") && cards.size() >= 2 && !random) { if ((destination == ZoneType.Library || destination == ZoneType.PlanarDeck)
Player p = AbilityUtils.getDefinedPlayers(source, sa.getParamOrDefault("DefinedPlayer", "You"), sa).get(0); && !sa.hasParam("Shuffle") && cards.size() >= 2) {
cards = (CardCollection) p.getController().orderMoveToZoneList(cards, destination, sa); Player p = AbilityUtils.getDefinedPlayers(source, sa.getParamOrDefault("DefinedPlayer", "You"), sa).get(0);
//the last card in this list will be the closest to the top, but we want the first card to be closest. cards = (CardCollection) p.getController().orderMoveToZoneList(cards, destination, sa);
//so reverse it here before moving them to the library. //the last card in this list will be the closest to the top, but we want the first card to be closest.
java.util.Collections.reverse(cards); //so reverse it here before moving them to the library.
} else { java.util.Collections.reverse(cards);
cards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, cards, destination, sa); } else {
cards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, cards, destination, sa);
}
} }
if (destination.equals(ZoneType.Library) && random) { if (destination.equals(ZoneType.Library) && random) {

View File

@@ -164,7 +164,6 @@ public class DigUntilEffect extends SpellAbilityEffect {
game.getAction().reveal(revealed, p, false); game.getAction().reveal(revealed, p, false);
} }
if (foundDest != null) { if (foundDest != null) {
// Allow ordering of found cards // Allow ordering of found cards
if ((foundDest.isKnown()) && found.size() >= 2 && !foundDest.equals(ZoneType.Exile)) { if ((foundDest.isKnown()) && found.size() >= 2 && !foundDest.equals(ZoneType.Exile)) {

View File

@@ -52,7 +52,7 @@ public class DrawEffect extends SpellAbilityEffect {
int actualNum = numCards; int actualNum = numCards;
if (upto) { if (upto) {
actualNum = p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCardDoYouWantDraw"),0, numCards); actualNum = p.getController().chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCardDoYouWantDraw"), 0, numCards);
} }
final CardCollectionView drawn = p.drawCards(actualNum, sa); final CardCollectionView drawn = p.drawCards(actualNum, sa);

View File

@@ -2628,8 +2628,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
sbCost.append(cost.toSimpleString()); sbCost.append(cost.toSimpleString());
sbAfter.append(sbCost).append(" (").append(inst.getReminderText()).append(")"); sbAfter.append(sbCost).append(" (").append(inst.getReminderText()).append(")");
sbAfter.append("\r\n"); sbAfter.append("\r\n");
} else if (keyword.equals("CARDNAME can't be countered.") || } else if (keyword.equals("CARDNAME can't be countered.") || keyword.equals("This spell can't be " +
keyword.equals("Remove CARDNAME from your deck before playing if you're not playing for ante.")) { "countered.") || keyword.equals("Remove CARDNAME from your deck before playing if you're not " +
"playing for ante.")) {
sbBefore.append(keyword); sbBefore.append(keyword);
sbBefore.append("\r\n"); sbBefore.append("\r\n");
} else if (keyword.startsWith("Haunt")) { } else if (keyword.startsWith("Haunt")) {

View File

@@ -256,7 +256,8 @@ public class CardFactoryUtil {
* @return a boolean. * @return a boolean.
*/ */
public static boolean isCounterable(final Card c) { public static boolean isCounterable(final Card c) {
return !c.hasKeyword("CARDNAME can't be countered.") && c.getCanCounter(); return !(c.hasKeyword("CARDNAME can't be countered.") || c.hasKeyword("This spell can't be countered."))
&& c.getCanCounter();
} }
/** /**

View File

@@ -197,6 +197,15 @@ public final class CardPredicates {
}; };
} }
public static Predicate<Card> sharesLandTypeWith(final Card card) {
return new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.sharesLandTypeWith(card);
}
};
}
public static final Predicate<Card> possibleBlockers(final Card attacker) { public static final Predicate<Card> possibleBlockers(final Card attacker) {
return new Predicate<Card>() { return new Predicate<Card>() {
@Override @Override

View File

@@ -25,6 +25,7 @@ import forge.game.zone.ZoneType;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.util.Expressions; import forge.util.Expressions;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -825,6 +826,11 @@ public class CardProperty {
} }
} }
} }
} else if (property.startsWith("sharesLandTypeWith")) {
final String restriction = property.split("sharesLandTypeWith ")[1];
if (!Iterables.any(AbilityUtils.getDefinedCards(source, restriction, spellAbility), CardPredicates.sharesLandTypeWith(card))) {
return false;
}
} else if (property.equals("sharesPermanentTypeWith")) { } else if (property.equals("sharesPermanentTypeWith")) {
if (!card.sharesPermanentTypeWith(source)) { if (!card.sharesPermanentTypeWith(source)) {
return false; return false;
@@ -1462,6 +1468,14 @@ public class CardProperty {
return false; return false;
} }
} }
if (property.startsWith("attacking ")) { // generic "attacking [DefinedGameEntity]"
FCollection<GameEntity> defined = AbilityUtils.getDefinedEntities(source, property.split(" ")[1],
spellAbility);
final GameEntity defender = combat.getDefenderByAttacker(card);
if (!defined.contains(defender)) {
return false;
}
}
} else if (property.startsWith("notattacking")) { } else if (property.startsWith("notattacking")) {
return null == combat || !combat.isAttacking(card); return null == combat || !combat.isAttacking(card);
} else if (property.equals("attackedThisCombat")) { } else if (property.equals("attackedThisCombat")) {

View File

@@ -179,6 +179,8 @@ public enum CounterEnumType {
JAVELIN("JAVLN", 180, 206, 172), JAVELIN("JAVLN", 180, 206, 172),
JUDGMENT("JUDGM", 249, 220, 52),
KI("KI", 190, 189, 255), KI("KI", 190, 189, 255),
KNOWLEDGE("KNOWL", 0, 115, 255), KNOWLEDGE("KNOWL", 0, 115, 255),

View File

@@ -832,12 +832,7 @@ public class Cost implements Serializable {
sb.append(Cost.NUM_NAMES[i]); sb.append(Cost.NUM_NAMES[i]);
} }
sb.append(" "); sb.append(" ").append(type);
char firstChar = type.charAt(0);
if (Character.isUpperCase(firstChar)) { //fix case of type before appending
type = Character.toLowerCase(firstChar) + type.substring(1);
}
sb.append(type);
if (1 != i) { if (1 != i) {
sb.append("s"); sb.append("s");
} }

View File

@@ -199,7 +199,9 @@ public class CostDiscard extends CostPartWithList {
// discard itself for cycling cost // discard itself for cycling cost
runParams.put(AbilityKey.Cycling, true); runParams.put(AbilityKey.Cycling, true);
} }
return targetCard.getController().discard(targetCard, null, null, runParams); // if this is caused by 118.12 it's also an effect
SpellAbility cause = targetCard.getGame().getStack().isResolving(ability.getHostCard()) ? ability : null;
return targetCard.getController().discard(targetCard, cause, null, runParams);
} }
/* (non-Javadoc) /* (non-Javadoc)

View File

@@ -132,7 +132,6 @@ public class CostReturn extends CostPartWithList {
return "ReturnedCards"; return "ReturnedCards";
} }
public <T> T accept(ICostVisitor<T> visitor) { public <T> T accept(ICostVisitor<T> visitor) {
return visitor.visit(this); return visitor.visit(this);
} }

View File

@@ -1774,6 +1774,10 @@ public class Player extends GameEntity implements Comparable<Player> {
if (land.isFaceDown()) { if (land.isFaceDown()) {
land.turnFaceUp(null); land.turnFaceUp(null);
} }
Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(land);
runParams.put(AbilityKey.Origin, land.getZone().getZoneType().name());
game.copyLastState(); game.copyLastState();
final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, cause); final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, cause);
game.updateLastStateForCard(c); game.updateLastStateForCard(c);
@@ -1782,7 +1786,6 @@ public class Player extends GameEntity implements Comparable<Player> {
game.fireEvent(new GameEventLandPlayed(this, land)); game.fireEvent(new GameEventLandPlayed(this, land));
// Run triggers // Run triggers
Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(land);
runParams.put(AbilityKey.SpellAbility, cause); runParams.put(AbilityKey.SpellAbility, cause);
game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false);
game.getStack().unfreezeStack(); game.getStack().unfreezeStack();

View File

@@ -294,7 +294,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
} }
if (this.getShareAllColors() != null) { if (this.getShareAllColors() != null) {
List<Card> tgts = AbilityUtils.getDefinedCards(sa.getHostCard(), this.getShareAllColors(), sa); List<Card> tgts = AbilityUtils.getDefinedCards(host, this.getShareAllColors(), sa);
Card first = Iterables.getFirst(tgts, null); Card first = Iterables.getFirst(tgts, null);
if (first == null) { if (first == null) {
return false; return false;
@@ -324,11 +324,11 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
return false; return false;
} }
if ((this.getActivationLimit() != -1) && (sa.getActivationsThisTurn() >= this.getActivationLimit())) { if (this.getActivationLimit() != -1 && sa.getActivationsThisTurn() >= this.getActivationLimit()) {
return false; return false;
} }
if ((this.getGameActivationLimit() != -1) && (sa.getActivationsThisGame() >= this.getGameActivationLimit())) { if (this.getGameActivationLimit() != -1 && sa.getActivationsThisGame() >= this.getGameActivationLimit()) {
return false; return false;
} }
@@ -352,7 +352,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
} }
if (this.getColorToCheck() != null) { if (this.getColorToCheck() != null) {
if (!sa.getHostCard().hasChosenColor(this.getColorToCheck())) { if (!host.hasChosenColor(this.getColorToCheck())) {
return false; return false;
} }
} }
@@ -365,7 +365,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
list = new FCollection<GameObject>(game.getCardsIn(getPresentZone())); list = new FCollection<GameObject>(game.getCardsIn(getPresentZone()));
} }
final int left = Iterables.size(Iterables.filter(list, GameObjectPredicates.restriction(getIsPresent().split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa))); final int left = Iterables.size(Iterables.filter(list, GameObjectPredicates.restriction(getIsPresent().split(","), sa.getActivatingPlayer(), host, sa)));
final String rightString = this.getPresentCompare().substring(2); final String rightString = this.getPresentCompare().substring(2);
int right = AbilityUtils.calculateAmount(host, rightString, sa); int right = AbilityUtils.calculateAmount(host, rightString, sa);
@@ -378,9 +378,9 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
if (this.getPlayerContains() != null) { if (this.getPlayerContains() != null) {
List<Player> list = new ArrayList<>(); List<Player> list = new ArrayList<>();
if (this.getPlayerDefined() != null) { if (this.getPlayerDefined() != null) {
list.addAll(AbilityUtils.getDefinedPlayers(sa.getHostCard(), this.getPlayerDefined(), sa)); list.addAll(AbilityUtils.getDefinedPlayers(host, this.getPlayerDefined(), sa));
} }
List<Player> contains = AbilityUtils.getDefinedPlayers(sa.getHostCard(), this.getPlayerContains(), sa); List<Player> contains = AbilityUtils.getDefinedPlayers(host, this.getPlayerContains(), sa);
if (contains.isEmpty() || !list.containsAll(contains)) { if (contains.isEmpty() || !list.containsAll(contains)) {
return false; return false;
} }
@@ -417,7 +417,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
boolean result = false; boolean result = false;
for (final GameObject o : matchTgt.getFirstTargetedSpell().getTargets()) { for (final GameObject o : matchTgt.getFirstTargetedSpell().getTargets()) {
if (o.isValid(this.getTargetValidTargeting().split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa)) { if (o.isValid(this.getTargetValidTargeting().split(","), sa.getActivatingPlayer(), host, sa)) {
result = true; result = true;
break; break;
} }
@@ -448,7 +448,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
} }
if (StringUtils.isNotEmpty(getManaSpent())) { if (StringUtils.isNotEmpty(getManaSpent())) {
SpellAbility castSa = sa.getHostCard().getCastSA(); SpellAbility castSa = host.getCastSA();
if (castSa == null) { if (castSa == null) {
return false; return false;
} }
@@ -457,7 +457,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
} }
} }
if (StringUtils.isNotEmpty(getManaNotSpent())) { if (StringUtils.isNotEmpty(getManaNotSpent())) {
SpellAbility castSa = sa.getHostCard().getCastSA(); SpellAbility castSa = host.getCastSA();
if (castSa != null && castSa.getPayingColors().hasAllColors(ColorSet.fromNames(getManaNotSpent().split(" ")).getColor())) { if (castSa != null && castSa.getPayingColors().hasAllColors(ColorSet.fromNames(getManaNotSpent().split(" ")).getColor())) {
return false; return false;
} }

View File

@@ -19,6 +19,8 @@ package forge.game.trigger;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.ArrayUtils;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -67,6 +69,19 @@ public class TriggerLandPlayed extends Trigger {
* @param runParams*/ * @param runParams*/
@Override @Override
public final boolean performTest(final Map<AbilityKey, Object> runParams) { public final boolean performTest(final Map<AbilityKey, Object> runParams) {
if (hasParam("Origin")) {
if (!getParam("Origin").equals("Any")) {
if (getParam("Origin") == null) {
return false;
}
if (!ArrayUtils.contains(
getParam("Origin").split(","), runParams.get(AbilityKey.Origin)
)) {
return false;
}
}
}
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Card))) { if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Card))) {
return false; return false;
} }

View File

@@ -290,6 +290,7 @@ public class GuiDesktop implements IGuiBase {
@Override @Override
public void clearImageCache() { public void clearImageCache() {
ImageCache.clear(); ImageCache.clear();
ImageKeys.clearMissingCards();
} }
@Override @Override

View File

@@ -96,6 +96,7 @@ public class ImageCache {
public static void clear() { public static void clear() {
_CACHE.invalidateAll(); _CACHE.invalidateAll();
_missingIconKeys.clear(); _missingIconKeys.clear();
ImageKeys.clearMissingCards();
} }
/** /**

View File

@@ -201,6 +201,9 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
private final FCheckBox cardArtPrefHasFilterCheckBox = new FCheckBox(Localizer.getInstance() private final FCheckBox cardArtPrefHasFilterCheckBox = new FCheckBox(Localizer.getInstance()
.getMessage("lblPrefArtExpansionOnly"), false); .getMessage("lblPrefArtExpansionOnly"), false);
// Smart Card Art Optimisation
private final FCheckBox smartCardArtCheckBox = new FCheckBox(Localizer.getInstance().getMessage("lblUseSmartCardArt"), false);
// Format Filter // Format Filter
private final FCheckBox includeBnRCheck = new FCheckBox(Localizer.getInstance().getMessage("lblIgnoreBnR"), false); private final FCheckBox includeBnRCheck = new FCheckBox(Localizer.getInstance().getMessage("lblIgnoreBnR"), false);
@@ -214,6 +217,8 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
private final String IMPORT_CARDS_CMD_LABEL = Localizer.getInstance().getMessage("lblImportCardsCmd"); private final String IMPORT_CARDS_CMD_LABEL = Localizer.getInstance().getMessage("lblImportCardsCmd");
private final String CREATE_NEW_DECK_CMD_LABEL = Localizer.getInstance().getMessage("lblCreateNewCmd"); private final String CREATE_NEW_DECK_CMD_LABEL = Localizer.getInstance().getMessage("lblCreateNewCmd");
private final String SMART_CARDART_TT_NO_DECK = Localizer.getInstance().getMessage("ttUseSmartCardArtNoDeck");
private final String SMART_CARDART_TT_WITH_DECK = Localizer.getInstance().getMessage("ttUseSmartCardArtWithDeck");
private final String currentGameType; private final String currentGameType;
private final VStatisticsImporter statsView; private final VStatisticsImporter statsView;
private final CStatisticsImporter cStatsView; private final CStatisticsImporter cStatsView;
@@ -224,6 +229,8 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
GameType currentGameType = g.getGameType(); GameType currentGameType = g.getGameType();
this.controller = new DeckImportController(dateTimeCheck, monthDropdown, yearDropdown, currentDeckIsNotEmpty); this.controller = new DeckImportController(dateTimeCheck, monthDropdown, yearDropdown, currentDeckIsNotEmpty);
this.controller.setGameFormat(currentGameType); this.controller.setGameFormat(currentGameType);
if (currentDeckIsNotEmpty)
this.controller.setCurrentDeckInEditor(this.host.getDeckController().getCurrentDeckInEditor());
// Get the list of allowed Sections // Get the list of allowed Sections
List<DeckSection> supportedSections = new ArrayList<>(); List<DeckSection> supportedSections = new ArrayList<>();
for (DeckSection section : EnumSet.allOf(DeckSection.class)) { for (DeckSection section : EnumSet.allOf(DeckSection.class)) {
@@ -380,6 +387,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
optionsPanel.add(dateFilterPanel, "cell 0 0, w 90%, left"); optionsPanel.add(dateFilterPanel, "cell 0 0, w 90%, left");
// B2. Card Art Preference Filter // B2. Card Art Preference Filter
final String latestOpt = Localizer.getInstance().getMessage("latestArtOpt"); final String latestOpt = Localizer.getInstance().getMessage("latestArtOpt");
final String originalOpt = Localizer.getInstance().getMessage("originalArtOpt"); final String originalOpt = Localizer.getInstance().getMessage("originalArtOpt");
final String [] choices = {latestOpt, originalOpt}; final String [] choices = {latestOpt, originalOpt};
@@ -393,6 +401,10 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
artPrefInfoLabel.setFont(FSkin.getItalicFont()); artPrefInfoLabel.setFont(FSkin.getItalicFont());
artPrefInfoLabel.setForeground(FSkin.getColor(FSkin.Colors.CLR_TEXT)); artPrefInfoLabel.setForeground(FSkin.getColor(FSkin.Colors.CLR_TEXT));
// Smart Art Checkbox
this.smartCardArtCheckBox.setToolTipText(currentDeckIsNotEmpty ? SMART_CARDART_TT_WITH_DECK : SMART_CARDART_TT_NO_DECK);
this.smartCardArtCheckBox.setSelected(StaticData.instance().isEnabledCardArtSmartSelection());
this.cardArtPrefHasFilterCheckBox.setSelected(StaticData.instance().isCoreExpansionOnlyFilterSet()); this.cardArtPrefHasFilterCheckBox.setSelected(StaticData.instance().isCoreExpansionOnlyFilterSet());
this.cardArtPrefHasFilterCheckBox.setToolTipText(Localizer.getInstance().getMessage("nlPrefArtExpansionOnly")); this.cardArtPrefHasFilterCheckBox.setToolTipText(Localizer.getInstance().getMessage("nlPrefArtExpansionOnly"));
@@ -401,7 +413,8 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
cardArtPanel.add(this.cardArtPrefsLabel, "cell 0 0, w 25%, left, split 2"); cardArtPanel.add(this.cardArtPrefsLabel, "cell 0 0, w 25%, left, split 2");
cardArtPanel.add(this.cardArtPrefsComboBox, "cell 0 0, w 10%, left, split 2"); cardArtPanel.add(this.cardArtPrefsComboBox, "cell 0 0, w 10%, left, split 2");
cardArtPanel.add(this.cardArtPrefHasFilterCheckBox, "cell 0 1, w 15%, left, gaptop 5"); cardArtPanel.add(this.cardArtPrefHasFilterCheckBox, "cell 0 1, w 15%, left, gaptop 5");
cardArtPanel.add(artPrefInfoLabel, "cell 0 2, w 90%, left"); cardArtPanel.add(this.smartCardArtCheckBox, "cell 0 2, w 15%, left, gaptop 5");
cardArtPanel.add(artPrefInfoLabel, "cell 0 3, w 90%, left");
// Action Listeners // Action Listeners
// ---------------- // ----------------
@@ -420,6 +433,15 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
this.cardArtPrefsComboBox.addItemListener(updateCardArtPreference); this.cardArtPrefsComboBox.addItemListener(updateCardArtPreference);
this.cardArtPrefHasFilterCheckBox.addItemListener(updateCardArtPreference); this.cardArtPrefHasFilterCheckBox.addItemListener(updateCardArtPreference);
this.smartCardArtCheckBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean enableSmartCardArt = smartCardArtCheckBox.isSelected();
controller.setSmartCardArtOptimisation(enableSmartCardArt);
parseAndDisplay();
}
});
optionsPanel.add(cardArtPanel, "cell 1 0, w 100%, left"); optionsPanel.add(cardArtPanel, "cell 1 0, w 100%, left");
// B3. Block Filter // B3. Block Filter
@@ -531,6 +553,9 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
controller.setCreateNewDeck(createNewDeck); controller.setCreateNewDeck(createNewDeck);
String cmdAcceptLabel = createNewDeck ? CREATE_NEW_DECK_CMD_LABEL : IMPORT_CARDS_CMD_LABEL; String cmdAcceptLabel = createNewDeck ? CREATE_NEW_DECK_CMD_LABEL : IMPORT_CARDS_CMD_LABEL;
cmdAcceptButton.setText(cmdAcceptLabel); cmdAcceptButton.setText(cmdAcceptLabel);
String smartCardArtChboxTooltip = createNewDeck ? SMART_CARDART_TT_NO_DECK : SMART_CARDART_TT_WITH_DECK;
smartCardArtCheckBox.setToolTipText(smartCardArtChboxTooltip);
parseAndDisplay();
} }
}); });
} }
@@ -554,63 +579,75 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
TokenKey tokenKey = TokenKey.fromString(keyString); TokenKey tokenKey = TokenKey.fromString(keyString);
if (tokenKey == null) if (tokenKey == null)
return; return;
StaticData data = StaticData.instance(); PaperCard card = StaticData.instance().fetchCard(tokenKey.cardName, tokenKey.setCode,
PaperCard card = data.fetchCard(tokenKey.cardName, tokenKey.setCode, tokenKey.collectorNumber); tokenKey.collectorNumber);
if (card != null) { if (card != null) {
// no need to check for card that has Image because CardPicturePanel DeckRecognizer.Token mockToken = createMockTokenFromTokenKey(card, tokenKey);
// has automatic integration with cardFetch setupCardImagePreviewPanel(card, mockToken);
StringBuilder statusLbl = new StringBuilder();
if ((tokenKey.tokenType == CARD_FROM_INVALID_SET) || (tokenKey.tokenType == CARD_FROM_NOT_ALLOWED_SET)
|| (tokenKey.tokenType == LIMITED_CARD)) {
DeckRecognizer.Token dummy = null;
switch (tokenKey.tokenType) {
case CARD_FROM_INVALID_SET:
dummy = DeckRecognizer.Token.CardInInvalidSet(card, 0);
break;
case CARD_FROM_NOT_ALLOWED_SET:
dummy = DeckRecognizer.Token.NotAllowedCard(card, 0);
break;
case LIMITED_CARD:
dummy = DeckRecognizer.Token.LimitedCard(card, 0, tokenKey.deckSection, tokenKey.limitedType);
break;
}
String cssClass = getTokenCSSClass(tokenKey.tokenType);
if (tokenKey.tokenType == LIMITED_CARD)
cssClass = WARN_MSG_CLASS;
String statusMsg = String.format("<span class=\"%s\" style=\"font-size: 9px;\">%s</span>", cssClass,
getTokenStatusMessage(dummy));
statusLbl.append(statusMsg);
}
CardEdition edition = data.getCardEdition(card.getEdition());
String editionName = edition != null ? String.format("%s ", edition.getName()) : "";
StringBuilder editionLbl = new StringBuilder("<span style=\"font-size: 9px;\">");
editionLbl.append(String.format("<b>%s</b>: \"%s\" (<code>%s</code>) - Collector Nr. %s",
Localizer.getInstance().getMessage("lblSet"), editionName,
tokenKey.setCode, card.getCollectorNumber()));
if ((tokenKey.tokenType == LEGAL_CARD) ||
((tokenKey.tokenType == LIMITED_CARD) && this.controller.importBannedAndRestrictedCards())){
editionLbl.append(String.format(" - <b class=\"%s\">%s: %s</b>", OK_IMPORT_CLASS,
Localizer.getInstance().getMessage("lblDeckSection"), tokenKey.deckSection));
}
editionLbl.append("</span>");
cardImagePreview.setItem(card);
if (tokenKey.tokenType == LEGAL_CARD ||
(tokenKey.tokenType == LIMITED_CARD && this.controller.importBannedAndRestrictedCards()))
cardImagePreview.showAsEnabled();
else
cardImagePreview.showAsDisabled();
cardPreviewLabel.setText(String.format("<html>%s %s<br>%s</html>", STYLESHEET, editionLbl, statusLbl));
// set tooltip
String tooltip = String.format("%s [%s] #%s", card.getName(), card.getEdition(),
card.getCollectorNumber());
cardImagePreview.setToolTipText(tooltip);
} }
} }
} }
private DeckRecognizer.Token createMockTokenFromTokenKey(PaperCard card, TokenKey tokenKey){
DeckRecognizer.Token mockToken;
switch (tokenKey.tokenType) {
case CARD_FROM_INVALID_SET:
mockToken = DeckRecognizer.Token.CardInInvalidSet(card, 0, true);
break;
case CARD_FROM_NOT_ALLOWED_SET:
mockToken = DeckRecognizer.Token.NotAllowedCard(card, 0, true);
break;
case LIMITED_CARD:
mockToken = DeckRecognizer.Token.LimitedCard(card, 0, tokenKey.deckSection, tokenKey.limitedType, true);
break;
case LEGAL_CARD:
mockToken = DeckRecognizer.Token.LegalCard(card, 0, tokenKey.deckSection, true);
break;
default:
mockToken = null;
break;
}
return mockToken;
}
private void setupCardImagePreviewPanel(PaperCard card, DeckRecognizer.Token token) {
StaticData data = StaticData.instance();
// no need to check for card that has Image because CardPicturePanel
// has automatic integration with cardFetch
StringBuilder statusLbl = new StringBuilder();
if (token != null && token.isCardToken()) {
String cssClass = getTokenCSSClass(token.getType());
if (token.getType() == LIMITED_CARD)
cssClass = WARN_MSG_CLASS;
String statusMsg = String.format("<span class=\"%s\" style=\"font-size: 9px;\">%s</span>", cssClass,
getTokenStatusMessage(token));
statusLbl.append(statusMsg);
}
CardEdition edition = data.getCardEdition(card.getEdition());
String editionName = edition != null ? String.format("%s ", edition.getName()) : "";
StringBuilder editionLbl = new StringBuilder("<span style=\"font-size: 9px;\">");
editionLbl.append(String.format("<b>%s</b>: \"%s\" (<code>%s</code>) - Collector Nr. %s",
Localizer.getInstance().getMessage("lblSet"), editionName, card.getEdition(), card.getCollectorNumber()));
if ((token.getType() == LEGAL_CARD) || ((token.getType() == LIMITED_CARD) && this.controller.importBannedAndRestrictedCards())){
editionLbl.append(String.format(" - <b class=\"%s\">%s: %s</b>", OK_IMPORT_CLASS,
Localizer.getInstance().getMessage("lblDeckSection"), token.getTokenSection()));
}
editionLbl.append("</span>");
cardImagePreview.setItem(card);
if (token.getType() == LEGAL_CARD || (token.getType() == LIMITED_CARD && this.controller.importBannedAndRestrictedCards()))
cardImagePreview.showAsEnabled();
else
cardImagePreview.showAsDisabled();
cardPreviewLabel.setText(String.format("<html>%s %s<br>%s</html>", STYLESHEET, editionLbl, statusLbl));
// set tooltip
String tooltip = String.format("%s [%s] #%s", card.getName(), card.getEdition(),
card.getCollectorNumber());
cardImagePreview.setToolTipText(tooltip);
}
private void resetCardImagePreviewPanel() { private void resetCardImagePreviewPanel() {
this.cardPreviewLabel.setText(Localizer.getInstance().getMessage("lblCardPreview")); this.cardPreviewLabel.setText(Localizer.getInstance().getMessage("lblCardPreview"));
this.cardImagePreview.setItem(ImageCache.getDefaultImage()); this.cardImagePreview.setItem(ImageCache.getDefaultImage());
@@ -641,7 +678,9 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
} }
private void parseAndDisplay() { private void parseAndDisplay() {
final List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText()); List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText());
if (controller.isSmartCardArtEnabled())
tokens = controller.optimiseCardArtInTokens();
displayTokens(tokens); displayTokens(tokens);
updateSummaries(tokens); updateSummaries(tokens);
} }
@@ -685,27 +724,39 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
PaperCard cardDisplayed = (PaperCard) displayedCardInPanel; PaperCard cardDisplayed = (PaperCard) displayedCardInPanel;
// this will return either the same card instance or its [un]foiled version // this will return either the same card instance or its [un]foiled version
// null will be returned if not found in card list anymore // null will be returned if not found in card list anymore
cardDisplayed = this.controller.getCardFromDecklist(cardDisplayed); PaperCard cardFromDecklist = this.controller.getCardFromDecklist(cardDisplayed);
if (cardDisplayed == null) if (cardFromDecklist == null) {
this.resetCardImagePreviewPanel(); // current displayed card is not in decklist cardFromDecklist = this.controller.getCardFromDecklistByName(cardDisplayed.getName());
else { if (cardFromDecklist == null)
if (this.controller.isTokenInListLegal(cardDisplayed)) { this.resetCardImagePreviewPanel(); // current displayed card is not in decklist
this.cardImagePreview.setItem(cardDisplayed); else {
this.cardImagePreview.showAsEnabled(); DeckRecognizer.Token cardToken = controller.getTokenFromCardInDecklist(cardFromDecklist);
} else if (this.controller.isTokenInListLimited(cardDisplayed)) { setupCardImagePreviewPanel(cardFromDecklist, cardToken);
this.cardImagePreview.setItem(cardDisplayed);
if (this.includeBnRCheck.isSelected())
this.cardImagePreview.showAsEnabled();
else
this.cardImagePreview.showAsDisabled();
} else { // any other card token NOT legal nor limited
this.cardImagePreview.setItem(cardDisplayed);
this.cardImagePreview.showAsDisabled();
} }
} }
else {
DeckRecognizer.Token cardToken = controller.getTokenFromCardInDecklist(cardFromDecklist);
setupCardImagePreviewPanel(cardFromDecklist, cardToken);
}
} }
} }
// private void setupCardImagePreview(PaperCard cardDisplayed) {
// if (this.controller.isTokenInListLegal(cardDisplayed)) {
// this.cardImagePreview.setItem(cardDisplayed);
// this.cardImagePreview.showAsEnabled();
// } else if (this.controller.isTokenInListLimited(cardDisplayed)) {
// this.cardImagePreview.setItem(cardDisplayed);
// if (this.includeBnRCheck.isSelected())
// this.cardImagePreview.showAsEnabled();
// else
// this.cardImagePreview.showAsDisabled();
// } else { // any other card token NOT legal nor limited
// this.cardImagePreview.setItem(cardDisplayed);
// this.cardImagePreview.showAsDisabled();
// }
// }
private String toHTML(final DeckRecognizer.Token token) { private String toHTML(final DeckRecognizer.Token token) {
if (token == null) if (token == null)
return ""; return "";
@@ -714,7 +765,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
return ""; return "";
String tokenStatus = getTokenStatusMessage(token); String tokenStatus = getTokenStatusMessage(token);
String cssClass = getTokenCSSClass(token.getType()); String cssClass = getTokenCSSClass(token.getType());
if (tokenStatus == null) if (tokenStatus.length() == 0)
tokenMsg = padEndWithHTMLSpaces(tokenMsg, 2*PADDING_TOKEN_MSG_LENGTH+10); tokenMsg = padEndWithHTMLSpaces(tokenMsg, 2*PADDING_TOKEN_MSG_LENGTH+10);
else { else {
tokenMsg = padEndWithHTMLSpaces(tokenMsg, PADDING_TOKEN_MSG_LENGTH); tokenMsg = padEndWithHTMLSpaces(tokenMsg, PADDING_TOKEN_MSG_LENGTH);
@@ -830,7 +881,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
case LEGAL_CARD: case LEGAL_CARD:
case UNKNOWN_TEXT: case UNKNOWN_TEXT:
default: default:
return null; return "";
} }

View File

@@ -120,6 +120,14 @@ public class DeckController<T extends DeckBase> {
this.setModel((T) currentDeck, isStored); this.setModel((T) currentDeck, isStored);
} }
public Deck getCurrentDeckInEditor(){
try{
return this.getModel().getHumanDeck();
} catch (NullPointerException npe){
return null;
}
}
private Deck pickFromCatalog(Deck deck, CardPool catalog) { private Deck pickFromCatalog(Deck deck, CardPool catalog) {
// Getting Latest among the earliest editions in catalog! // Getting Latest among the earliest editions in catalog!
CardEdition referenceEdition = StaticData.instance().getEditions().getTheLatestOfAllTheOriginalEditionsOfCardsIn(catalog); CardEdition referenceEdition = StaticData.instance().getEditions().getTheLatestOfAllTheOriginalEditionsOfCardsIn(catalog);
@@ -182,8 +190,7 @@ public class DeckController<T extends DeckBase> {
if (card == null) if (card == null)
continue; continue;
int countToAdd = countByName.get(cardName); int countToAdd = countByName.get(cardName);
card = StaticData.instance().getAlternativeCardPrint(card, referenceReleaseDate, card = StaticData.instance().getAlternativeCardPrint(card, referenceReleaseDate);
true, true);
if (card != null) if (card != null)
targetSection.add(card.getName(), card.getEdition(), countToAdd); targetSection.add(card.getName(), card.getEdition(), countToAdd);
} }

View File

@@ -19,6 +19,7 @@ import forge.model.FModel;
import forge.player.GamePlayerUtil; import forge.player.GamePlayerUtil;
import forge.screens.deckeditor.CDeckEditorUI; import forge.screens.deckeditor.CDeckEditorUI;
import forge.screens.deckeditor.controllers.CEditorTokenViewer; import forge.screens.deckeditor.controllers.CEditorTokenViewer;
import forge.sound.MusicPlaylist;
import forge.sound.SoundSystem; import forge.sound.SoundSystem;
import forge.toolbox.*; import forge.toolbox.*;
import forge.util.Localizer; import forge.util.Localizer;
@@ -27,6 +28,8 @@ import org.apache.commons.lang3.tuple.Pair;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent; import java.awt.event.ItemEvent;
import java.awt.event.ItemListener; import java.awt.event.ItemListener;
import java.io.File; import java.io.File;
@@ -262,6 +265,8 @@ public enum CSubmenuPreferences implements ICDoc {
initializeAutoUpdaterComboBox(); initializeAutoUpdaterComboBox();
initializeMulliganRuleComboBox(); initializeMulliganRuleComboBox();
initializeAiProfilesComboBox(); initializeAiProfilesComboBox();
initializeSoundSetsComboBox();
initializeMusicSetsComboBox();
initializeStackAdditionsComboBox(); initializeStackAdditionsComboBox();
initializeLandPlayedComboBox(); initializeLandPlayedComboBox();
initializeColorIdentityCombobox(); initializeColorIdentityCombobox();
@@ -466,6 +471,35 @@ public enum CSubmenuPreferences implements ICDoc {
panel.setComboBox(comboBox, selectedItem); panel.setComboBox(comboBox, selectedItem);
} }
private void initializeSoundSetsComboBox() {
final FPref userSetting = FPref.UI_CURRENT_SOUND_SET;
final FComboBoxPanel<String> panel = this.view.getSoundSetsComboBoxPanel();
final FComboBox<String> comboBox = createComboBox(SoundSystem.instance.getAvailableSoundSets(), userSetting);
final String selectedItem = this.prefs.getPref(userSetting);
panel.setComboBox(comboBox, selectedItem);
comboBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
SoundSystem.instance.invalidateSoundCache();
}
});
}
private void initializeMusicSetsComboBox() {
final FPref userSetting = FPref.UI_CURRENT_MUSIC_SET;
final FComboBoxPanel<String> panel = this.view.getMusicSetsComboBoxPanel();
final FComboBox<String> comboBox = createComboBox(SoundSystem.instance.getAvailableMusicSets(), userSetting);
final String selectedItem = this.prefs.getPref(userSetting);
panel.setComboBox(comboBox, selectedItem);
comboBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
MusicPlaylist.invalidateMusicPlaylist();
SoundSystem.instance.changeBackgroundTrack();
}
});
}
private void initializeCardArtPreference() { private void initializeCardArtPreference() {
final String latestOpt = Localizer.getInstance().getMessage("latestArtOpt"); final String latestOpt = Localizer.getInstance().getMessage("latestArtOpt");
final String originalOpt = Localizer.getInstance().getMessage("originalArtOpt"); final String originalOpt = Localizer.getInstance().getMessage("originalArtOpt");

View File

@@ -128,6 +128,8 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
private final FComboBoxPanel<String> cbpCardArtFormat = new FComboBoxPanel<>(localizer.getMessage("cbpCardArtFormat")+":"); private final FComboBoxPanel<String> cbpCardArtFormat = new FComboBoxPanel<>(localizer.getMessage("cbpCardArtFormat")+":");
private final FComboBoxPanel<String> cbpCardArtPreference = new FComboBoxPanel<>(localizer.getMessage("lblPreferredArt")+":"); private final FComboBoxPanel<String> cbpCardArtPreference = new FComboBoxPanel<>(localizer.getMessage("lblPreferredArt")+":");
private final FComboBoxPanel<String> cbpMulliganRule = new FComboBoxPanel<>(localizer.getMessage("cbpMulliganRule")+":"); private final FComboBoxPanel<String> cbpMulliganRule = new FComboBoxPanel<>(localizer.getMessage("cbpMulliganRule")+":");
private final FComboBoxPanel<String> cbpSoundSets = new FComboBoxPanel<>(localizer.getMessage("cbpSoundSets")+":");
private final FComboBoxPanel<String> cbpMusicSets = new FComboBoxPanel<>(localizer.getMessage("cbpMusicSets")+":");
private final FComboBoxPanel<String> cbpAiProfiles = new FComboBoxPanel<>(localizer.getMessage("cbpAiProfiles")+":"); private final FComboBoxPanel<String> cbpAiProfiles = new FComboBoxPanel<>(localizer.getMessage("cbpAiProfiles")+":");
private final FComboBoxPanel<String> cbpStackAdditions = new FComboBoxPanel<>(localizer.getMessage("cbpStackAdditions")+":"); private final FComboBoxPanel<String> cbpStackAdditions = new FComboBoxPanel<>(localizer.getMessage("cbpStackAdditions")+":");
private final FComboBoxPanel<String> cbpLandPlayed = new FComboBoxPanel<>(localizer.getMessage("cbpLandPlayed")+":"); private final FComboBoxPanel<String> cbpLandPlayed = new FComboBoxPanel<>(localizer.getMessage("cbpLandPlayed")+":");
@@ -414,9 +416,15 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
pnlPrefs.add(cbEnableSounds, titleConstraints); pnlPrefs.add(cbEnableSounds, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnableSounds")), descriptionConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnableSounds")), descriptionConstraints);
pnlPrefs.add(cbpSoundSets, comboBoxConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpSoundSets")), descriptionConstraints);
pnlPrefs.add(cbEnableMusic, titleConstraints); pnlPrefs.add(cbEnableMusic, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnableMusic")), descriptionConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnableMusic")), descriptionConstraints);
pnlPrefs.add(cbpMusicSets, comboBoxConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpMusicSets")), descriptionConstraints);
pnlPrefs.add(cbAltSoundSystem, titleConstraints); pnlPrefs.add(cbAltSoundSystem, titleConstraints);
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlAltSoundSystem")), descriptionConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlAltSoundSystem")), descriptionConstraints);
pnlPrefs.add(cbSROptimize, titleConstraints); pnlPrefs.add(cbSROptimize, titleConstraints);
@@ -740,6 +748,14 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
return cbpMulliganRule; return cbpMulliganRule;
} }
public FComboBoxPanel<String> getSoundSetsComboBoxPanel() {
return cbpSoundSets;
}
public FComboBoxPanel<String> getMusicSetsComboBoxPanel() {
return cbpMusicSets;
}
public FComboBoxPanel<String> getAiProfilesComboBoxPanel() { public FComboBoxPanel<String> getAiProfilesComboBoxPanel() {
return cbpAiProfiles; return cbpAiProfiles;
} }

View File

@@ -40,9 +40,6 @@ import javax.sound.sampled.UnsupportedAudioFileException;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.sipgate.mp3wav.Converter; import com.sipgate.mp3wav.Converter;
import forge.localinstance.properties.ForgeConstants;
/** /**
* SoundSystem - a simple sound playback system for Forge. * SoundSystem - a simple sound playback system for Forge.
* Do not use directly. Instead, use the {@link forge.sound.SoundEffectType} enumeration. * Do not use directly. Instead, use the {@link forge.sound.SoundEffectType} enumeration.
@@ -64,7 +61,7 @@ public class AudioClip implements IAudioClip {
} }
public static boolean fileExists(String fileName) { public static boolean fileExists(String fileName) {
File fSound = new File(ForgeConstants.SOUND_DIR, fileName); File fSound = new File(SoundSystem.instance.getSoundDirectory(), fileName);
return fSound.exists(); return fSound.exists();
} }
@@ -195,7 +192,7 @@ public class AudioClip implements IAudioClip {
} }
private Clip createClip(String filename) { private Clip createClip(String filename) {
File fSound = new File(ForgeConstants.SOUND_DIR, filename); File fSound = new File(SoundSystem.instance.getSoundDirectory(), filename);
if (!fSound.exists()) { if (!fSound.exists()) {
throw new IllegalArgumentException("Sound file " + fSound.toString() + " does not exist, cannot make a clip of it"); throw new IllegalArgumentException("Sound file " + fSound.toString() + " does not exist, cannot make a clip of it");
} }

View File

@@ -1114,6 +1114,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertTrue(tokenCard.isFoil()); assertTrue(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "TMP"); assertEquals(tokenCard.getEdition(), "TMP");
assertEquals(tokenCard.getCollectorNumber(), "78"); assertEquals(tokenCard.getCollectorNumber(), "78");
assertFalse(cardToken.cardRequestHasNoCode());
lineRequest = "4x Power Sink TMP 78"; lineRequest = "4x Power Sink TMP 78";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1125,6 +1126,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(tokenCard.getName(), "Power Sink"); assertEquals(tokenCard.getName(), "Power Sink");
assertFalse(tokenCard.isFoil()); assertFalse(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "TMP"); assertEquals(tokenCard.getEdition(), "TMP");
assertFalse(cardToken.cardRequestHasNoCode());
assertEquals(tokenCard.getCollectorNumber(), "78"); assertEquals(tokenCard.getCollectorNumber(), "78");
lineRequest = "4x TMP Power Sink 78"; lineRequest = "4x TMP Power Sink 78";
@@ -1138,6 +1140,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertFalse(tokenCard.isFoil()); assertFalse(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "TMP"); assertEquals(tokenCard.getEdition(), "TMP");
assertEquals(tokenCard.getCollectorNumber(), "78"); assertEquals(tokenCard.getCollectorNumber(), "78");
assertFalse(cardToken.cardRequestHasNoCode());
lineRequest = "4x TMP Power Sink"; lineRequest = "4x TMP Power Sink";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1150,6 +1153,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertFalse(tokenCard.isFoil()); assertFalse(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "TMP"); assertEquals(tokenCard.getEdition(), "TMP");
assertEquals(tokenCard.getCollectorNumber(), "78"); assertEquals(tokenCard.getCollectorNumber(), "78");
assertFalse(cardToken.cardRequestHasNoCode());
lineRequest = "4x Power Sink TMP"; lineRequest = "4x Power Sink TMP";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1162,6 +1166,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertFalse(tokenCard.isFoil()); assertFalse(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "TMP"); assertEquals(tokenCard.getEdition(), "TMP");
assertEquals(tokenCard.getCollectorNumber(), "78"); assertEquals(tokenCard.getCollectorNumber(), "78");
assertFalse(cardToken.cardRequestHasNoCode());
lineRequest = "Power Sink TMP"; lineRequest = "Power Sink TMP";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1174,6 +1179,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertFalse(tokenCard.isFoil()); assertFalse(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "TMP"); assertEquals(tokenCard.getEdition(), "TMP");
assertEquals(tokenCard.getCollectorNumber(), "78"); assertEquals(tokenCard.getCollectorNumber(), "78");
assertFalse(cardToken.cardRequestHasNoCode());
lineRequest = "[TMP] Power Sink"; lineRequest = "[TMP] Power Sink";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1186,6 +1192,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertFalse(tokenCard.isFoil()); assertFalse(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "TMP"); assertEquals(tokenCard.getEdition(), "TMP");
assertEquals(tokenCard.getCollectorNumber(), "78"); assertEquals(tokenCard.getCollectorNumber(), "78");
assertFalse(cardToken.cardRequestHasNoCode());
// Relax Set Preference // Relax Set Preference
assertEquals(StaticData.instance().getCommonCards().getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS); assertEquals(StaticData.instance().getCommonCards().getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
@@ -1200,6 +1207,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(tokenCard.getName(), "Power Sink"); assertEquals(tokenCard.getName(), "Power Sink");
assertFalse(tokenCard.isFoil()); assertFalse(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "VMA"); assertEquals(tokenCard.getEdition(), "VMA");
assertTrue(cardToken.cardRequestHasNoCode());
lineRequest = "4x Power Sink+"; lineRequest = "4x Power Sink+";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1211,6 +1219,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(tokenCard.getName(), "Power Sink"); assertEquals(tokenCard.getName(), "Power Sink");
assertTrue(tokenCard.isFoil()); assertTrue(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "VMA"); assertEquals(tokenCard.getEdition(), "VMA");
assertTrue(cardToken.cardRequestHasNoCode());
lineRequest = "Power Sink+"; lineRequest = "Power Sink+";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1222,6 +1231,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(tokenCard.getName(), "Power Sink"); assertEquals(tokenCard.getName(), "Power Sink");
assertTrue(tokenCard.isFoil()); assertTrue(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "VMA"); assertEquals(tokenCard.getEdition(), "VMA");
assertTrue(cardToken.cardRequestHasNoCode());
} }
@Test void testSingleWordCardNameMatchesCorrectly(){ @Test void testSingleWordCardNameMatchesCorrectly(){
@@ -1236,6 +1246,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getQuantity(), 2); assertEquals(cardToken.getQuantity(), 2);
assertEquals(tokenCard.getName(), "Counterspell"); assertEquals(tokenCard.getName(), "Counterspell");
assertEquals(tokenCard.getEdition(), "ICE"); assertEquals(tokenCard.getEdition(), "ICE");
assertFalse(cardToken.cardRequestHasNoCode());
// Remove Set code // Remove Set code
assertEquals(StaticData.instance().getCommonCards().getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS); assertEquals(StaticData.instance().getCommonCards().getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
@@ -1248,6 +1259,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getQuantity(), 2); assertEquals(cardToken.getQuantity(), 2);
assertEquals(tokenCard.getName(), "Counterspell"); assertEquals(tokenCard.getName(), "Counterspell");
assertEquals(tokenCard.getEdition(), "MH2"); assertEquals(tokenCard.getEdition(), "MH2");
assertTrue(cardToken.cardRequestHasNoCode());
} }
@@ -1271,6 +1283,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertTrue(tokenCard.isFoil()); assertTrue(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "TMP"); assertEquals(tokenCard.getEdition(), "TMP");
assertEquals(tokenCard.getCollectorNumber(), "78"); assertEquals(tokenCard.getCollectorNumber(), "78");
assertFalse(cardToken.cardRequestHasNoCode());
} }
/*================================== /*==================================
@@ -1402,6 +1415,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(tokenCard.getEdition(), "MIR"); assertEquals(tokenCard.getEdition(), "MIR");
assertEquals(tokenCard.getArtIndex(), 3); assertEquals(tokenCard.getArtIndex(), 3);
assertEquals(tokenCard.getCollectorNumber(), "345"); assertEquals(tokenCard.getCollectorNumber(), "345");
assertFalse(cardToken.cardRequestHasNoCode());
} }
@Test void testCollectorNumberIsNotConfusedAsArtIndexInstead(){ @Test void testCollectorNumberIsNotConfusedAsArtIndexInstead(){
@@ -1418,6 +1432,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(tokenCard.getEdition(), "MIR"); assertEquals(tokenCard.getEdition(), "MIR");
assertEquals(tokenCard.getArtIndex(), 1); assertEquals(tokenCard.getArtIndex(), 1);
assertEquals(tokenCard.getCollectorNumber(), "3"); assertEquals(tokenCard.getCollectorNumber(), "3");
assertFalse(cardToken.cardRequestHasNoCode());
} }
@Test void testCardRequestWithWrongCollectorNumberStillReturnsTheCardFromSetIfAny(){ @Test void testCardRequestWithWrongCollectorNumberStillReturnsTheCardFromSetIfAny(){
@@ -1433,6 +1448,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(card.getName(), "Jayemdae Tome"); assertEquals(card.getName(), "Jayemdae Tome");
assertEquals(card.getEdition(), "LEB"); assertEquals(card.getEdition(), "LEB");
assertEquals(card.getCollectorNumber(), "255"); assertEquals(card.getCollectorNumber(), "255");
assertFalse(cardToken.cardRequestHasNoCode());
// No Match - Unknown card // No Match - Unknown card
requestLine = "3 Jayemdae Tome (TMP)"; // actually found in TappedOut Deck Export requestLine = "3 Jayemdae Tome (TMP)"; // actually found in TappedOut Deck Export
@@ -1486,6 +1502,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(ancestralCard.getName(), "Ancestral Recall"); assertEquals(ancestralCard.getName(), "Ancestral Recall");
assertEquals(StaticData.instance().getCommonCards().getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS); assertEquals(StaticData.instance().getCommonCards().getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
assertEquals(ancestralCard.getEdition(), "2ED"); assertEquals(ancestralCard.getEdition(), "2ED");
assertTrue(cardToken.cardRequestHasNoCode());
// Counterspell editions // Counterspell editions
lineRequest = "Counterspell"; lineRequest = "Counterspell";
@@ -1498,6 +1515,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getQuantity(), 1); assertEquals(cardToken.getQuantity(), 1);
assertEquals(counterSpellCard.getName(), "Counterspell"); assertEquals(counterSpellCard.getName(), "Counterspell");
assertEquals(counterSpellCard.getEdition(), "MMQ"); assertEquals(counterSpellCard.getEdition(), "MMQ");
assertTrue(cardToken.cardRequestHasNoCode());
} }
@Test void testInvalidCardRequestWhenReleaseDateConstraintsAreUp(){ @Test void testInvalidCardRequestWhenReleaseDateConstraintsAreUp(){
@@ -1515,12 +1533,14 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getQuantity(), 1); assertEquals(cardToken.getQuantity(), 1);
assertEquals(counterSpellCard.getName(), "Counterspell"); assertEquals(counterSpellCard.getName(), "Counterspell");
assertEquals(counterSpellCard.getEdition(), "MH2"); assertEquals(counterSpellCard.getEdition(), "MH2");
assertFalse(cardToken.cardRequestHasNoCode());
recognizer.setDateConstraint(1999, 10); recognizer.setDateConstraint(1999, 10);
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
assertNotNull(cardToken); assertNotNull(cardToken);
assertEquals(cardToken.getType(), TokenType.CARD_FROM_INVALID_SET); assertEquals(cardToken.getType(), TokenType.CARD_FROM_INVALID_SET);
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertFalse(cardToken.cardRequestHasNoCode());
} }
/*====================================== /*======================================
@@ -1542,6 +1562,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
PaperCard tc = cardToken.getCard(); PaperCard tc = cardToken.getCard();
assertEquals(tc.getName(), "Counterspell"); assertEquals(tc.getName(), "Counterspell");
assertEquals(tc.getEdition(), "MH2"); assertEquals(tc.getEdition(), "MH2");
assertTrue(cardToken.cardRequestHasNoCode());
// Setting Original Core // Setting Original Core
recognizer.setArtPreference(CardDb.CardArtPreference.ORIGINAL_ART_ALL_EDITIONS); recognizer.setArtPreference(CardDb.CardArtPreference.ORIGINAL_ART_ALL_EDITIONS);
@@ -1555,6 +1576,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
tc = cardToken.getCard(); tc = cardToken.getCard();
assertEquals(tc.getName(), "Counterspell"); assertEquals(tc.getName(), "Counterspell");
assertEquals(tc.getEdition(), "LEA"); assertEquals(tc.getEdition(), "LEA");
assertTrue(cardToken.cardRequestHasNoCode());
} }
@Test void testCardRequestVariesUponChangesInArtPreference(){ @Test void testCardRequestVariesUponChangesInArtPreference(){
@@ -1571,6 +1593,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(tokenCard.getName(), "Power Sink"); assertEquals(tokenCard.getName(), "Power Sink");
assertTrue(tokenCard.isFoil()); assertTrue(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "VMA"); assertEquals(tokenCard.getEdition(), "VMA");
assertTrue(cardToken.cardRequestHasNoCode());
recognizer.setArtPreference(CardDb.CardArtPreference.ORIGINAL_ART_CORE_EXPANSIONS_REPRINT_ONLY); recognizer.setArtPreference(CardDb.CardArtPreference.ORIGINAL_ART_CORE_EXPANSIONS_REPRINT_ONLY);
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1582,6 +1605,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(tokenCard.getName(), "Power Sink"); assertEquals(tokenCard.getName(), "Power Sink");
assertTrue(tokenCard.isFoil()); assertTrue(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "LEA"); assertEquals(tokenCard.getEdition(), "LEA");
assertTrue(cardToken.cardRequestHasNoCode());
// Check that result is persistent - and consistent with change // Check that result is persistent - and consistent with change
lineRequest = "Power Sink"; lineRequest = "Power Sink";
@@ -1594,6 +1618,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(tokenCard.getName(), "Power Sink"); assertEquals(tokenCard.getName(), "Power Sink");
assertFalse(tokenCard.isFoil()); assertFalse(tokenCard.isFoil());
assertEquals(tokenCard.getEdition(), "LEA"); assertEquals(tokenCard.getEdition(), "LEA");
assertTrue(cardToken.cardRequestHasNoCode());
} }
/*============================== /*==============================
@@ -1612,6 +1637,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET); assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET);
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertNull(cardToken.getTokenSection()); assertNull(cardToken.getTokenSection());
assertFalse(cardToken.cardRequestHasNoCode());
lineRequest = "2x Counterspell"; // It does not exist any Counterspell in Urza's block lineRequest = "2x Counterspell"; // It does not exist any Counterspell in Urza's block
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1619,6 +1645,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET); assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET);
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertNull(cardToken.getTokenSection()); assertNull(cardToken.getTokenSection());
assertTrue(cardToken.cardRequestHasNoCode());
} }
@Test void testRequestingCardWithRestrictionsOnDeckFormat(){ @Test void testRequestingCardWithRestrictionsOnDeckFormat(){
@@ -1634,6 +1661,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(ancestralCard.getName(), "Ancestral Recall"); assertEquals(ancestralCard.getName(), "Ancestral Recall");
assertEquals(StaticData.instance().getCommonCards().getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS); assertEquals(StaticData.instance().getCommonCards().getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
assertEquals(ancestralCard.getEdition(), "VMA"); assertEquals(ancestralCard.getEdition(), "VMA");
assertTrue(cardToken.cardRequestHasNoCode());
recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders); recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders);
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1643,6 +1671,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getTokenSection(), DeckSection.Main); assertEquals(cardToken.getTokenSection(), DeckSection.Main);
assertNotNull(cardToken.getLimitedCardType()); assertNotNull(cardToken.getLimitedCardType());
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED); assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
assertTrue(cardToken.cardRequestHasNoCode());
} }
@Test void testCardRequestUnderGameConstraints(){ @Test void testCardRequestUnderGameConstraints(){
@@ -1668,6 +1697,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Bloodstained Mire"); assertEquals(cardToken.getCard().getName(), "Bloodstained Mire");
assertEquals(cardToken.getCard().getEdition(), "ONS"); assertEquals(cardToken.getCard().getEdition(), "ONS");
assertEquals(cardToken.getText(), "Bloodstained Mire [ONS] #313"); assertEquals(cardToken.getText(), "Bloodstained Mire [ONS] #313");
assertFalse(cardToken.cardRequestHasNoCode());
String noEditionCardRequest = "4 Bloodstained Mire"; String noEditionCardRequest = "4 Bloodstained Mire";
cardToken = recognizer.recogniseCardToken(noEditionCardRequest, null); cardToken = recognizer.recogniseCardToken(noEditionCardRequest, null);
@@ -1681,6 +1711,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Bloodstained Mire"); assertEquals(cardToken.getCard().getName(), "Bloodstained Mire");
assertEquals(cardToken.getCard().getEdition(), "KTK"); assertEquals(cardToken.getCard().getEdition(), "KTK");
assertEquals(cardToken.getText(), "Bloodstained Mire [KTK] #230"); assertEquals(cardToken.getText(), "Bloodstained Mire [KTK] #230");
assertTrue(cardToken.cardRequestHasNoCode());
} }
@Test void testGameFormatRestrictionsAlsoWithRestrictedCardList(){ @Test void testGameFormatRestrictionsAlsoWithRestrictedCardList(){
@@ -1712,6 +1743,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Ancestral Recall"); assertEquals(cardToken.getCard().getName(), "Ancestral Recall");
assertEquals(cardToken.getQuantity(), 1); assertEquals(cardToken.getQuantity(), 1);
assertEquals(cardToken.getCard().getEdition(), "VMA"); assertEquals(cardToken.getCard().getEdition(), "VMA");
assertTrue(cardToken.cardRequestHasNoCode());
cardRequest = "4x Ancestral Recall"; cardRequest = "4x Ancestral Recall";
cardToken = recognizer.recogniseCardToken(cardRequest, null); cardToken = recognizer.recogniseCardToken(cardRequest, null);
@@ -1723,6 +1755,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Ancestral Recall"); assertEquals(cardToken.getCard().getName(), "Ancestral Recall");
assertEquals(cardToken.getQuantity(), 4); assertEquals(cardToken.getQuantity(), 4);
assertEquals(cardToken.getCard().getEdition(), "VMA"); assertEquals(cardToken.getCard().getEdition(), "VMA");
assertTrue(cardToken.cardRequestHasNoCode());
} }
@Test void testSettingPartialConstraintsOnGameFormatsAreStillApplied(){ @Test void testSettingPartialConstraintsOnGameFormatsAreStillApplied(){
@@ -1746,6 +1779,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker"); assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker");
assertEquals(cardToken.getQuantity(), 1); assertEquals(cardToken.getQuantity(), 1);
assertEquals(cardToken.getCard().getEdition(), "MB1"); assertEquals(cardToken.getCard().getEdition(), "MB1");
assertTrue(cardToken.cardRequestHasNoCode());
cardRequest = "4x Viashino Sandstalker"; cardRequest = "4x Viashino Sandstalker";
cardToken = recognizer.recogniseCardToken(cardRequest, null); cardToken = recognizer.recogniseCardToken(cardRequest, null);
@@ -1757,6 +1791,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker"); assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker");
assertEquals(cardToken.getQuantity(), 4); assertEquals(cardToken.getQuantity(), 4);
assertEquals(cardToken.getCard().getEdition(), "MB1"); assertEquals(cardToken.getCard().getEdition(), "MB1");
assertTrue(cardToken.cardRequestHasNoCode());
// Requesting now what will be a Banned card later in this test // Requesting now what will be a Banned card later in this test
cardRequest = "Squandered Resources"; cardRequest = "Squandered Resources";
@@ -1768,6 +1803,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Squandered Resources"); assertEquals(cardToken.getCard().getName(), "Squandered Resources");
assertEquals(cardToken.getQuantity(), 1); assertEquals(cardToken.getQuantity(), 1);
assertEquals(cardToken.getCard().getEdition(), "VIS"); assertEquals(cardToken.getCard().getEdition(), "VIS");
assertTrue(cardToken.cardRequestHasNoCode());
// == ALLOWED SETS ONLY // == ALLOWED SETS ONLY
recognizer.setGameFormatConstraint(allowedSetCodes, null, null); recognizer.setGameFormatConstraint(allowedSetCodes, null, null);
@@ -1781,6 +1817,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker"); assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker");
assertEquals(cardToken.getQuantity(), 4); assertEquals(cardToken.getQuantity(), 4);
assertEquals(cardToken.getCard().getEdition(), "VIS"); assertEquals(cardToken.getCard().getEdition(), "VIS");
assertTrue(cardToken.cardRequestHasNoCode());
// == BANNED CARDS ONLY // == BANNED CARDS ONLY
recognizer.setGameFormatConstraint(null, bannedCards, null); recognizer.setGameFormatConstraint(null, bannedCards, null);
@@ -1794,6 +1831,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker"); assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker");
assertEquals(cardToken.getQuantity(), 4); assertEquals(cardToken.getQuantity(), 4);
assertEquals(cardToken.getCard().getEdition(), "MB1"); assertEquals(cardToken.getCard().getEdition(), "MB1");
assertTrue(cardToken.cardRequestHasNoCode());
cardRequest = "Squandered Resources"; cardRequest = "Squandered Resources";
cardToken = recognizer.recogniseCardToken(cardRequest, null); cardToken = recognizer.recogniseCardToken(cardRequest, null);
@@ -1806,6 +1844,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Squandered Resources"); assertEquals(cardToken.getCard().getName(), "Squandered Resources");
assertEquals(cardToken.getQuantity(), 1); assertEquals(cardToken.getQuantity(), 1);
assertEquals(cardToken.getCard().getEdition(), "VIS"); assertEquals(cardToken.getCard().getEdition(), "VIS");
assertTrue(cardToken.cardRequestHasNoCode());
// ALLOWED SET CODES AND RESTRICTED // ALLOWED SET CODES AND RESTRICTED
recognizer.setGameFormatConstraint(allowedSetCodes, null, restrictedCards); recognizer.setGameFormatConstraint(allowedSetCodes, null, restrictedCards);
@@ -1820,6 +1859,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker"); assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker");
assertEquals(cardToken.getQuantity(), 4); assertEquals(cardToken.getQuantity(), 4);
assertEquals(cardToken.getCard().getEdition(), "VIS"); assertEquals(cardToken.getCard().getEdition(), "VIS");
assertTrue(cardToken.cardRequestHasNoCode());
} }
/*====================================== /*======================================
@@ -1840,6 +1880,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
PaperCard tc = cardToken.getCard(); PaperCard tc = cardToken.getCard();
assertEquals(tc.getName(), "Lightning Dragon"); assertEquals(tc.getName(), "Lightning Dragon");
assertEquals(tc.getEdition(), "VMA"); assertEquals(tc.getEdition(), "VMA");
assertTrue(cardToken.cardRequestHasNoCode());
recognizer.setDateConstraint(2000, 0); // Jan 2000 recognizer.setDateConstraint(2000, 0); // Jan 2000
// Setting Fantasy Constructed Game Format: Urza's Block Format (no promo) // Setting Fantasy Constructed Game Format: Urza's Block Format (no promo)
@@ -1855,6 +1896,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
tc = cardToken.getCard(); tc = cardToken.getCard();
assertEquals(tc.getName(), "Lightning Dragon"); assertEquals(tc.getName(), "Lightning Dragon");
assertEquals(tc.getEdition(), "USG"); assertEquals(tc.getEdition(), "USG");
assertFalse(cardToken.cardRequestHasNoCode());
// Relaxing Constraint on Set // Relaxing Constraint on Set
lineRequest = "2x Lightning Dragon"; lineRequest = "2x Lightning Dragon";
@@ -1866,6 +1908,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
tc = cardToken.getCard(); tc = cardToken.getCard();
assertEquals(tc.getName(), "Lightning Dragon"); assertEquals(tc.getName(), "Lightning Dragon");
assertEquals(tc.getEdition(), "USG"); // the latest available within set requested assertEquals(tc.getEdition(), "USG"); // the latest available within set requested
assertTrue(cardToken.cardRequestHasNoCode());
// Now setting a tighter date constraint // Now setting a tighter date constraint
recognizer.setDateConstraint(1998, 0); // Jan 1998 recognizer.setDateConstraint(1998, 0); // Jan 1998
@@ -1875,6 +1918,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getType(), TokenType.CARD_FROM_INVALID_SET); assertEquals(cardToken.getType(), TokenType.CARD_FROM_INVALID_SET);
assertEquals(cardToken.getQuantity(), 2); assertEquals(cardToken.getQuantity(), 2);
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertFalse(cardToken.cardRequestHasNoCode());
lineRequest = "2x Lightning Dragon"; lineRequest = "2x Lightning Dragon";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1883,6 +1927,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getQuantity(), 2); assertEquals(cardToken.getQuantity(), 2);
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertEquals(cardToken.getText(), "Lightning Dragon [USG] #202"); assertEquals(cardToken.getText(), "Lightning Dragon [USG] #202");
assertTrue(cardToken.cardRequestHasNoCode());
// Now relaxing date constraint but removing USG from allowed sets // Now relaxing date constraint but removing USG from allowed sets
// VMA release date: 2014-06-16 // VMA release date: 2014-06-16
@@ -1895,6 +1940,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET); assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET);
assertEquals(cardToken.getQuantity(), 2); assertEquals(cardToken.getQuantity(), 2);
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertFalse(cardToken.cardRequestHasNoCode());
lineRequest = "2x Lightning Dragon"; lineRequest = "2x Lightning Dragon";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1902,6 +1948,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET); assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET);
assertEquals(cardToken.getQuantity(), 2); assertEquals(cardToken.getQuantity(), 2);
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertTrue(cardToken.cardRequestHasNoCode());
// Now relaxing date constraint but removing USG from allowed sets // Now relaxing date constraint but removing USG from allowed sets
// VMA release date: 2014-06-16 // VMA release date: 2014-06-16
@@ -1916,6 +1963,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
tc = cardToken.getCard(); tc = cardToken.getCard();
assertEquals(tc.getName(), "Lightning Dragon"); assertEquals(tc.getName(), "Lightning Dragon");
assertEquals(tc.getEdition(), "VMA"); assertEquals(tc.getEdition(), "VMA");
assertTrue(cardToken.cardRequestHasNoCode());
} }
@Test void testCardMatchWithDateANDdeckFormatConstraints(){ @Test void testCardMatchWithDateANDdeckFormatConstraints(){
@@ -1933,6 +1981,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
PaperCard tc = cardToken.getCard(); PaperCard tc = cardToken.getCard();
assertEquals(tc.getName(), "Flash"); assertEquals(tc.getName(), "Flash");
assertEquals(tc.getEdition(), "A25"); assertEquals(tc.getEdition(), "A25");
assertTrue(cardToken.cardRequestHasNoCode());
recognizer.setDateConstraint(2012, 0); // Jan 2012 recognizer.setDateConstraint(2012, 0); // Jan 2012
recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders); recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders);
@@ -1946,6 +1995,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getTokenSection(), DeckSection.Main); assertEquals(cardToken.getTokenSection(), DeckSection.Main);
assertNotNull(cardToken.getLimitedCardType()); assertNotNull(cardToken.getLimitedCardType());
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED); assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
assertTrue(cardToken.cardRequestHasNoCode());
lineRequest = "2x Cancel"; lineRequest = "2x Cancel";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1956,6 +2006,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
tc = cardToken.getCard(); tc = cardToken.getCard();
assertEquals(tc.getName(), "Cancel"); assertEquals(tc.getName(), "Cancel");
assertEquals(tc.getEdition(), "M12"); // the latest within date constraint assertEquals(tc.getEdition(), "M12"); // the latest within date constraint
assertTrue(cardToken.cardRequestHasNoCode());
lineRequest = "2x Cancel|M21"; lineRequest = "2x Cancel|M21";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -1964,6 +2015,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getQuantity(), 2); assertEquals(cardToken.getQuantity(), 2);
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertEquals(cardToken.getText(), "Cancel [M21] #46"); assertEquals(cardToken.getText(), "Cancel [M21] #46");
assertFalse(cardToken.cardRequestHasNoCode());
} }
@Test void testCardMatchWithGameANDdeckFormatConstraints(){ @Test void testCardMatchWithGameANDdeckFormatConstraints(){
@@ -1981,6 +2033,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
PaperCard tc = cardToken.getCard(); PaperCard tc = cardToken.getCard();
assertEquals(tc.getName(), "Flash"); assertEquals(tc.getName(), "Flash");
assertEquals(tc.getEdition(), "A25"); assertEquals(tc.getEdition(), "A25");
assertTrue(cardToken.cardRequestHasNoCode());
recognizer.setGameFormatConstraint(Arrays.asList("MIR", "VIS", "WTH"), null, null); recognizer.setGameFormatConstraint(Arrays.asList("MIR", "VIS", "WTH"), null, null);
recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders); recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders);
@@ -1993,6 +2046,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getTokenSection(), DeckSection.Main); assertEquals(cardToken.getTokenSection(), DeckSection.Main);
assertNotNull(cardToken.getLimitedCardType()); assertNotNull(cardToken.getLimitedCardType());
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED); assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
assertTrue(cardToken.cardRequestHasNoCode());
lineRequest = "2x Femeref Knight"; lineRequest = "2x Femeref Knight";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -2003,6 +2057,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
tc = cardToken.getCard(); tc = cardToken.getCard();
assertEquals(tc.getName(), "Femeref Knight"); assertEquals(tc.getName(), "Femeref Knight");
assertEquals(tc.getEdition(), "MIR"); assertEquals(tc.getEdition(), "MIR");
assertTrue(cardToken.cardRequestHasNoCode());
lineRequest = "2x Incinerate"; lineRequest = "2x Incinerate";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -2013,6 +2068,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
tc = cardToken.getCard(); tc = cardToken.getCard();
assertEquals(tc.getName(), "Incinerate"); assertEquals(tc.getName(), "Incinerate");
assertEquals(tc.getEdition(), "MIR"); assertEquals(tc.getEdition(), "MIR");
assertTrue(cardToken.cardRequestHasNoCode());
lineRequest = "Noble Elephant"; lineRequest = "Noble Elephant";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -2024,6 +2080,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getTokenSection(), DeckSection.Main); assertEquals(cardToken.getTokenSection(), DeckSection.Main);
assertNotNull(cardToken.getLimitedCardType()); assertNotNull(cardToken.getLimitedCardType());
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED); assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
assertTrue(cardToken.cardRequestHasNoCode());
lineRequest = "Incinerate|ICE"; lineRequest = "Incinerate|ICE";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -2032,6 +2089,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getQuantity(), 1); assertEquals(cardToken.getQuantity(), 1);
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertEquals(cardToken.getText(), "Incinerate [ICE] #194"); assertEquals(cardToken.getText(), "Incinerate [ICE] #194");
assertFalse(cardToken.cardRequestHasNoCode());
} }
@Test void testCardMatchWitDateANDgameANDdeckFormatConstraints(){ @Test void testCardMatchWitDateANDgameANDdeckFormatConstraints(){
@@ -2049,6 +2107,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
PaperCard tc = cardToken.getCard(); PaperCard tc = cardToken.getCard();
assertEquals(tc.getName(), "Flash"); assertEquals(tc.getName(), "Flash");
assertEquals(tc.getEdition(), "A25"); assertEquals(tc.getEdition(), "A25");
assertTrue(cardToken.cardRequestHasNoCode());
recognizer.setGameFormatConstraint(Arrays.asList("MIR", "VIS", "WTH"), null, null); recognizer.setGameFormatConstraint(Arrays.asList("MIR", "VIS", "WTH"), null, null);
recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders); recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders);
@@ -2063,6 +2122,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getTokenSection(), DeckSection.Main); assertEquals(cardToken.getTokenSection(), DeckSection.Main);
assertNotNull(cardToken.getLimitedCardType()); assertNotNull(cardToken.getLimitedCardType());
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED); assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
assertTrue(cardToken.cardRequestHasNoCode());
lineRequest = "Ardent Militia"; lineRequest = "Ardent Militia";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -2073,6 +2133,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getTokenSection(), DeckSection.Main); assertEquals(cardToken.getTokenSection(), DeckSection.Main);
assertNotNull(cardToken.getLimitedCardType()); assertNotNull(cardToken.getLimitedCardType());
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED); assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
assertTrue(cardToken.cardRequestHasNoCode());
lineRequest = "Buried Alive|UMA"; lineRequest = "Buried Alive|UMA";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -2080,6 +2141,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET); // illegal in game format assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET); // illegal in game format
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertEquals(cardToken.getText(), "Buried Alive [UMA] #88"); // within set constraints assertEquals(cardToken.getText(), "Buried Alive [UMA] #88"); // within set constraints
assertFalse(cardToken.cardRequestHasNoCode());
lineRequest = "Buried Alive"; lineRequest = "Buried Alive";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -2088,6 +2150,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertEquals(cardToken.getCard().getName(), "Buried Alive"); assertEquals(cardToken.getCard().getName(), "Buried Alive");
assertEquals(cardToken.getCard().getEdition(), "WTH"); // within set constraints assertEquals(cardToken.getCard().getEdition(), "WTH"); // within set constraints
assertTrue(cardToken.cardRequestHasNoCode());
recognizer.setDateConstraint(1997, 2); // March '97 - before WTH recognizer.setDateConstraint(1997, 2); // March '97 - before WTH
lineRequest = "Buried Alive"; lineRequest = "Buried Alive";
@@ -2096,6 +2159,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(cardToken.getType(), TokenType.CARD_FROM_INVALID_SET); assertEquals(cardToken.getType(), TokenType.CARD_FROM_INVALID_SET);
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertEquals(cardToken.getText(), "Buried Alive [WTH] #63"); assertEquals(cardToken.getText(), "Buried Alive [WTH] #63");
assertTrue(cardToken.cardRequestHasNoCode());
} }
/*================================== /*==================================
@@ -2166,6 +2230,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(aspectOfHydraCard.getName(), "Aspect of Hydra"); assertEquals(aspectOfHydraCard.getName(), "Aspect of Hydra");
assertEquals(aspectOfHydraCard.getEdition(), "BNG"); assertEquals(aspectOfHydraCard.getEdition(), "BNG");
assertTrue(aspectOfHydraCard.isFoil()); assertTrue(aspectOfHydraCard.isFoil());
assertFalse(cardToken.cardRequestHasNoCode());
lineRequest = "18 Forest <254> [THB] (F)"; lineRequest = "18 Forest <254> [THB] (F)";
cardToken = recognizer.recogniseCardToken(lineRequest, null); cardToken = recognizer.recogniseCardToken(lineRequest, null);
@@ -2177,6 +2242,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(forestCard.getName(), "Forest"); assertEquals(forestCard.getName(), "Forest");
assertEquals(forestCard.getEdition(), "THB"); assertEquals(forestCard.getEdition(), "THB");
assertTrue(forestCard.isFoil()); assertTrue(forestCard.isFoil());
assertFalse(cardToken.cardRequestHasNoCode());
} }
// === TappedOut Markdown Format // === TappedOut Markdown Format
@@ -2258,6 +2324,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(acCard.getName(), "Amoeboid Changeling"); assertEquals(acCard.getName(), "Amoeboid Changeling");
assertEquals(acCard.getEdition(), "LRW"); assertEquals(acCard.getEdition(), "LRW");
assertEquals(acCard.getCollectorNumber(), "51"); assertEquals(acCard.getCollectorNumber(), "51");
assertFalse(xmageCardToken.cardRequestHasNoCode());
} }
// === Deckstats Commander // === Deckstats Commander
@@ -2276,6 +2343,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(soCard.getName(), "Sliver Overlord"); assertEquals(soCard.getName(), "Sliver Overlord");
assertEquals(soCard.getEdition(), "SLD"); assertEquals(soCard.getEdition(), "SLD");
assertEquals(soCard.getCollectorNumber(), "10"); assertEquals(soCard.getCollectorNumber(), "10");
assertTrue(deckStatsToken.cardRequestHasNoCode());
// Check that deck section is made effective even if we're currently in Main // Check that deck section is made effective even if we're currently in Main
deckStatsToken = recognizer.recognizeLine(deckstatsCommanderRequest, DeckSection.Main); deckStatsToken = recognizer.recognizeLine(deckstatsCommanderRequest, DeckSection.Main);
@@ -2287,6 +2355,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertNotNull(deckStatsToken.getCard()); assertNotNull(deckStatsToken.getCard());
soCard = deckStatsToken.getCard(); soCard = deckStatsToken.getCard();
assertEquals(soCard.getName(), "Sliver Overlord"); assertEquals(soCard.getName(), "Sliver Overlord");
assertTrue(deckStatsToken.cardRequestHasNoCode());
} }
// === Double-Sided Cards // === Double-Sided Cards
@@ -2306,6 +2375,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
PaperCard leftSideCard = cardToken.getCard(); PaperCard leftSideCard = cardToken.getCard();
assertEquals(leftSideCard.getName(), leftSideRequest); assertEquals(leftSideCard.getName(), leftSideRequest);
assertEquals(cardToken.getQuantity(), 1); assertEquals(cardToken.getQuantity(), 1);
assertTrue(cardToken.cardRequestHasNoCode());
// Check Right side first // Check Right side first
cardToken = recognizer.recogniseCardToken(rightSideRequest, null); cardToken = recognizer.recogniseCardToken(rightSideRequest, null);
@@ -2316,6 +2386,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
PaperCard rightSideCard = cardToken.getCard(); PaperCard rightSideCard = cardToken.getCard();
assertEquals(rightSideCard.getName(), leftSideRequest); // NOTE: this is not a blunder! Back side will result in front side name assertEquals(rightSideCard.getName(), leftSideRequest); // NOTE: this is not a blunder! Back side will result in front side name
assertEquals(cardToken.getQuantity(), 1); assertEquals(cardToken.getQuantity(), 1);
assertTrue(cardToken.cardRequestHasNoCode());
// Check double side // Check double side
cardToken = recognizer.recogniseCardToken(doubleSideRequest, null); cardToken = recognizer.recogniseCardToken(doubleSideRequest, null);
@@ -2326,6 +2397,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
PaperCard doubleSideCard = cardToken.getCard(); PaperCard doubleSideCard = cardToken.getCard();
assertEquals(doubleSideCard.getName(), leftSideRequest); assertEquals(doubleSideCard.getName(), leftSideRequest);
assertEquals(cardToken.getQuantity(), 1); assertEquals(cardToken.getQuantity(), 1);
assertTrue(cardToken.cardRequestHasNoCode());
} }
/*================================= /*=================================
@@ -2928,6 +3000,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertNotNull(token.getCard()); assertNotNull(token.getCard());
assertNotNull(token.getTokenSection()); assertNotNull(token.getTokenSection());
assertEquals(token.getTokenSection(), DeckSection.Sideboard); assertEquals(token.getTokenSection(), DeckSection.Sideboard);
assertTrue(token.cardRequestHasNoCode());
lineRequest = "SB:Ancestral Recall"; lineRequest = "SB:Ancestral Recall";
token = recognizer.recognizeLine(lineRequest, null); token = recognizer.recognizeLine(lineRequest, null);
@@ -2937,6 +3010,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertNotNull(token.getCard()); assertNotNull(token.getCard());
assertNotNull(token.getTokenSection()); assertNotNull(token.getTokenSection());
assertEquals(token.getTokenSection(), DeckSection.Sideboard); assertEquals(token.getTokenSection(), DeckSection.Sideboard);
assertTrue(token.cardRequestHasNoCode());
lineRequest = "Ancestral Recall"; lineRequest = "Ancestral Recall";
token = recognizer.recognizeLine(lineRequest, null); token = recognizer.recognizeLine(lineRequest, null);
@@ -2944,6 +3018,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertEquals(token.getType(), TokenType.LEGAL_CARD); assertEquals(token.getType(), TokenType.LEGAL_CARD);
assertEquals(token.getText(), "Ancestral Recall [VMA] #1"); assertEquals(token.getText(), "Ancestral Recall [VMA] #1");
assertNotNull(token.getCard()); assertNotNull(token.getCard());
assertTrue(token.cardRequestHasNoCode());
lineRequest = "* 4 [Counterspell](http://tappedout.nethttp://tappedout.net/mtg-card/counterspell/)"; lineRequest = "* 4 [Counterspell](http://tappedout.nethttp://tappedout.net/mtg-card/counterspell/)";
token = recognizer.recognizeLine(lineRequest, null); token = recognizer.recognizeLine(lineRequest, null);
@@ -2952,6 +3027,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
assertNotNull(token.getText()); assertNotNull(token.getText());
assertNotNull(token.getCard()); assertNotNull(token.getCard());
assertEquals(token.getQuantity(), 4); assertEquals(token.getQuantity(), 4);
assertTrue(token.cardRequestHasNoCode());
lineRequest = "### Instant (14)"; lineRequest = "### Instant (14)";
token = recognizer.recognizeLine(lineRequest, null); token = recognizer.recognizeLine(lineRequest, null);
@@ -2993,6 +3069,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
Token cardToken = tokens.get(2); Token cardToken = tokens.get(2);
assertTrue(cardToken.isCardToken()); assertTrue(cardToken.isCardToken());
assertNotNull(cardToken.getCard()); assertNotNull(cardToken.getCard());
assertFalse(cardToken.cardRequestHasNoCode());
assertEquals(cardToken.getQuantity(), 4); assertEquals(cardToken.getQuantity(), 4);
assertEquals(cardToken.getCard().getName(), "Incinerate"); assertEquals(cardToken.getCard().getName(), "Incinerate");
assertEquals(cardToken.getCard().getEdition(), "ICE"); assertEquals(cardToken.getCard().getEdition(), "ICE");

View File

@@ -79,6 +79,7 @@ public class Forge implements ApplicationListener {
public static String CJK_Font = ""; public static String CJK_Font = "";
public static int hoveredCount = 0; public static int hoveredCount = 0;
public static boolean afterDBloaded = false; public static boolean afterDBloaded = false;
public static int mouseButtonID = 0;
public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0, boolean value, boolean androidOrientation, int totalRAM, boolean isTablet, int AndroidAPI, String AndroidRelease, String deviceName) { public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0, boolean value, boolean androidOrientation, int totalRAM, boolean isTablet, int AndroidAPI, String AndroidRelease, String deviceName) {
app = new Forge(); app = new Forge();
@@ -780,6 +781,7 @@ public class Forge implements ApplicationListener {
} }
} }
} }
mouseButtonID = button;
return super.touchDown(x, y, pointer, button); return super.touchDown(x, y, pointer, button);
} }

View File

@@ -21,10 +21,7 @@ import forge.screens.LoadingOverlay;
import forge.screens.match.MatchController; import forge.screens.match.MatchController;
import forge.screens.quest.QuestMenu; import forge.screens.quest.QuestMenu;
import forge.screens.settings.GuiDownloader; import forge.screens.settings.GuiDownloader;
import forge.sound.AudioClip; import forge.sound.*;
import forge.sound.AudioMusic;
import forge.sound.IAudioClip;
import forge.sound.IAudioMusic;
import forge.toolbox.FOptionPane; import forge.toolbox.FOptionPane;
import forge.toolbox.GuiChoose; import forge.toolbox.GuiChoose;
import forge.util.*; import forge.util.*;
@@ -272,7 +269,7 @@ public class GuiMobile implements IGuiBase {
@Override @Override
public IAudioClip createAudioClip(final String filename) { public IAudioClip createAudioClip(final String filename) {
return AudioClip.createClip(ForgeConstants.SOUND_DIR + filename); return AudioClip.createClip(SoundSystem.instance.getSoundDirectory() + filename);
} }
@Override @Override
@@ -288,6 +285,7 @@ public class GuiMobile implements IGuiBase {
@Override @Override
public void clearImageCache() { public void clearImageCache() {
ImageCache.clear(); ImageCache.clear();
ImageKeys.clearMissingCards();
} }
@Override @Override

View File

@@ -113,6 +113,7 @@ public class ImageCache {
public static void clear() { public static void clear() {
missingIconKeys.clear(); missingIconKeys.clear();
ImageKeys.clearMissingCards();
} }
public static void disposeTexture(){ public static void disposeTexture(){

View File

@@ -403,15 +403,26 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
addItem(new FMenuItem(localizer.getMessage("lblImportFromClipboard"), Forge.hdbuttons ? FSkinImage.HDIMPORT : FSkinImage.OPEN, new FEventHandler() { addItem(new FMenuItem(localizer.getMessage("lblImportFromClipboard"), Forge.hdbuttons ? FSkinImage.HDIMPORT : FSkinImage.OPEN, new FEventHandler() {
@Override @Override
public void handleEvent(FEvent e) { public void handleEvent(FEvent e) {
FDeckImportDialog dialog = new FDeckImportDialog(!deck.isEmpty(), editorType, new Callback<Deck>() { FDeckImportDialog dialog = new FDeckImportDialog(!deck.isEmpty(), editorType);
dialog.setCallback(new Callback<Deck>() {
@Override @Override
public void run(Deck importedDeck) { public void run(Deck importedDeck) {
getMainDeckPage().setCards(importedDeck.getMain()); if (deck != null && importedDeck.hasName()) {
if (getSideboardPage() != null) { deck.setName(importedDeck.getName());
getSideboardPage().setCards(importedDeck.getOrCreate(DeckSection.Sideboard)); lblName.setText(importedDeck.getName());
} }
if (getCommanderPage() != null) { if (dialog.createNewDeck()) {
getCommanderPage().setCards(importedDeck.getOrCreate(DeckSection.Commander)); getMainDeckPage().setCards(importedDeck.getMain());
if (getSideboardPage() != null)
getSideboardPage().setCards(importedDeck.getOrCreate(DeckSection.Sideboard));
if (getCommanderPage() != null)
getCommanderPage().setCards(importedDeck.getOrCreate(DeckSection.Commander));
} else {
getMainDeckPage().addCards(importedDeck.getMain());
if (getSideboardPage() != null)
getSideboardPage().addCards(importedDeck.getOrCreate(DeckSection.Sideboard));
if (getCommanderPage() != null)
getCommanderPage().addCards(importedDeck.getOrCreate(DeckSection.Commander));
} }
} }
}); });

View File

@@ -41,11 +41,14 @@ import forge.util.Localizer;
public class FDeckImportDialog extends FDialog { public class FDeckImportDialog extends FDialog {
private final Callback<Deck> callback; private Callback<Deck> callback;
private final FTextArea txtInput = add(new FTextArea(true)); private final FTextArea txtInput = add(new FTextArea(true));
private final FCheckBox newEditionCheck = add(new FCheckBox(Localizer.getInstance().getMessage("lblImportLatestVersionCard"), false)); private final FCheckBox newEditionCheck = add(new FCheckBox(Localizer.getInstance().getMessage("lblImportLatestVersionCard"), false));
private final FCheckBox dateTimeCheck = add(new FCheckBox(Localizer.getInstance().getMessage("lblUseOnlySetsReleasedBefore"), false)); private final FCheckBox dateTimeCheck = add(new FCheckBox(Localizer.getInstance().getMessage("lblUseOnlySetsReleasedBefore"), false));
private final FCheckBox smartCardArtCheck = add(new FCheckBox(Localizer.getInstance().getMessage("lblUseSmartCardArt"), false));
private final FCheckBox createNewDeckCheck = add(new FCheckBox(Localizer.getInstance().getMessage("lblNewDeckCheckbox"), false));
// private final FCheckBox importInDeck = add(new FCheckBox()
/*setting onlyCoreExpCheck to false allow the copied cards to pass the check of deck contents /*setting onlyCoreExpCheck to false allow the copied cards to pass the check of deck contents
forge-core\src\main\java\forge\deck\Deck.javaDeck.java starting @ Line 320 which is called by forge-core\src\main\java\forge\deck\Deck.javaDeck.java starting @ Line 320 which is called by
forge-gui-mobile\src\forge\deck\FDeckEditor.java starting @ Line 373 forge-gui-mobile\src\forge\deck\FDeckEditor.java starting @ Line 373
@@ -57,14 +60,14 @@ public class FDeckImportDialog extends FDialog {
private final FComboBox<Integer> yearDropdown = add(new FComboBox<>()); private final FComboBox<Integer> yearDropdown = add(new FComboBox<>());
private final boolean showOptions; private final boolean showOptions;
private final boolean currentDeckIsEmpty;
private boolean createNewDeckControl;
private final DeckImportController controller; private final DeckImportController controller;
private final static ImmutableList<String> importOrCancel = ImmutableList.of(Localizer.getInstance().getMessage("lblImport"), Localizer.getInstance().getMessage("lblCancel")); private final static ImmutableList<String> importOrCancel = ImmutableList.of(Localizer.getInstance().getMessage("lblImport"), Localizer.getInstance().getMessage("lblCancel"));
public FDeckImportDialog(final boolean replacingDeck, final FDeckEditor.EditorType editorType, final Callback<Deck> callback0) { public FDeckImportDialog(final boolean replacingDeck, final FDeckEditor.EditorType editorType) {
super(Localizer.getInstance().getMessage("lblImportFromClipboard"), 2); super(Localizer.getInstance().getMessage("lblImportFromClipboard"), 2);
callback = callback0;
controller = new DeckImportController(dateTimeCheck, monthDropdown, yearDropdown, replacingDeck); controller = new DeckImportController(dateTimeCheck, monthDropdown, yearDropdown, replacingDeck);
String contents = Forge.getClipboard().getContents(); String contents = Forge.getClipboard().getContents();
if (contents == null) if (contents == null)
@@ -84,6 +87,10 @@ public class FDeckImportDialog extends FDialog {
onlyCoreExpCheck.setSelected(StaticData.instance().isCoreExpansionOnlyFilterSet()); onlyCoreExpCheck.setSelected(StaticData.instance().isCoreExpansionOnlyFilterSet());
newEditionCheck.setSelected(StaticData.instance().cardArtPreferenceIsLatest()); newEditionCheck.setSelected(StaticData.instance().cardArtPreferenceIsLatest());
smartCardArtCheck.setSelected(StaticData.instance().isEnabledCardArtSmartSelection());
createNewDeckCheck.setSelected(replacingDeck);
this.currentDeckIsEmpty = !replacingDeck;
this.createNewDeckControl = replacingDeck;
initButton(0, Localizer.getInstance().getMessage("lblImport"), new FEventHandler() { initButton(0, Localizer.getInstance().getMessage("lblImport"), new FEventHandler() {
@Override @Override
@@ -93,21 +100,23 @@ public class FDeckImportDialog extends FDialog {
public void run() { public void run() {
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText()); //ensure deck updated based on any changes to options List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText()); //ensure deck updated based on any changes to options
if (controller.isSmartCardArtEnabled())
tokens = controller.optimiseCardArtInTokens();
//if there are any cards that cannot be imported, let user know this and give them the option to cancel //if there are any cards that cannot be imported, let user know this and give them the option to cancel
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (DeckRecognizer.Token token : tokens) { for (DeckRecognizer.Token token : tokens) {
if (TokenType.CARD_FROM_NOT_ALLOWED_SET.equals(token.getType()) if (token.getType() == TokenType.CARD_FROM_NOT_ALLOWED_SET
|| TokenType.CARD_FROM_INVALID_SET.equals(token.getType()) || token.getType() == TokenType.CARD_FROM_INVALID_SET
|| TokenType.UNKNOWN_CARD.equals(token.getType()) || token.getType() == TokenType.UNKNOWN_CARD
|| TokenType.UNSUPPORTED_CARD.equals(token.getType())) { || token.getType() == TokenType.UNSUPPORTED_CARD) {
if (sb.length() > 0) { if (sb.length() > 0)
sb.append("\n"); sb.append("\n");
}
sb.append(token.getQuantity()).append(" ").append(token.getText()); sb.append(token.getQuantity()).append(" ").append(token.getText());
} }
} }
if (sb.length() > 0) { if (sb.length() > 0) {
if (SOptionPane.showOptionDialog(Localizer.getInstance().getMessage("lblFollowingCardsCannotBeImported") + "\n\n" + sb.toString(), Localizer.getInstance().getMessage("lblImportRemainingCards"), SOptionPane.INFORMATION_ICON, importOrCancel) == 1) { if (SOptionPane.showOptionDialog(Localizer.getInstance().getMessage("lblFollowingCardsCannotBeImported") + "\n\n" + sb, Localizer.getInstance().getMessage("lblImportRemainingCards"), SOptionPane.INFORMATION_ICON, importOrCancel) == 1) {
return; return;
} }
} }
@@ -118,9 +127,9 @@ public class FDeckImportDialog extends FDialog {
FThreads.invokeInEdtLater(new Runnable() { FThreads.invokeInEdtLater(new Runnable() {
@Override @Override
public void run() { public void run() {
deck.optimizeMainCardArt();
hide(); hide();
callback.run(deck); if (callback != null)
callback.run(deck);
} }
}); });
} }
@@ -135,7 +144,8 @@ public class FDeckImportDialog extends FDialog {
}); });
List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText()); List<DeckRecognizer.Token> tokens = controller.parseInput(txtInput.getText());
StaticData data = StaticData.instance(); if (controller.isSmartCardArtEnabled())
tokens = controller.optimiseCardArtInTokens();
//ensure at least one known card found on clipboard //ensure at least one known card found on clipboard
for (DeckRecognizer.Token token : tokens) { for (DeckRecognizer.Token token : tokens) {
if (token.getType() == TokenType.LEGAL_CARD) { if (token.getType() == TokenType.LEGAL_CARD) {
@@ -155,6 +165,19 @@ public class FDeckImportDialog extends FDialog {
@Override @Override
public void handleEvent(FEvent e) {setArtPreferenceInController();} public void handleEvent(FEvent e) {setArtPreferenceInController();}
}); });
smartCardArtCheck.setCommand(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
controller.setSmartCardArtOptimisation(smartCardArtCheck.isSelected());
}
});
createNewDeckCheck.setCommand(new FEventHandler() {
@Override
public void handleEvent(FEvent e) {
createNewDeckControl = createNewDeckCheck.isSelected();
controller.setCreateNewDeck(createNewDeckControl);
}
});
updateDropDownEnabled(); updateDropDownEnabled();
setArtPreferenceInController(); setArtPreferenceInController();
return; return;
@@ -178,6 +201,12 @@ public class FDeckImportDialog extends FDialog {
yearDropdown.setEnabled(enabled); yearDropdown.setEnabled(enabled);
} }
public void setCallback(Callback<Deck> callback0){
callback = callback0;
}
public boolean createNewDeck(){ return this.createNewDeckControl; }
@Override @Override
public void drawOverlay(Graphics g) { public void drawOverlay(Graphics g) {
super.drawOverlay(g); super.drawOverlay(g);
@@ -198,7 +227,8 @@ public class FDeckImportDialog extends FDialog {
h = monthDropdown.getHeight(); h = monthDropdown.getHeight();
float fieldPadding = padding / 2; float fieldPadding = padding / 2;
newEditionCheck.setBounds(x, y, w, h); newEditionCheck.setBounds(x, y, w / 2, h);
onlyCoreExpCheck.setBounds(x + w/2, y, w/2, h);
y += h + fieldPadding; y += h + fieldPadding;
dateTimeCheck.setBounds(x, y, w, h); dateTimeCheck.setBounds(x, y, w, h);
y += h + fieldPadding; y += h + fieldPadding;
@@ -208,7 +238,12 @@ public class FDeckImportDialog extends FDialog {
yearDropdown.setBounds(x + dropDownWidth + fieldPadding, y, dropDownWidth, h); yearDropdown.setBounds(x + dropDownWidth + fieldPadding, y, dropDownWidth, h);
y += h + fieldPadding; y += h + fieldPadding;
onlyCoreExpCheck.setBounds(x, y, w, h); if (!this.currentDeckIsEmpty){
smartCardArtCheck.setBounds(x, y, w/2, h);
createNewDeckCheck.setBounds(x + w/2, y, w/2, h);
} else
smartCardArtCheck.setBounds(x, y, w, h);
y += h + 2 * padding; y += h + 2 * padding;
} }
h = txtInput.getPreferredHeight(w); h = txtInput.getPreferredHeight(w);

View File

@@ -113,7 +113,7 @@ public class NewGauntletScreen extends LaunchScreen {
}); });
} }
}); });
chooser.show(null, true); chooser.show(null, false); /*setting selectMax to true will select all available option*/
} }
}); });
} }

View File

@@ -6,8 +6,10 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import forge.Forge;
import forge.Graphics; import forge.Graphics;
import forge.card.CardRenderer.CardStackPosition; import forge.card.CardRenderer.CardStackPosition;
import forge.card.CardZoom; import forge.card.CardZoom;
@@ -324,7 +326,15 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
ThreadUtil.invokeInGameThread(new Runnable() { //must invoke in game thread in case a dialog needs to be shown ThreadUtil.invokeInGameThread(new Runnable() { //must invoke in game thread in case a dialog needs to be shown
@Override @Override
public void run() { public void run() {
if (!selectCard(false)) { if (GuiBase.getInterface().isRunningOnDesktop() && Forge.mouseButtonID == Input.Buttons.RIGHT) {
FThreads.invokeInEdtLater(new Runnable() {
@Override
public void run() {
showZoom();
}
});
return;
} else if (!selectCard(false)) {
//if no cards in stack can be selected, just show zoom/details for card //if no cards in stack can be selected, just show zoom/details for card
FThreads.invokeInEdtLater(new Runnable() { FThreads.invokeInEdtLater(new Runnable() {
@Override @Override

View File

@@ -18,6 +18,7 @@ import forge.screens.TabPageScreen;
import forge.screens.TabPageScreen.TabPage; import forge.screens.TabPageScreen.TabPage;
import forge.screens.home.HomeScreen; import forge.screens.home.HomeScreen;
import forge.screens.match.MatchController; import forge.screens.match.MatchController;
import forge.sound.MusicPlaylist;
import forge.sound.SoundSystem; import forge.sound.SoundSystem;
import forge.toolbox.FCheckBox; import forge.toolbox.FCheckBox;
import forge.toolbox.FGroupList; import forge.toolbox.FGroupList;
@@ -612,6 +613,29 @@ public class SettingsPage extends TabPage<SettingsScreen> {
localizer.getMessage("nlVibrateAfterLongPress")), localizer.getMessage("nlVibrateAfterLongPress")),
6); 6);
//Sound Options //Sound Options
lstSettings.addItem(new CustomSelectSetting(FPref.UI_CURRENT_SOUND_SET,
localizer.getMessage("cbpSoundSets"),
localizer.getMessage("nlpSoundSets"),
SoundSystem.instance.getAvailableSoundSets()) {
@Override
public void valueChanged(String newValue) {
super.valueChanged(newValue);
SoundSystem.instance.invalidateSoundCache();
}
},
7);
lstSettings.addItem(new CustomSelectSetting(FPref.UI_CURRENT_MUSIC_SET,
localizer.getMessage("cbpMusicSets"),
localizer.getMessage("nlpMusicSets"),
SoundSystem.getAvailableMusicSets()) {
@Override
public void valueChanged(String newValue) {
super.valueChanged(newValue);
MusicPlaylist.invalidateMusicPlaylist();
SoundSystem.instance.changeBackgroundTrack();
}
},
7);
lstSettings.addItem(new CustomSelectSetting(FPref.UI_VOL_SOUNDS, lstSettings.addItem(new CustomSelectSetting(FPref.UI_VOL_SOUNDS,
localizer.getMessage("cbAdjustSoundsVolume"), localizer.getMessage("cbAdjustSoundsVolume"),
localizer.getMessage("nlAdjustSoundsVolume"), localizer.getMessage("nlAdjustSoundsVolume"),

View File

@@ -1,6 +1,8 @@
#Add one announcement per line #Add one announcement per line
Get in the discord if you aren't yet. https://discord.gg/3v9JCVr Get in the discord if you aren't yet. https://discord.gg/3v9JCVr
The Throne of Eldraine quest world is the largest and most meticulously developed quest world to date, combining fascinating fairytales and interesting interactions. If you've never baked a pie into a pie, have you really played Magic? Forge now supports 100% of core and expansion set cards from Limited Edition Alpha to Innistrad: Midnight Hunt. This includes Equinox, Chaos Orb, and Falling Star.
Innistrad: Midnight Hunt (MID) and Innistrad: Midnight Hunt Commander (MIC) are 100% supported Planar Conquest now features a new plane - Forgotten Realms, based on the AFR and AFC sets.
Various improvements have been made to the desktop and mobile user interface to enhance the experience Several planes are now available in Planar Conquest in their Classic form, as originally intended by the author, without support for the newer sets and with the original events not modified with newer cards. These planes are available in addition to the contemporary versions of the planes and are marked as "Classic".
Sound sets are now configurable - you can place your sound sets under "sound" in your Forge cache (as subfolders) and then select them with the "Sound set" option.
Music sets are now configurable - you can place your music sets under "music" in your Forge cache (as subfolders) and then select them with the "Music set" option. The music set folder must have the same structure as "res/music", with "match" and "menus" subfolders containing relevant music tracks.
*** Android 7 & 8 support is now deprecated. Support will be dropped in an upcoming release. *** *** Android 7 & 8 support is now deprecated. Support will be dropped in an upcoming release. ***

View File

@@ -1,7 +1,6 @@
Name:Abrupt Decay Name:Abrupt Decay
ManaCost:B G ManaCost:B G
Types:Instant Types:Instant
K:CARDNAME can't be countered. K:This spell can't be countered.
A:SP$ Destroy | Cost$ B G | ValidTgts$ Permanent.nonLand+cmcLE3 | TgtPrompt$ Select target nonland permanent with mana value 3 or less | SpellDescription$ Destroy target nonland permanent with mana value 3 or less. A:SP$ Destroy | Cost$ B G | ValidTgts$ Permanent.nonLand+cmcLE3 | TgtPrompt$ Select target nonland permanent with mana value 3 or less | SpellDescription$ Destroy target nonland permanent with mana value 3 or less.
SVar:Picture:http://www.wizards.com/global/images/magic/general/abrupt_decay.jpg
Oracle:This spell can't be countered.\nDestroy target nonland permanent with mana value 3 or less. Oracle:This spell can't be countered.\nDestroy target nonland permanent with mana value 3 or less.

View File

@@ -5,8 +5,8 @@ PT:*/4
K:Vigilance K:Vigilance
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | Description$ CARDNAME's power is equal to the number of creatures you control. S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | Description$ CARDNAME's power is equal to the number of creatures you control.
SVar:X:Count$Valid Creature.YouCtrl SVar:X:Count$Valid Creature.YouCtrl
T:Mode$ AttackersDeclared | AttackingPlayer$ You | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control. T:Mode$ AttackersDeclared | AttackingPlayer$ You | Execute$ DBRepeat | TriggerZones$ Battlefield | TriggerDescription$ Whenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control.
SVar:TrigToken:DB$ Token | TokenAmount$ Y | TokenScript$ w_1_1_human | TokenTapped$ True | TokenAttacking$ True | ChoosePlayerOrPlaneswalker$ True | TokenOwner$ You SVar:DBRepeat:DB$ RepeatEach | RepeatPlayers$ Opponent | ChangeZoneTable$ True | RepeatSubAbility$ DBToken
SVar:Y:PlayerCountOpponents$Amount SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human | TokenTapped$ True | TokenAttacking$ Remembered | ChoosePlayerOrPlaneswalker$ True | TokenOwner$ You
DeckHas:Ability$Token DeckHas:Ability$Token
Oracle:Vigilance\nAdeline, Resplendent Cathar's power is equal to the number of creatures you control.\nWhenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control. Oracle:Vigilance\nAdeline, Resplendent Cathar's power is equal to the number of creatures you control.\nWhenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control.

View File

@@ -8,5 +8,5 @@ K:Protection from white
K:Protection from blue K:Protection from blue
A:AB$ Pump | Cost$ R | Defined$ Self | NumAtt$ 1 | SpellDescription$ CARDNAME gets +1/+0 until end of turn. A:AB$ Pump | Cost$ R | Defined$ Self | NumAtt$ 1 | SpellDescription$ CARDNAME gets +1/+0 until end of turn.
K:Morph:3 R R R K:Morph:3 R R R
K:CARDNAME can't be countered. K:This spell can't be countered.
Oracle:This spell can't be countered.\nFlying, trample, protection from white and from blue\n{R}: Akroma, Angel of Fury gets +1/+0 until end of turn.\nMorph {3}{R}{R}{R} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.) Oracle:This spell can't be countered.\nFlying, trample, protection from white and from blue\n{R}: Akroma, Angel of Fury gets +1/+0 until end of turn.\nMorph {3}{R}{R}{R} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.)

View File

@@ -2,11 +2,10 @@ Name:Altered Ego
ManaCost:X 2 G U ManaCost:X 2 G U
Types:Creature Shapeshifter Types:Creature Shapeshifter
PT:0/0 PT:0/0
K:CARDNAME can't be countered. K:This spell can't be countered.
K:ETBReplacement:Copy:DBCopy:Optional K:ETBReplacement:Copy:DBCopy:Optional
SVar:DBCopy:DB$ Clone | Choices$ Creature.Other | SubAbility$ DBAddCounter | SpellDescription$ You may have CARDNAME enter the battlefield as a copy of any creature on the battlefield, except it enters with X additional +1/+1 counters on it. SVar:DBCopy:DB$ Clone | Choices$ Creature.Other | SubAbility$ DBAddCounter | SpellDescription$ You may have CARDNAME enter the battlefield as a copy of any creature on the battlefield, except it enters with X additional +1/+1 counters on it.
SVar:DBAddCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | ETB$ True | CounterNum$ X SVar:DBAddCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | ETB$ True | CounterNum$ X
SVar:X:Count$xPaid SVar:X:Count$xPaid
DeckHas:Ability$Counter DeckHas:Ability$Counter
SVar:Picture:http://www.wizards.com/global/images/magic/general/altered_ego.jpg
Oracle:This spell can't be countered.\nYou may have Altered Ego enter the battlefield as a copy of any creature on the battlefield, except it enters with X additional +1/+1 counters on it. Oracle:This spell can't be countered.\nYou may have Altered Ego enter the battlefield as a copy of any creature on the battlefield, except it enters with X additional +1/+1 counters on it.

View File

@@ -3,6 +3,6 @@ ManaCost:X R
Types:Sorcery Types:Sorcery
A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | ConditionCheckSVar$ X | ConditionSVarCompare$ LT5 | SubAbility$ BanefulDmg | SpellDescription$ CARDNAME deals X damage to any target. A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | ConditionCheckSVar$ X | ConditionSVarCompare$ LT5 | SubAbility$ BanefulDmg | SpellDescription$ CARDNAME deals X damage to any target.
SVar:BanefulDmg:DB$ DealDamage | Defined$ Targeted | NumDmg$ X | NoPrevention$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GE5 | StackDescription$ If X is 5 or more, CARDNAME can't be countered by spells or abilities and the damage can't be prevented. SVar:BanefulDmg:DB$ DealDamage | Defined$ Targeted | NumDmg$ X | NoPrevention$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GE5 | StackDescription$ If X is 5 or more, CARDNAME can't be countered by spells or abilities and the damage can't be prevented.
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | AddHiddenKeyword$ CARDNAME can't be countered. | CheckSVar$ X | SVarCompare$ GE5 | Description$ If X is 5 or more, CARDNAME can't be countered by spells or abilities and the damage can't be prevented. S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | AddHiddenKeyword$ This spell can't be countered. | CheckSVar$ X | SVarCompare$ GE5 | Description$ If X is 5 or more, CARDNAME can't be countered by spells or abilities and the damage can't be prevented.
SVar:X:Count$xPaid SVar:X:Count$xPaid
Oracle:Banefire deals X damage to any target.\nIf X is 5 or more, this spell can't be countered and the damage can't be prevented. Oracle:Banefire deals X damage to any target.\nIf X is 5 or more, this spell can't be countered and the damage can't be prevented.

View File

@@ -4,6 +4,6 @@ Types:Creature Cat Cleric
PT:2/3 PT:2/3
K:Lifelink K:Lifelink
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounters | TriggerDescription$ When CARDNAME enters the battlefield, put a +1/+1 counter on each of up to two other target creatures you control. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounters | TriggerDescription$ When CARDNAME enters the battlefield, put a +1/+1 counter on each of up to two other target creatures you control.
SVar:TrigPutCounters:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature.YouCtrl+Other | TgtPrompt$ Select target creature you control SVar:TrigPutCounters:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature.YouCtrl+Other | TgtPrompt$ Select up to two other target creatures you control
DeckHas:Ability$LifeGain & Ability$Counters DeckHas:Ability$LifeGain & Ability$Counters
Oracle:Lifelink (Damage dealt by this creature also causes you to gain that much life.)\nWhen Basri's Acolyte enters the battlefield, put a +1/+1 counter on each of up to two other target creatures you control. Oracle:Lifelink (Damage dealt by this creature also causes you to gain that much life.)\nWhen Basri's Acolyte enters the battlefield, put a +1/+1 counter on each of up to two other target creatures you control.

View File

@@ -2,7 +2,6 @@ Name:Blurred Mongoose
ManaCost:1 G ManaCost:1 G
Types:Creature Mongoose Types:Creature Mongoose
PT:2/1 PT:2/1
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Shroud K:Shroud
SVar:Picture:http://www.wizards.com/global/images/magic/general/blurred_mongoose.jpg
Oracle:This spell can't be countered.\nShroud (This creature can't be the target of spells or abilities.) Oracle:This spell can't be countered.\nShroud (This creature can't be the target of spells or abilities.)

View File

@@ -2,8 +2,7 @@ Name:Carnage Tyrant
ManaCost:4 G G ManaCost:4 G G
Types:Creature Dinosaur Types:Creature Dinosaur
PT:7/6 PT:7/6
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Trample K:Trample
K:Hexproof K:Hexproof
SVar:Picture:http://www.wizards.com/global/images/magic/general/carnage_tyrant.jpg
Oracle:This spell can't be countered.\nTrample, hexproof Oracle:This spell can't be countered.\nTrample, hexproof

View File

@@ -2,7 +2,7 @@ Name:Chandra, Awakened Inferno
Types:Legendary Planeswalker Chandra Types:Legendary Planeswalker Chandra
ManaCost:4 R R ManaCost:4 R R
Loyalty:6 Loyalty:6
K:CARDNAME can't be countered. K:This spell can't be countered.
A:AB$ Effect | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | EffectOwner$ Player.Opponent | Name$ Emblem - Chandra, Awakened Inferno | Triggers$ BOTTrig | Duration$ Permanent | AILogic$ Always | SpellDescription$ Each opponent gets an emblem with "At the beginning of your upkeep, this emblem deals 1 damage to you." A:AB$ Effect | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | EffectOwner$ Player.Opponent | Name$ Emblem - Chandra, Awakened Inferno | Triggers$ BOTTrig | Duration$ Permanent | AILogic$ Always | SpellDescription$ Each opponent gets an emblem with "At the beginning of your upkeep, this emblem deals 1 damage to you."
SVar:BOTTrig:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ ChandraDmg | TriggerDescription$ At the beginning of your upkeep, this emblem deals 1 damage to you. SVar:BOTTrig:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ ChandraDmg | TriggerDescription$ At the beginning of your upkeep, this emblem deals 1 damage to you.
SVar:ChandraDmg:DB$ DealDamage | Defined$ TriggeredPlayer | NumDmg$ 1 SVar:ChandraDmg:DB$ DealDamage | Defined$ TriggeredPlayer | NumDmg$ 1

View File

@@ -4,6 +4,6 @@ Types:Legendary Creature Elder Dragon
PT:7/7 PT:7/7
K:Flash K:Flash
K:Flying K:Flying
K:CARDNAME can't be countered. K:This spell can't be countered.
A:AB$ Animate | Cost$ Discard<1/Card> | Types$ Human | Power$ 1 | Toughness$ 1 | Keywords$ Hexproof | HiddenKeywords$ Unblockable | RemoveAllAbilities$ True | RemoveCreatureTypes$ True | SpellDescription$ Until end of turn, CARDNAME becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. A:AB$ Animate | Cost$ Discard<1/Card> | Types$ Human | Power$ 1 | Toughness$ 1 | Keywords$ Hexproof | HiddenKeywords$ Unblockable | RemoveAllAbilities$ True | RemoveCreatureTypes$ True | SpellDescription$ Until end of turn, CARDNAME becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn.
Oracle:Flash\nThis spell can't be countered.\nFlying\nDiscard a card: Until end of turn, Chromium, the Mutable becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. Oracle:Flash\nThis spell can't be countered.\nFlying\nDiscard a card: Until end of turn, Chromium, the Mutable becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn.

View File

@@ -1,8 +1,7 @@
Name:Combust Name:Combust
ManaCost:1 R ManaCost:1 R
Types:Instant Types:Instant
K:CARDNAME can't be countered. K:This spell can't be countered.
A:SP$ DealDamage | Cost$ 1 R | ValidTgts$ Creature.White,Creature.Blue | NumDmg$ 5 | NoPrevention$ True | TgtPrompt$ Select target white or blue creature. | SpellDescription$ CARDNAME deals 5 damage to target white or blue creature. The damage can't be prevented. A:SP$ DealDamage | Cost$ 1 R | ValidTgts$ Creature.White,Creature.Blue | NumDmg$ 5 | NoPrevention$ True | TgtPrompt$ Select target white or blue creature. | SpellDescription$ CARDNAME deals 5 damage to target white or blue creature. The damage can't be prevented.
AI:RemoveDeck:Random AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/combust.jpg
Oracle:This spell can't be countered.\nCombust deals 5 damage to target white or blue creature. The damage can't be prevented. Oracle:This spell can't be countered.\nCombust deals 5 damage to target white or blue creature. The damage can't be prevented.

View File

@@ -1,7 +1,7 @@
Name:Commence the Endgame Name:Commence the Endgame
ManaCost:4 U U ManaCost:4 U U
Types:Instant Types:Instant
K:CARDNAME can't be countered. K:This spell can't be countered.
A:SP$ Draw | Cost$ 4 U U | NumCards$ 2 | SpellDescription$ Draw two cards, then amass X, where X is the number of cards in your hand. (Put X +1/+1 counters on an Army you control. If you don't control one, create a 0/0 black Zombie Army creature token first.) | SubAbility$ DBAmass A:SP$ Draw | Cost$ 4 U U | NumCards$ 2 | SpellDescription$ Draw two cards, then amass X, where X is the number of cards in your hand. (Put X +1/+1 counters on an Army you control. If you don't control one, create a 0/0 black Zombie Army creature token first.) | SubAbility$ DBAmass
SVar:DBAmass:DB$ Amass | Num$ X SVar:DBAmass:DB$ Amass | Num$ X
DeckHints:Ability$Amass & Type$Zombie DeckHints:Ability$Amass & Type$Zombie

View File

@@ -5,5 +5,5 @@ A:SP$ DealDamage | Cost$ X X R | ValidTgts$ Creature,Player,Planeswalker | TgtPr
SVar:MaxTgts:PlayerCountPlayers$Amount/Plus.NumCreatures SVar:MaxTgts:PlayerCountPlayers$Amount/Plus.NumCreatures
SVar:NumCreatures:Count$Valid Creature,Planeswalker SVar:NumCreatures:Count$Valid Creature,Planeswalker
SVar:X:Count$xPaid SVar:X:Count$xPaid
K:Flashback:R R Discard<X/Card/card> K:Flashback:R R Discard<X/Card/cards>
Oracle:Conflagrate deals X damage divided as you choose among any number of targets.\nFlashback—{R}{R}, Discard X cards. (You may cast this card from your graveyard for its flashback cost. Then exile it.) Oracle:Conflagrate deals X damage divided as you choose among any number of targets.\nFlashback—{R}{R}, Discard X cards. (You may cast this card from your graveyard for its flashback cost. Then exile it.)

View File

@@ -2,7 +2,7 @@ Name:Control Win Condition
ManaCost:4 U U ManaCost:4 U U
Types:Creature Whale Types:Creature Whale
PT:*/* PT:*/*
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Shroud K:Shroud
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of turns youve taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.) S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of turns youve taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.)
SVar:X:Count$YourTurns SVar:X:Count$YourTurns

View File

@@ -1,8 +1,7 @@
Name:Counterflux Name:Counterflux
ManaCost:U U R ManaCost:U U R
Types:Instant Types:Instant
K:CARDNAME can't be countered. K:This spell can't be countered.
A:SP$ Counter | Cost$ U U R | TargetType$ Spell | TgtPrompt$ Select target spell you don't control. | ValidTgts$ Card.YouDontCtrl | SpellDescription$ Counter target spell you don't control. A:SP$ Counter | Cost$ U U R | TargetType$ Spell | TgtPrompt$ Select target spell you don't control. | ValidTgts$ Card.YouDontCtrl | SpellDescription$ Counter target spell you don't control.
A:SP$ Counter | Cost$ 1 U U R | AllType$ Spell | AllValid$ Card.YouDontCtrl | PrecostDesc$ Overload | CostDesc$ {1}{U}{U}{R} | NonBasicSpell$ True | SpellDescription$ (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.") | StackDescription$ Counter each spell you don't control. A:SP$ Counter | Cost$ 1 U U R | AllType$ Spell | AllValid$ Card.YouDontCtrl | PrecostDesc$ Overload | CostDesc$ {1}{U}{U}{R} | NonBasicSpell$ True | SpellDescription$ (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.") | StackDescription$ Counter each spell you don't control.
SVar:Picture:http://www.wizards.com/global/images/magic/general/counterflux.jpg
Oracle:This spell can't be countered.\nCounter target spell you don't control.\nOverload {1}{U}{U}{R} (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.") Oracle:This spell can't be countered.\nCounter target spell you don't control.\nOverload {1}{U}{U}{R} (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.")

View File

@@ -3,7 +3,7 @@ ManaCost:5 G G
Types:Creature Beast Types:Creature Beast
PT:6/6 PT:6/6
K:Kicker:2 G K:Kicker:2 G
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Hexproof K:Hexproof
K:Haste K:Haste
K:etbCounter:P1P1:4:CheckSVar$ WasKicked:If CARDNAME was kicked, it enters the battlefield with four +1/+1 counters on it. K:etbCounter:P1P1:4:CheckSVar$ WasKicked:If CARDNAME was kicked, it enters the battlefield with four +1/+1 counters on it.

View File

@@ -4,7 +4,7 @@ Types:Sorcery
A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | ConditionCheckSVar$ Y | ConditionSVarCompare$ GE1 | RememberDamaged$ True | ReplaceDyingDefined$ Remembered | SubAbility$ DBDemonfire | SpellDescription$ CARDNAME deals X damage to any target. If a creature dealt damage this way would die this turn, exile it instead. A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | ConditionCheckSVar$ Y | ConditionSVarCompare$ GE1 | RememberDamaged$ True | ReplaceDyingDefined$ Remembered | SubAbility$ DBDemonfire | SpellDescription$ CARDNAME deals X damage to any target. If a creature dealt damage this way would die this turn, exile it instead.
SVar:DBDemonfire:DB$ DealDamage | Defined$ Targeted | NumDmg$ X | NoPrevention$ True | ConditionCheckSVar$ Y | ConditionSVarCompare$ EQ0 | RememberDamaged$ True | ReplaceDyingDefined$ Remembered | SubAbility$ DBCleanup SVar:DBDemonfire:DB$ DealDamage | Defined$ Targeted | NumDmg$ X | NoPrevention$ True | ConditionCheckSVar$ Y | ConditionSVarCompare$ EQ0 | RememberDamaged$ True | ReplaceDyingDefined$ Remembered | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | AddHiddenKeyword$ CARDNAME can't be countered. | CheckSVar$ Y | SVarCompare$ EQ0 | Description$ Hellbent — If you have no cards in hand, this spell can't be countered and the damage can't be prevented. S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | AddHiddenKeyword$ This spell can't be countered. | CheckSVar$ Y | SVarCompare$ EQ0 | Description$ Hellbent — If you have no cards in hand, this spell can't be countered and the damage can't be prevented.
SVar:X:Count$xPaid SVar:X:Count$xPaid
SVar:Y:Count$InYourHand SVar:Y:Count$InYourHand
Oracle:Demonfire deals X damage to any target. If a creature dealt damage this way would die this turn, exile it instead.\nHellbent — If you have no cards in hand, this spell can't be countered and the damage can't be prevented. Oracle:Demonfire deals X damage to any target. If a creature dealt damage this way would die this turn, exile it instead.\nHellbent — If you have no cards in hand, this spell can't be countered and the damage can't be prevented.

View File

@@ -1,8 +1,7 @@
Name:Demonic Tutor Name:Demonic Tutor
ManaCost:1 B ManaCost:1 B
Types:Sorcery Types:Sorcery
A:SP$ ChangeZone | Cost$ 1 B | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SpellDescription$ Search your library for a card, put that card into your hand, then shuffle. A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | StackDescription$ SpellDescription | SpellDescription$ Search your library for a card, put that card into your hand, then shuffle.
#TODO: Improve the tutoring logic for the AI. Currently will generally look for the most expensive castable thing in the library (which can, of course, be used to advantage in properly constructed AI decks). #TODO: Improve the tutoring logic for the AI. Currently will generally look for the most expensive castable thing in the library (which can, of course, be used to advantage in properly constructed AI decks).
AI:RemoveDeck:Random AI:RemoveDeck:Random
SVar:Picture:http://resources.wizards.com/magic/cards/3e/en-us/card1155.jpg
Oracle:Search your library for a card, put that card into your hand, then shuffle. Oracle:Search your library for a card, put that card into your hand, then shuffle.

View File

@@ -3,7 +3,7 @@ ManaCost:G
Types:Creature Human Druid Types:Creature Human Druid
PT:1/1 PT:1/1
A:AB$ ChangeZone | Cost$ 1 G Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. A:AB$ ChangeZone | Cost$ 1 G Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.
S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Graveyard | AddHiddenKeyword$ CARDNAME count as Muscle Burst. | Description$ If CARDNAME is in a graveyard, effects from spells named Muscle Burst count it as a card named Muscle Burst. S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Graveyard | AffectedZone$ Graveyard | AddHiddenKeyword$ CARDNAME count as Muscle Burst. | Description$ If CARDNAME is in a graveyard, effects from spells named Muscle Burst count it as a card named Muscle Burst.
DeckHints:Name$Diligent Farmhand|Muscle Burst DeckHints:Name$Diligent Farmhand|Muscle Burst
SVar:Picture:http://www.wizards.com/global/images/magic/general/diligent_farmhand.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/diligent_farmhand.jpg
Oracle:{1}{G}, Sacrifice Diligent Farmhand: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.\nIf Diligent Farmhand is in a graveyard, effects from spells named Muscle Burst count it as a card named Muscle Burst. Oracle:{1}{G}, Sacrifice Diligent Farmhand: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.\nIf Diligent Farmhand is in a graveyard, effects from spells named Muscle Burst count it as a card named Muscle Burst.

View File

@@ -1,6 +1,6 @@
Name:Dovin's Veto Name:Dovin's Veto
ManaCost:W U ManaCost:W U
Types:Instant Types:Instant
K:CARDNAME can't be countered. K:This spell can't be countered.
A:SP$ Counter | Cost$ W U | TargetType$ Spell | TgtPrompt$ Select target noncreature Spell | ValidTgts$ Card.nonCreature | SpellDescription$ Counter target noncreature spell. A:SP$ Counter | Cost$ W U | TargetType$ Spell | TgtPrompt$ Select target noncreature Spell | ValidTgts$ Card.nonCreature | SpellDescription$ Counter target noncreature spell.
Oracle:This spell can't be countered.\nCounter target noncreature spell. Oracle:This spell can't be countered.\nCounter target noncreature spell.

View File

@@ -2,7 +2,7 @@ Name:Dragonlord Dromoka
ManaCost:4 G W ManaCost:4 G W
Types:Legendary Creature Elder Dragon Types:Legendary Creature Elder Dragon
PT:5/7 PT:5/7
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Flying K:Flying
K:Lifelink K:Lifelink
S:Mode$ CantBeCast | ValidCard$ Card | Condition$ PlayerTurn | Caster$ Opponent | Description$ Your opponents can't cast spells during your turn. S:Mode$ CantBeCast | ValidCard$ Card | Condition$ PlayerTurn | Caster$ Opponent | Description$ Your opponents can't cast spells during your turn.

View File

@@ -2,7 +2,7 @@ Name:Dragonlord's Prerogative
ManaCost:4 U U ManaCost:4 U U
Types:Instant Types:Instant
K:Presence:Dragon K:Presence:Dragon
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | AddHiddenKeyword$ CARDNAME can't be countered. | Presence$ Dragon | Description$ If you revealed a Dragon card or controlled a Dragon as you cast this spell, this spell can't be countered. S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | AddHiddenKeyword$ This spell can't be countered. | Presence$ Dragon | Description$ If you revealed a Dragon card or controlled a Dragon as you cast this spell, this spell can't be countered.
A:SP$ Draw | Cost$ 4 U U | NumCards$ 4 | SpellDescription$ Draw four cards. A:SP$ Draw | Cost$ 4 U U | NumCards$ 4 | SpellDescription$ Draw four cards.
DeckHints:Type$Dragon DeckHints:Type$Dragon
Oracle:As an additional cost to cast this spell, you may reveal a Dragon card from your hand.\nIf you revealed a Dragon card or controlled a Dragon as you cast this spell, this spell can't be countered.\nDraw four cards. Oracle:As an additional cost to cast this spell, you may reveal a Dragon card from your hand.\nIf you revealed a Dragon card or controlled a Dragon as you cast this spell, this spell can't be countered.\nDraw four cards.

View File

@@ -4,6 +4,6 @@ Types:Artifact
K:CARDNAME doesn't untap during your untap step. K:CARDNAME doesn't untap during your untap step.
A:AB$ DealDamage | Cost$ 4 T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to any target. A:AB$ DealDamage | Cost$ 4 T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to any target.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ At the beginning of your upkeep, you may discard a card. If you do, untap CARDNAME. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ At the beginning of your upkeep, you may discard a card. If you do, untap CARDNAME.
SVar:TrigUntap:AB$Untap | Cost$ Discard<1/Card> | Defined$ Self SVar:TrigUntap:AB$ Untap | Cost$ Discard<1/Card> | Defined$ Self
SVar:Picture:http://www.wizards.com/global/images/magic/general/elaborate_firecannon.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/elaborate_firecannon.jpg
Oracle:Elaborate Firecannon doesn't untap during your untap step.\n{4}, {T}: Elaborate Firecannon deals 2 damage to any target.\nAt the beginning of your upkeep, you may discard a card. If you do, untap Elaborate Firecannon. Oracle:Elaborate Firecannon doesn't untap during your untap step.\n{4}, {T}: Elaborate Firecannon deals 2 damage to any target.\nAt the beginning of your upkeep, you may discard a card. If you do, untap Elaborate Firecannon.

View File

@@ -2,7 +2,7 @@ Name:Emrakul, the Aeons Torn
ManaCost:15 ManaCost:15
Types:Legendary Creature Eldrazi Types:Legendary Creature Eldrazi
PT:15/15 PT:15/15
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Flying K:Flying
K:Protection:Spell.nonColorless:Protection from colored spells K:Protection:Spell.nonColorless:Protection from colored spells
K:Annihilator:6 K:Annihilator:6

View File

@@ -2,6 +2,6 @@ Name:Exquisite Firecraft
ManaCost:1 R R ManaCost:1 R R
Types:Sorcery Types:Sorcery
A:SP$ DealDamage | Cost$ 1 R R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to any target. A:SP$ DealDamage | Cost$ 1 R R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to any target.
S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | AddHiddenKeyword$ CARDNAME can't be countered. | CheckSVar$ X | SVarCompare$ GE2 | Description$ Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, this spell can't be countered. S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | AddHiddenKeyword$ This spell can't be countered. | CheckSVar$ X | SVarCompare$ GE2 | Description$ Spell mastery — If there are two or more instant and/or sorcery cards in your graveyard, this spell can't be countered.
SVar:X:Count$ValidGraveyard Instant.YouOwn,Sorcery.YouOwn SVar:X:Count$ValidGraveyard Instant.YouOwn,Sorcery.YouOwn
Oracle:Exquisite Firecraft deals 4 damage to any target.\nSpell mastery — If there are two or more instant and/or sorcery cards in your graveyard, this spell can't be countered. Oracle:Exquisite Firecraft deals 4 damage to any target.\nSpell mastery — If there are two or more instant and/or sorcery cards in your graveyard, this spell can't be countered.

View File

@@ -4,7 +4,7 @@ Types:Creature Cat Beast
PT:4/6 PT:4/6
K:Vigilance K:Vigilance
K:Lifelink K:Lifelink
T:Mode$Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | LifeTotal$ You | LifeAmount$ GE40 | Execute$ TrigWin | TriggerDescription$ At the beginning of your upkeep, if you have 40 or more life, you win the game. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | LifeTotal$ You | LifeAmount$ GE40 | Execute$ TrigWin | TriggerDescription$ At the beginning of your upkeep, if you have 40 or more life, you win the game.
SVar:TrigWin:DB$ WinsGame | Defined$ You SVar:TrigWin:DB$ WinsGame | Defined$ You
DeckHints:Ability$LifeGain DeckHints:Ability$LifeGain
SVar:Picture:http://www.wizards.com/global/images/magic/general/felidar_sovereign.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/felidar_sovereign.jpg

View File

@@ -2,8 +2,7 @@ Name:Followed Footsteps
ManaCost:3 U U ManaCost:3 U U
Types:Enchantment Aura Types:Enchantment Aura
K:Enchant creature K:Enchant creature
A:SP$ Attach | Cost$ 3 U U | ValidTgts$ Creature | AILogic$ Curse A:SP$ Attach | ValidTgts$ Creature | AILogic$ HighestEvaluation
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigCopy | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, create a token that's a copy of enchanted creature. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigCopy | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, create a token that's a copy of enchanted creature.
SVar:TrigCopy:DB$ CopyPermanent | Defined$ Enchanted | SpellDescription$ At the beginning of your upkeep, put a token that's a copy of enchanted creature onto the battlefield. SVar:TrigCopy:DB$ CopyPermanent | Defined$ Enchanted | SpellDescription$ At the beginning of your upkeep, create a token that's a copy of enchanted creature.
SVar:Picture:http://www.wizards.com/global/images/magic/general/followed_footsteps.jpg
Oracle:Enchant creature\nAt the beginning of your upkeep, create a token that's a copy of enchanted creature. Oracle:Enchant creature\nAt the beginning of your upkeep, create a token that's a copy of enchanted creature.

View File

@@ -1,7 +1,7 @@
Name:Fry Name:Fry
ManaCost:1 R ManaCost:1 R
Types:Instant Types:Instant
K:CARDNAME can't be countered. K:This spell can't be countered.
A:SP$ DealDamage | Cost$ 1 R | ValidTgts$ Creature.White,Planeswalker.White,Creature.Blue,Planeswalker.Blue | TgtPrompt$ Select target creature or planeswalker that's white or blue | NumDmg$ 5 | SpellDescription$ CARDNAME deals 5 damage to target creature or planeswalker that's white or blue. A:SP$ DealDamage | Cost$ 1 R | ValidTgts$ Creature.White,Planeswalker.White,Creature.Blue,Planeswalker.Blue | TgtPrompt$ Select target creature or planeswalker that's white or blue | NumDmg$ 5 | SpellDescription$ CARDNAME deals 5 damage to target creature or planeswalker that's white or blue.
AI:RemoveDeck:Random AI:RemoveDeck:Random
Oracle:This spell can't be countered.\nFry deals 5 damage to target creature or planeswalker that's white or blue. Oracle:This spell can't be countered.\nFry deals 5 damage to target creature or planeswalker that's white or blue.

View File

@@ -2,8 +2,7 @@ Name:Gaea's Revenge
ManaCost:5 G G ManaCost:5 G G
Types:Creature Elemental Types:Creature Elemental
PT:8/5 PT:8/5
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Haste K:Haste
S:Mode$ CantTarget | ValidCard$ Card.Self | ValidSource$ Card.nonGreen | Description$ CARDNAME can't be the target of nongreen spells or abilities from nongreen sources. S:Mode$ CantTarget | ValidCard$ Card.Self | ValidSource$ Card.nonGreen | Description$ CARDNAME can't be the target of nongreen spells or abilities from nongreen sources.
SVar:Picture:http://www.wizards.com/global/images/magic/general/gaeas_revenge.jpg
Oracle:This spell can't be countered.\nHaste\nGaea's Revenge can't be the target of nongreen spells or abilities from nongreen sources. Oracle:This spell can't be countered.\nHaste\nGaea's Revenge can't be the target of nongreen spells or abilities from nongreen sources.

View File

@@ -1,7 +1,7 @@
Name:Giant Opportunity Name:Giant Opportunity
ManaCost:2 G ManaCost:2 G
Types:Sorcery Types:Sorcery
A:SP$ Sacrifice | Cost$ 2 G | SacValid$ Food | Defined$ You | Amount$ 2 | OptionalSacrifice$ True | StrictAmount$ True | RememberSacrificed$ True | SubAbility$ DBToken | SpellDescription$ You may sacrifice two Foods. If you do, create a 7/7 green Giant creature token. Otherwise, create three Food tokens. (They're artifacts with "{2}, {T}, Sacrifice this artifact: You gain 3 life.") A:SP$ Sacrifice | Cost$ 2 G | SacValid$ Food | Defined$ You | Amount$ 2 | Optional$ True | StrictAmount$ True | RememberSacrificed$ True | SubAbility$ DBToken | SpellDescription$ You may sacrifice two Foods. If you do, create a 7/7 green Giant creature token. Otherwise, create three Food tokens. (They're artifacts with "{2}, {T}, Sacrifice this artifact: You gain 3 life.")
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_7_7_giant | TokenOwner$ You | ConditionDefined$ RememberedLKI | ConditionPresent$ Food | ConditionCompare$ EQ2 | SubAbility$ DBToken2 SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_7_7_giant | TokenOwner$ You | ConditionDefined$ RememberedLKI | ConditionPresent$ Food | ConditionCompare$ EQ2 | SubAbility$ DBToken2
SVar:DBToken2:DB$ Token | TokenAmount$ 3 | TokenScript$ c_a_food_sac | TokenOwner$ You | ConditionDefined$ RememberedLKI | ConditionPresent$ Food | ConditionCompare$ EQ0 | SubAbility$ DBCleanup SVar:DBToken2:DB$ Token | TokenAmount$ 3 | TokenScript$ c_a_food_sac | TokenOwner$ You | ConditionDefined$ RememberedLKI | ConditionPresent$ Food | ConditionCompare$ EQ0 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True

View File

@@ -3,7 +3,7 @@ ManaCost:3 G G
Types:Creature Insect Types:Creature Insect
PT:6/1 PT:6/1
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Graveyard | IsPresent$ Card.StrictlySelf | PresentZone$ Graveyard | PresentPlayer$ You | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, if CARDNAME is in your graveyard, you may discard a card. If you do, return CARDNAME to your hand. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Graveyard | IsPresent$ Card.StrictlySelf | PresentZone$ Graveyard | PresentPlayer$ You | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, if CARDNAME is in your graveyard, you may discard a card. If you do, return CARDNAME to your hand.
SVar:TrigChange:AB$ChangeZone | Cost$ Discard<1/Card> | Origin$ Graveyard | Destination$ Hand | Defined$ Self SVar:TrigChange:AB$ ChangeZone | Cost$ Discard<1/Card> | Origin$ Graveyard | Destination$ Hand | Defined$ Self
K:Shroud K:Shroud
SVar:Picture:http://www.wizards.com/global/images/magic/general/gigapede.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/gigapede.jpg
Oracle:Shroud (This creature can't be the target of spells or abilities.)\nAt the beginning of your upkeep, if Gigapede is in your graveyard, you may discard a card. If you do, return Gigapede to your hand. Oracle:Shroud (This creature can't be the target of spells or abilities.)\nAt the beginning of your upkeep, if Gigapede is in your graveyard, you may discard a card. If you do, return Gigapede to your hand.

View File

@@ -4,6 +4,5 @@ Types:Creature Elk
PT:3/3 PT:3/3
K:Protection from blue K:Protection from blue
K:Protection from black K:Protection from black
K:CARDNAME can't be countered. K:This spell can't be countered.
SVar:Picture:http://www.wizards.com/global/images/magic/general/great_sable_stag.jpg
Oracle:This spell can't be countered.\nProtection from blue and from black (This creature can't be blocked, targeted, dealt damage, or enchanted by anything blue or black.) Oracle:This spell can't be countered.\nProtection from blue and from black (This creature can't be blocked, targeted, dealt damage, or enchanted by anything blue or black.)

View File

@@ -1,6 +1,6 @@
Name:Heated Debate Name:Heated Debate
ManaCost:2 R ManaCost:2 R
Types:Instant Types:Instant
K:CARDNAME can't be countered. K:This spell can't be countered.
A:SP$ DealDamage | Cost$ 2 R | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker. | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to target creature or planeswalker. A:SP$ DealDamage | Cost$ 2 R | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker. | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to target creature or planeswalker.
Oracle:This spell can't be countered. (This includes by the ward ability.)\nHeated Debate deals 4 damage to target creature or planeswalker. Oracle:This spell can't be countered. (This includes by the ward ability.)\nHeated Debate deals 4 damage to target creature or planeswalker.

View File

@@ -1,7 +1,7 @@
Name:Hunter's Mark Name:Hunter's Mark
ManaCost:3 G ManaCost:3 G
Types:Instant Types:Instant
K:CARDNAME can't be countered. K:This spell can't be countered.
S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ CostReduction | Relative$ True | EffectZone$ All | Description$ This spell costs {3} less to cast if it targets a blue permanent you don't control. S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ CostReduction | Relative$ True | EffectZone$ All | Description$ This spell costs {3} less to cast if it targets a blue permanent you don't control.
SVar:CostReduction:Count$Compare CheckTgt GE1.3.0 SVar:CostReduction:Count$Compare CheckTgt GE1.3.0
SVar:CheckTgt:TargetedObjects$Valid Card.Blue+YouDontCtrl SVar:CheckTgt:TargetedObjects$Valid Card.Blue+YouDontCtrl

View File

@@ -1,6 +1,6 @@
Name:Inescapable Blaze Name:Inescapable Blaze
ManaCost:4 R R ManaCost:4 R R
Types:Instant Types:Instant
K:CARDNAME can't be countered. K:This spell can't be countered.
A:SP$ DealDamage | Cost$ 4 R R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 6 | SpellDescription$ CARDNAME deals 6 damage to any target. A:SP$ DealDamage | Cost$ 4 R R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 6 | SpellDescription$ CARDNAME deals 6 damage to any target.
Oracle:This spell can't be countered.\nInescapable Blaze deals 6 damage to any target. Oracle:This spell can't be countered.\nInescapable Blaze deals 6 damage to any target.

View File

@@ -2,7 +2,7 @@ Name:Inferno of the Star Mounts
ManaCost:4 R R ManaCost:4 R R
Types:Legendary Creature Dragon Types:Legendary Creature Dragon
PT:6/6 PT:6/6
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Flying K:Flying
K:Haste K:Haste
A:AB$ Pump | Cost$ R | Defined$ Self | NumAtt$ +1 | SubAbility$ DBImmediateTrigger | AILogic$ InfernoOfTheStarMounts | SpellDescription$ CARDNAME gets +1/+0 until end of turn. When its power becomes 20 this way, it deals 20 damage to any target. A:AB$ Pump | Cost$ R | Defined$ Self | NumAtt$ +1 | SubAbility$ DBImmediateTrigger | AILogic$ InfernoOfTheStarMounts | SpellDescription$ CARDNAME gets +1/+0 until end of turn. When its power becomes 20 this way, it deals 20 damage to any target.

View File

@@ -2,9 +2,8 @@ Name:Isao, Enlightened Bushi
ManaCost:2 G ManaCost:2 G
Types:Legendary Creature Human Samurai Types:Legendary Creature Human Samurai
PT:2/1 PT:2/1
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Bushido:2 K:Bushido:2
A:AB$ Regenerate | Cost$ 2 | ValidTgts$ Samurai | TgtPrompt$ Select target Samurai. | SpellDescription$ Regenerate target Samurai. A:AB$ Regenerate | Cost$ 2 | ValidTgts$ Samurai | TgtPrompt$ Select target Samurai. | SpellDescription$ Regenerate target Samurai.
DeckHints:Type$Samurai DeckHints:Type$Samurai
SVar:Picture:http://www.wizards.com/global/images/magic/general/isao_enlightened_bushi.jpg
Oracle:This spell can't be countered.\nBushido 2 (Whenever this creature blocks or becomes blocked, it gets +2/+2 until end of turn.)\n{2}: Regenerate target Samurai. Oracle:This spell can't be countered.\nBushido 2 (Whenever this creature blocks or becomes blocked, it gets +2/+2 until end of turn.)\n{2}: Regenerate target Samurai.

View File

@@ -2,8 +2,7 @@ Name:Kavu Chameleon
ManaCost:3 G G ManaCost:3 G G
Types:Creature Kavu Types:Creature Kavu
PT:4/4 PT:4/4
K:CARDNAME can't be countered. K:This spell can't be countered.
A:AB$ ChooseColor | Cost$ G | Defined$ You | SubAbility$ Animate | SpellDescription$ CARDNAME becomes the color of your choice until end of turn. A:AB$ ChooseColor | Cost$ G | Defined$ You | SubAbility$ Animate | SpellDescription$ CARDNAME becomes the color of your choice until end of turn.
SVar:Animate:DB$ Animate | Defined$ Self | Colors$ ChosenColor | OverwriteColors$ True SVar:Animate:DB$ Animate | Defined$ Self | Colors$ ChosenColor | OverwriteColors$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/kavu_chameleon.jpg
Oracle:This spell can't be countered.\n{G}: Kavu Chameleon becomes the color of your choice until end of turn. Oracle:This spell can't be countered.\n{G}: Kavu Chameleon becomes the color of your choice until end of turn.

View File

@@ -2,7 +2,7 @@ Name:Koma, Cosmos Serpent
ManaCost:3 G G U U ManaCost:3 G G U U
Types:Legendary Creature Serpent Types:Legendary Creature Serpent
PT:6/6 PT:6/6
K:CARDNAME can't be countered. K:This spell can't be countered.
T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of each upkeep, create a 3/3 blue Serpent creature token named Koma's Coil. T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of each upkeep, create a 3/3 blue Serpent creature token named Koma's Coil.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ komas_coil | TokenOwner$ You SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ komas_coil | TokenOwner$ You
A:AB$ Charm | Cost$ Sac<1/Serpent.Other/another Serpent> | Choices$ DBTap,DBPump A:AB$ Charm | Cost$ Sac<1/Serpent.Other/another Serpent> | Choices$ DBTap,DBPump

View File

@@ -2,6 +2,5 @@ Name:Last Word
ManaCost:2 U U ManaCost:2 U U
Types:Instant Types:Instant
A:SP$ Counter | Cost$ 2 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell. A:SP$ Counter | Cost$ 2 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell.
K:CARDNAME can't be countered. K:This spell can't be countered.
SVar:Picture:http://www.wizards.com/global/images/magic/general/last_word.jpg
Oracle:This spell can't be countered.\nCounter target spell. Oracle:This spell can't be countered.\nCounter target spell.

View File

@@ -2,7 +2,7 @@ Name:Lightning Mare
ManaCost:R R ManaCost:R R
Types:Creature Elemental Horse Types:Creature Elemental Horse
PT:3/1 PT:3/1
K:CARDNAME can't be countered. K:This spell can't be countered.
S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.Blue | Description$ CARDNAME can't be blocked by blue creatures. S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | ValidBlocker$ Creature.Blue | Description$ CARDNAME can't be blocked by blue creatures.
A:AB$ Pump | Cost$ 1 R | ValidCard$ Card.Self | NumAtt$ +1 | NumDef$ +0 | SpellDescription$ CARDNAME gets +1/+0 until end of turn. A:AB$ Pump | Cost$ 1 R | ValidCard$ Card.Self | NumAtt$ +1 | NumDef$ +0 | SpellDescription$ CARDNAME gets +1/+0 until end of turn.
Oracle:This spell can't be countered.\nLightning Mare can't be blocked by blue creatures.\n{1}{R}: Lightning Mare gets +1/+0 until end of turn. Oracle:This spell can't be countered.\nLightning Mare can't be blocked by blue creatures.\n{1}{R}: Lightning Mare gets +1/+0 until end of turn.

View File

@@ -2,7 +2,7 @@ Name:Loxodon Smiter
ManaCost:1 G W ManaCost:1 G W
Types:Creature Elephant Soldier Types:Creature Elephant Soldier
PT:4/4 PT:4/4
K:CARDNAME can't be countered. K:This spell can't be countered.
R:Event$ Discard | ActiveZones$ Hand | ValidCard$ Card.Self | ValidSource$ Card.OppCtrl | ReplaceWith$ SurpriseETB | DiscardFromEffect$ True | Description$ If a spell or ability an opponent controls causes you to discard CARDNAME, put it onto the battlefield instead of putting it into your graveyard. R:Event$ Discard | ActiveZones$ Hand | ValidCard$ Card.Self | ValidSource$ Card.OppCtrl | ReplaceWith$ SurpriseETB | DiscardFromEffect$ True | Description$ If a spell or ability an opponent controls causes you to discard CARDNAME, put it onto the battlefield instead of putting it into your graveyard.
SVar:SurpriseETB:DB$ ChangeZone | DefinedPlayer$ ReplacedPlayer | Defined$ ReplacedCard | Origin$ Hand | Destination$ Battlefield SVar:SurpriseETB:DB$ ChangeZone | DefinedPlayer$ ReplacedPlayer | Defined$ ReplacedCard | Origin$ Hand | Destination$ Battlefield
SVar:DiscardMeByOpp:2 SVar:DiscardMeByOpp:2

View File

@@ -5,7 +5,7 @@ PT:2/1
S:Mode$ Continuous | Affected$ Dwarf.Other+YouCtrl | AddPower$ 1 | Description$ Other Dwarves you control get +1/+0. S:Mode$ Continuous | Affected$ Dwarf.Other+YouCtrl | AddPower$ 1 | Description$ Other Dwarves you control get +1/+0.
T:Mode$ Taps | ValidCard$ Dwarf.YouCtrl | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever a Dwarf you control becomes tapped, create a Treasure token. T:Mode$ Taps | ValidCard$ Dwarf.YouCtrl | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever a Dwarf you control becomes tapped, create a Treasure token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ You SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ You
A:AB$ ChangeZone | Cost$ Sac<5/Treasure> | CostDesc$ Sacrifice five Treasures: | Origin$ Library | Destination$ Battlefield | ChangeType$ Card.Artifact,Card.Dragon | ChangeNum$ 1 | Mandatory$ True | StackDescription$ {p:You} searches their library for an Artifact or Dragon card, puts that card onto the battlefield, then shuffles their library. | SpellDescription$ Search your library for an artifact or Dragon card, put that card onto the battlefield, then shuffle. A:AB$ ChangeZone | Cost$ Sac<5/Treasure> | Origin$ Library | Destination$ Battlefield | ChangeType$ Card.Artifact,Card.Dragon | ChangeNum$ 1 | Mandatory$ True | StackDescription$ {p:You} searches their library for an Artifact or Dragon card, puts that card onto the battlefield, then shuffles their library. | SpellDescription$ Search your library for an artifact or Dragon card, put that card onto the battlefield, then shuffle.
SVar:BuffedBy:Dwarf SVar:BuffedBy:Dwarf
SVar:PlayMain1:TRUE SVar:PlayMain1:TRUE
DeckNeeds:Type$Dwarf DeckNeeds:Type$Dwarf

View File

@@ -1,6 +1,6 @@
Name:Magic Missile Name:Magic Missile
ManaCost:1 R R ManaCost:1 R R
Types:Sorcery Types:Sorcery
K:CARDNAME can't be countered. K:This spell can't be countered.
A:SP$ DealDamage | Cost$ 1 R R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target to distribute damage to | NumDmg$ 3 | TargetMin$ 1 | TargetMax$ 3 | DividedAsYouChoose$ 3 | SpellDescription$ CARDNAME deals 3 damage divided as you choose among one, two, or three targets. A:SP$ DealDamage | Cost$ 1 R R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target to distribute damage to | NumDmg$ 3 | TargetMin$ 1 | TargetMax$ 3 | DividedAsYouChoose$ 3 | SpellDescription$ CARDNAME deals 3 damage divided as you choose among one, two, or three targets.
Oracle:This spell can't be countered.\nMagic Missile deals 3 damage divided as you choose among one, two, or three targets. Oracle:This spell can't be countered.\nMagic Missile deals 3 damage divided as you choose among one, two, or three targets.

View File

@@ -2,7 +2,7 @@ Name:Mistcutter Hydra
ManaCost:X G ManaCost:X G
Types:Creature Hydra Types:Creature Hydra
PT:0/0 PT:0/0
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Haste K:Haste
K:Protection from blue K:Protection from blue
K:etbCounter:P1P1:X K:etbCounter:P1P1:X

View File

@@ -2,9 +2,7 @@ Name:Nahiri, the Harbinger
ManaCost:2 R W ManaCost:2 R W
Types:Legendary Planeswalker Nahiri Types:Legendary Planeswalker Nahiri
Loyalty:4 Loyalty:4
A:AB$ Discard | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | NumCards$ 1 | Optional$ True | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBDraw | SpellDescription$ You may discard a card. If you do, draw a card. A:AB$ Draw | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | Defined$ You | NumCards$ 1 | UnlessCost$ Discard<1/Card> | UnlessSwitched$ True | UnlessPayer$ You | SpellDescription$ You may discard a card. If you do, draw a card.
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
A:AB$ ChangeZone | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | ValidTgts$ Enchantment,Artifact.tapped,Creature.tapped | TgtPrompt$ Select target enchantment, tapped artifact, or tapped creature | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target enchantment, tapped artifact, or tapped creature. A:AB$ ChangeZone | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | ValidTgts$ Enchantment,Artifact.tapped,Creature.tapped | TgtPrompt$ Select target enchantment, tapped artifact, or tapped creature | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target enchantment, tapped artifact, or tapped creature.
A:AB$ ChangeZone | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Artifact,Creature | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBPump | SpellDescription$ Search your library for an artifact or creature card, put it onto the battlefield, then shuffle. It gains haste. Return it to your hand at the beginning of the next end step. A:AB$ ChangeZone | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Artifact,Creature | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBPump | SpellDescription$ Search your library for an artifact or creature card, put it onto the battlefield, then shuffle. It gains haste. Return it to your hand at the beginning of the next end step.
SVar:DBPump:DB$ Animate | Keywords$ Haste | Duration$ Permanent | AtEOT$ Hand | Defined$ Remembered | SubAbility$ DBCleanup SVar:DBPump:DB$ Animate | Keywords$ Haste | Duration$ Permanent | AtEOT$ Hand | Defined$ Remembered | SubAbility$ DBCleanup

View File

@@ -2,7 +2,7 @@ Name:Nezahal, Primal Tide
ManaCost:5 U U ManaCost:5 U U
Types:Legendary Creature Elder Dinosaur Types:Legendary Creature Elder Dinosaur
PT:7/7 PT:7/7
K:CARDNAME can't be countered. K:This spell can't be countered.
S:Mode$ Continuous | Affected$ You | SetMaxHandSize$ Unlimited | Description$ You have no maximum hand size. S:Mode$ Continuous | Affected$ You | SetMaxHandSize$ Unlimited | Description$ You have no maximum hand size.
T:Mode$ SpellCast | TriggerZones$ Battlefield | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ Opponent | Execute$ TrigDraw | TriggerDescription$ Whenever an opponent casts a noncreature spell, draw a card. T:Mode$ SpellCast | TriggerZones$ Battlefield | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ Opponent | Execute$ TrigDraw | TriggerDescription$ Whenever an opponent casts a noncreature spell, draw a card.
SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1

View File

@@ -3,7 +3,7 @@ ManaCost:U U U R R R
Types:Legendary Creature Dragon Wizard Types:Legendary Creature Dragon Wizard
PT:5/5 PT:5/5
K:Flying K:Flying
K:CARDNAME can't be countered. K:This spell can't be countered.
T:Mode$ Drawn | ValidCard$ Card.YouOwn | TriggerZones$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ Whenever you draw a card, CARDNAME deals 1 damage to any target. T:Mode$ Drawn | ValidCard$ Card.YouOwn | TriggerZones$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ Whenever you draw a card, CARDNAME deals 1 damage to any target.
SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1
T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever a player casts an instant or sorcery spell, you draw a card. T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever a player casts an instant or sorcery spell, you draw a card.

View File

@@ -2,6 +2,5 @@ Name:Obliterate
ManaCost:6 R R ManaCost:6 R R
Types:Sorcery Types:Sorcery
A:SP$ DestroyAll | Cost$ 6 R R | ValidCards$ Artifact,Creature,Land | NoRegen$ True | SpellDescription$ Destroy all artifacts, creatures, and lands. They can't be regenerated. A:SP$ DestroyAll | Cost$ 6 R R | ValidCards$ Artifact,Creature,Land | NoRegen$ True | SpellDescription$ Destroy all artifacts, creatures, and lands. They can't be regenerated.
K:CARDNAME can't be countered. K:This spell can't be countered.
SVar:Picture:http://www.wizards.com/global/images/magic/general/obliterate.jpg
Oracle:This spell can't be countered.\nDestroy all artifacts, creatures, and lands. They can't be regenerated. Oracle:This spell can't be countered.\nDestroy all artifacts, creatures, and lands. They can't be regenerated.

View File

@@ -3,11 +3,9 @@ ManaCost:1 B R
Types:Legendary Creature Vampire Knight Types:Legendary Creature Vampire Knight
PT:3/3 PT:3/3
K:Flying K:Flying
T:Mode$ ChangesZone | ValidCard$ Creature.Other+YouCtrl | Origin$ Any | Destination$ Battlefield | Execute$ TrigDiscard | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ Whenever another creature enters the battlefield under your control, you may discard a card. If you do, put a +1/+1 counter on that creature, it gains haste until end of turn, and it becomes a vampire in addition to its other types. T:Mode$ ChangesZone | ValidCard$ Creature.Other+YouCtrl | Origin$ Any | Destination$ Battlefield | Execute$ TrigDiscard | TriggerZones$ Battlefield | TriggerDescription$ Whenever another creature enters the battlefield under your control, you may discard a card. If you do, put a +1/+1 counter on that creature, it gains haste until end of turn, and it becomes a vampire in addition to its other types.
SVar:TrigDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBPutCounter SVar:TrigDiscard:AB$ PutCounter | Cost$ Discard<1/Card> | Defined$ TriggeredCardLKICopy | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBPump
SVar:DBPutCounter:DB$ PutCounter | Defined$ TriggeredCardLKICopy | CounterType$ P1P1 | CounterNum$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBPump SVar:DBPump:DB$ Pump | Defined$ TriggeredCard | KW$ Haste | SubAbility$ DBAnimate
SVar:DBPump:DB$ Pump | Defined$ TriggeredCard | KW$ Haste | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBAnimate SVar:DBAnimate:DB$ Animate | Defined$ TriggeredCard | Types$ Vampire | Duration$ Permanent
SVar:DBAnimate:DB$ Animate | Defined$ TriggeredCard | Types$ Vampire | Duration$ Permanent | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:Picture:http://www.wizards.com/global/images/magic/general/olivia_mobilized_for_war.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/olivia_mobilized_for_war.jpg
Oracle:Flying\nWhenever another creature enters the battlefield under your control, you may discard a card. If you do, put a +1/+1 counter on that creature, it gains haste until end of turn, and it becomes a Vampire in addition to its other types. Oracle:Flying\nWhenever another creature enters the battlefield under your control, you may discard a card. If you do, put a +1/+1 counter on that creature, it gains haste until end of turn, and it becomes a Vampire in addition to its other types.

View File

@@ -3,6 +3,5 @@ ManaCost:2 U U
Types:Instant Types:Instant
K:Surge:U U K:Surge:U U
A:SP$ Counter | Cost$ 2 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell. A:SP$ Counter | Cost$ 2 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell.
K:CARDNAME can't be countered. K:This spell can't be countered.
SVar:Picture:http://www.wizards.com/global/images/magic/general/overwhelming_denial.jpg
Oracle:Surge {U}{U} (You may cast this spell for its surge cost if you or a teammate has cast another spell this turn.)\nThis spell can't be countered.\nCounter target spell. Oracle:Surge {U}{U} (You may cast this spell for its surge cost if you or a teammate has cast another spell this turn.)\nThis spell can't be countered.\nCounter target spell.

View File

@@ -3,7 +3,7 @@ ManaCost:3 R
Types:Creature Elemental Cat Types:Creature Elemental Cat
PT:2/3 PT:2/3
K:Haste K:Haste
S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Graveyard | AddHiddenKeyword$ CARDNAME count as Flame Burst. | Description$ If CARDNAME is in a graveyard, effects from spells named Flame Burst count it as a card named Flame Burst. S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Graveyard | AffectedZone$ Graveyard | AddHiddenKeyword$ CARDNAME count as Flame Burst. | Description$ If CARDNAME is in a graveyard, effects from spells named Flame Burst count it as a card named Flame Burst.
DeckHints:Name$Flame Burst|Pardic Firecat DeckHints:Name$Flame Burst|Pardic Firecat
SVar:Picture:http://www.wizards.com/global/images/magic/general/pardic_firecat.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/pardic_firecat.jpg
Oracle:Haste\nIf Pardic Firecat is in a graveyard, effects from spells named Flame Burst count it as a card named Flame Burst. Oracle:Haste\nIf Pardic Firecat is in a graveyard, effects from spells named Flame Burst count it as a card named Flame Burst.

View File

@@ -4,7 +4,6 @@ Types:Creature Leviathan
PT:6/7 PT:6/7
K:Flash K:Flash
K:Prowess K:Prowess
K:CARDNAME can't be countered. K:This spell can't be countered.
A:AB$ ChangeZone | Cost$ Return<3/Land> | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return CARDNAME to its owner's hand. A:AB$ ChangeZone | Cost$ Return<3/Land> | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return CARDNAME to its owner's hand.
SVar:Picture:http://www.wizards.com/global/images/magic/general/pearl_lake_ancient.jpg
Oracle:Flash\nThis spell can't be countered.\nProwess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)\nReturn three lands you control to their owner's hand: Return Pearl Lake Ancient to its owner's hand. Oracle:Flash\nThis spell can't be countered.\nProwess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)\nReturn three lands you control to their owner's hand: Return Pearl Lake Ancient to its owner's hand.

View File

@@ -2,7 +2,7 @@ Name:Petrified Wood-Kin
ManaCost:6 G ManaCost:6 G
Types:Creature Elemental Warrior Types:Creature Elemental Warrior
PT:3/3 PT:3/3
K:CARDNAME can't be countered. K:This spell can't be countered.
K:Bloodthirst:X K:Bloodthirst:X
K:Protection:Instant:Protection from instants K:Protection:Instant:Protection from instants
SVar:Picture:http://www.wizards.com/global/images/magic/general/petrified_wood_kin.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/petrified_wood_kin.jpg

View File

@@ -9,7 +9,7 @@ SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | RememberObjects$ Remembered
SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ Until the end of your next turn, you may play that card. SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ Until the end of your next turn, you may play that card.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
T:Mode$ SpellCast | ValidCard$ Card.wasCastFromExile | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Pact Boon — Whenever you play a card from exile, create a Treasure token. T:Mode$ SpellCast | ValidCard$ Card.wasCastFromExile | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Pact Boon — Whenever you play a card from exile, create a Treasure token.
T:Mode$ ChangesZone | Origin$ Exile | Destination$ Battlefield | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | Secondary$ True | TriggerDescription$ Pact Boon — Whenever you play a card from exile, create a Treasure token. T:Mode$ LandPlayed | Origin$ Exile | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | Secondary$ True | TriggerDescription$ Pact Boon — Whenever you play a card from exile, create a Treasure token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ You | LegacyImage$ c a treasure sac afr SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ You | LegacyImage$ c a treasure sac afr
DeckHas:Ability$Token DeckHas:Ability$Token
Oracle:Deathtouch\nMystic Arcanum — At the beginning of your end step, exile the top card of your library. Until the end of your next turn, you may play that card.\nPact Boon — Whenever you play a card from exile, create a Treasure token. Oracle:Deathtouch\nMystic Arcanum — At the beginning of your end step, exile the top card of your library. Until the end of your next turn, you may play that card.\nPact Boon — Whenever you play a card from exile, create a Treasure token.

Some files were not shown because too many files have changed in this diff Show More