mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 04:08:01 +00:00
Merge branch 'master' of https://git.cardforge.org/core-developers/forge into adventure
This commit is contained in:
@@ -714,7 +714,24 @@ public class AiController {
|
||||
|
||||
// Check a predefined condition
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -753,6 +770,7 @@ public class AiController {
|
||||
// one is warded and can't be paid for.
|
||||
if (sa.usesTargeting()) {
|
||||
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())) {
|
||||
int amount = 0;
|
||||
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
|
||||
@@ -1809,7 +1827,35 @@ public class AiController {
|
||||
}
|
||||
}
|
||||
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")) {
|
||||
return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac");
|
||||
} else return sa != null && doTrigger(sa, false);
|
||||
@@ -2315,37 +2361,4 @@ public class AiController {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2497,4 +2497,27 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,21 +452,15 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
/**
|
||||
* Attach to player ai preferences.
|
||||
*
|
||||
* @param sa
|
||||
* the sa
|
||||
* @param mandatory
|
||||
* the mandatory
|
||||
* @param newParam TODO
|
||||
*
|
||||
* @return the player
|
||||
*/
|
||||
public static Player attachToPlayerAIPreferences(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||
List<Player> targetable = new ArrayList<>();
|
||||
for (final Player player : aiPlayer.getGame().getPlayers()) {
|
||||
if (sa.canTarget(player)) {
|
||||
targetable.add(player);
|
||||
}
|
||||
}
|
||||
|
||||
public static Player attachToPlayerAIPreferences(final Player aiPlayer, final SpellAbility sa, final boolean mandatory, List<Player> targetable) {
|
||||
if ("Curse".equals(sa.getParam("AILogic"))) {
|
||||
if (!mandatory) {
|
||||
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) {
|
||||
GameObject o;
|
||||
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 {
|
||||
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,
|
||||
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)
|
||||
|| "MoveAllAuras".equals(logic)) {
|
||||
prefPlayer = ai;
|
||||
} else {
|
||||
prefPlayer = AiAttackController.choosePreferredDefenderPlayer(ai);
|
||||
}
|
||||
// Some ChangeType cards are beneficial, and PrefPlayer should be
|
||||
// changed to represent that
|
||||
@@ -1745,6 +1747,10 @@ public class AttachAi extends SpellAbilityAi {
|
||||
sa.getTargets().add(tgt);
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -1761,6 +1767,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import forge.game.card.*;
|
||||
import forge.game.keyword.Keyword;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
@@ -27,19 +29,13 @@ import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.GlobalRuleChange;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
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.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostDiscard;
|
||||
@@ -55,6 +51,7 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbilityMustTarget;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
public class ChangeZoneAi extends SpellAbilityAi {
|
||||
/*
|
||||
@@ -428,7 +425,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
return canBouncePermanent(ai, sa, list) != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1738,8 +1785,20 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
// Called when attaching Aura to player
|
||||
return AttachAi.attachToPlayerAIPreferences(ai, sa, true);
|
||||
// Called when attaching Aura to player or adding creature to combat
|
||||
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) {
|
||||
|
||||
@@ -12,10 +12,12 @@ import forge.ai.AiPlayDecision;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
@@ -32,6 +34,7 @@ import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
public class CopyPermanentAi extends SpellAbilityAi {
|
||||
@Override
|
||||
@@ -232,7 +235,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
|
||||
|
||||
private CardCollection getBetterOptions(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player ctrl = host.getController();
|
||||
@@ -244,9 +247,21 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
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();
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,10 +8,12 @@ import forge.ai.AiAttackController;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilAbility;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
@@ -25,6 +27,7 @@ import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
|
||||
public class DigAi extends SpellAbilityAi {
|
||||
@@ -180,10 +183,22 @@ public class DigAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
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)
|
||||
* @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String)
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@ import forge.ai.AiController;
|
||||
import forge.ai.AiProps;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.ComputerUtilCost;
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.PlayerControllerAi;
|
||||
@@ -37,6 +38,7 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -312,15 +314,8 @@ public class TokenAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
Combat combat = ai.getGame().getCombat();
|
||||
// TokenAttacking
|
||||
if (combat != null && sa.hasParam("TokenAttacking")) {
|
||||
Card attacker = spawnToken(ai, sa);
|
||||
for (Player p : options) {
|
||||
if (!ComputerUtilCard.canBeBlockedProfitably(p, attacker)) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
if (params.containsKey("Attacker")) {
|
||||
return (Player) ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
}
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
@@ -330,28 +325,11 @@ public class TokenAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
|
||||
Combat combat = ai.getGame().getCombat();
|
||||
// TokenAttacking
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (params.containsKey("Attacker")) {
|
||||
return ComputerUtilCombat.addAttackerToCombat(sa, (Card) params.get("Attacker"), new FCollection<GameEntity>(options));
|
||||
}
|
||||
return Iterables.getFirst(options, null);
|
||||
// should not be reached
|
||||
return super.chooseSinglePlayerOrPlaneswalker(ai, sa, options, params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,6 +66,10 @@ public final class ImageKeys {
|
||||
}
|
||||
|
||||
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) {
|
||||
return cachedCards.get(key);
|
||||
}
|
||||
@@ -101,6 +105,9 @@ public final class ImageKeys {
|
||||
dir = CACHE_CARD_PICS_DIR;
|
||||
}
|
||||
|
||||
if (missingCards.contains(filename))
|
||||
return null;
|
||||
|
||||
File cachedFile = cachedCards.get(filename);
|
||||
if (cachedFile != null) {
|
||||
return cachedFile;
|
||||
@@ -237,6 +244,8 @@ public final class ImageKeys {
|
||||
}
|
||||
|
||||
// System.out.println("File not found, no image created: " + key);
|
||||
//add missing cards
|
||||
missingCards.add(filename);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
* @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!
|
||||
*
|
||||
* @see StaticData#getAlternativeCardPrint(forge.item.PaperCard, java.util.Date)
|
||||
* @param card Input Reference Card
|
||||
* @param setReleaseDate reference set release date
|
||||
* @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 cardArtPreferenceHasFilter = this.isCoreExpansionOnlyFilterSet();
|
||||
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.
|
||||
* 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
|
||||
* @param isCardArtPreferenceLatestArt Determines whether "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
|
||||
* @param cardArtPreferenceHasFilter Determines whether the search should only consider
|
||||
* 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>
|
||||
* if None could be found.
|
||||
*/
|
||||
public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate,
|
||||
boolean isCardArtPreferenceLatestArt,
|
||||
boolean cardArtPreferenceHasFilter) {
|
||||
boolean cardArtPreferenceHasFilter, List<String> allowedSetCodes) {
|
||||
Date searchReferenceDate = getReferenceDate(setReleaseDate, isCardArtPreferenceLatestArt);
|
||||
CardDb.CardArtPreference searchCardArtStrategy = getSearchStrategyForAlternativeCardArt(isCardArtPreferenceLatestArt,
|
||||
cardArtPreferenceHasFilter);
|
||||
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.
|
||||
*
|
||||
* <p>
|
||||
@@ -531,18 +530,54 @@ public class StaticData {
|
||||
public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate, boolean isCardArtPreferenceLatestArt,
|
||||
boolean cardArtPreferenceHasFilter,
|
||||
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,
|
||||
cardArtPreferenceHasFilter);
|
||||
cardArtPreferenceHasFilter, allowedSetCodes);
|
||||
if (altCard == null)
|
||||
return altCard;
|
||||
// from here on, we're sure we do have a candidate already!
|
||||
|
||||
/* Try to refine selection by getting one candidate with frame matching current
|
||||
Card Art Preference (that is NOT the lookup strategy!)*/
|
||||
PaperCard refinedAltCandidate = this.tryToGetCardPrintWithMatchingFrame(altCard,
|
||||
isCardArtPreferenceLatestArt,
|
||||
cardArtPreferenceHasFilter,
|
||||
preferModernFrame);
|
||||
PaperCard refinedAltCandidate = this.tryToGetCardPrintWithMatchingFrame(altCard, isCardArtPreferenceLatestArt,
|
||||
cardArtPreferenceHasFilter,
|
||||
preferModernFrame, allowedSetCodes);
|
||||
if (refinedAltCandidate != null)
|
||||
altCard = refinedAltCandidate;
|
||||
|
||||
@@ -551,7 +586,7 @@ public class StaticData {
|
||||
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 */
|
||||
refinedAltCandidate = this.tryToGetCardPrintFromExpansionSet(altCard, isCardArtPreferenceLatestArt,
|
||||
preferModernFrame);
|
||||
preferModernFrame, allowedSetCodes);
|
||||
if (refinedAltCandidate != null)
|
||||
altCard = refinedAltCandidate;
|
||||
}
|
||||
@@ -560,21 +595,29 @@ public class StaticData {
|
||||
|
||||
private PaperCard searchAlternativeCardCandidate(PaperCard card, boolean isCardArtPreferenceLatestArt,
|
||||
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!
|
||||
CardDb cardDb = this.commonCards;
|
||||
String cardName = card.getName();
|
||||
int artIndex = card.getArtIndex();
|
||||
PaperCard altCard = null;
|
||||
Predicate<PaperCard> filter = null;
|
||||
if (allowedSetCodes != null && !allowedSetCodes.isEmpty())
|
||||
filter = (Predicate<PaperCard>) cardDb.isLegal(allowedSetCodes);
|
||||
|
||||
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
|
||||
altCard = cardDb.getCardFromEditionsReleasedAfter(cardName, searchCardArtStrategy, searchReferenceDate);
|
||||
altCard = cardDb.getCardFromEditionsReleasedAfter(cardName, searchCardArtStrategy,
|
||||
searchReferenceDate, filter);
|
||||
} 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
|
||||
altCard = cardDb.getCardFromEditionsReleasedBefore(cardName, searchCardArtStrategy, searchReferenceDate);
|
||||
altCard = cardDb.getCardFromEditionsReleasedBefore(cardName, searchCardArtStrategy,
|
||||
searchReferenceDate, filter);
|
||||
}
|
||||
if (altCard == null)
|
||||
return null;
|
||||
@@ -611,7 +654,8 @@ public class StaticData {
|
||||
|
||||
private PaperCard tryToGetCardPrintFromExpansionSet(PaperCard altCard,
|
||||
boolean isCardArtPreferenceLatestArt,
|
||||
boolean preferModernFrame) {
|
||||
boolean preferModernFrame,
|
||||
List<String> allowedSetCodes) {
|
||||
CardEdition altCardEdition = editions.get(altCard.getEdition());
|
||||
if (altCardEdition.getType() == CardEdition.Type.EXPANSION)
|
||||
return null; // Nothing to do here!
|
||||
@@ -624,7 +668,7 @@ public class StaticData {
|
||||
while (altCandidate != null) {
|
||||
Date referenceDate = editions.get(altCandidate.getEdition()).getDate();
|
||||
altCandidate = this.searchAlternativeCardCandidate(altCandidate, preferModernFrame,
|
||||
referenceDate, searchStrategy);
|
||||
referenceDate, searchStrategy, allowedSetCodes);
|
||||
if (altCandidate != null) {
|
||||
CardEdition altCandidateEdition = editions.get(altCandidate.getEdition());
|
||||
if (altCandidateEdition.getType() == CardEdition.Type.EXPANSION)
|
||||
@@ -638,7 +682,7 @@ public class StaticData {
|
||||
private PaperCard tryToGetCardPrintWithMatchingFrame(PaperCard altCard,
|
||||
boolean isCardArtPreferenceLatestArt,
|
||||
boolean cardArtHasFilter,
|
||||
boolean preferModernFrame) {
|
||||
boolean preferModernFrame, List<String> allowedSetCodes) {
|
||||
CardEdition altCardEdition = editions.get(altCard.getEdition());
|
||||
boolean frameIsCompliantAlready = (altCardEdition.isModern() == preferModernFrame);
|
||||
if (frameIsCompliantAlready)
|
||||
@@ -650,7 +694,7 @@ public class StaticData {
|
||||
while (altCandidate != null) {
|
||||
Date referenceDate = editions.get(altCandidate.getEdition()).getDate();
|
||||
altCandidate = this.searchAlternativeCardCandidate(altCandidate, preferModernFrame,
|
||||
referenceDate, searchStrategy);
|
||||
referenceDate, searchStrategy, allowedSetCodes);
|
||||
if (altCandidate != null) {
|
||||
CardEdition altCandidateEdition = editions.get(altCandidate.getEdition());
|
||||
if (altCandidateEdition.isModern() == preferModernFrame)
|
||||
|
||||
@@ -266,7 +266,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
}
|
||||
deferredSections = null; // set to null, just in case!
|
||||
if (includeCardsFromUnspecifiedSet && smartCardArtSelection)
|
||||
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition, true);
|
||||
optimiseCardArtSelectionInDeckSections(cardsWithNoEdition);
|
||||
}
|
||||
|
||||
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, 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) {
|
||||
ArrayList<String> cardNamesWithNoEdition = new ArrayList<>();
|
||||
List<Pair<String, Integer>> cardRequests = CardPool.processCardList(cardsInSection);
|
||||
@@ -372,7 +364,7 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
return cardNamesWithNoEdition;
|
||||
}
|
||||
|
||||
private void optimiseCardArtSelectionInDeckSections(Map<DeckSection, ArrayList<String>> cardsWithNoEdition, boolean multiArtPrint) {
|
||||
private void optimiseCardArtSelectionInDeckSections(Map<DeckSection, ArrayList<String>> cardsWithNoEdition) {
|
||||
StaticData data = StaticData.instance();
|
||||
// Get current Card Art Preference Settings
|
||||
boolean isCardArtPreferenceLatestArt = data.cardArtPreferenceIsLatest();
|
||||
@@ -405,13 +397,13 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
int totalToAddToPool = cp.getValue();
|
||||
// A. Skip cards not requiring any update, because they add the edition specified!
|
||||
if (!cardNamesWithNoEditionInSection.contains(card.getName())) {
|
||||
addCardToPool(newPool, card, totalToAddToPool, card.isFoil(), multiArtPrint);
|
||||
addCardToPool(newPool, card, totalToAddToPool, card.isFoil());
|
||||
continue;
|
||||
}
|
||||
// B. Determine if current card requires update
|
||||
boolean cardArtNeedsOptimisation = this.isCardArtUpdateRequired(card, releaseDatePivotEdition);
|
||||
if (!cardArtNeedsOptimisation) {
|
||||
addCardToPool(newPool, card, totalToAddToPool, card.isFoil(), multiArtPrint);
|
||||
addCardToPool(newPool, card, totalToAddToPool, card.isFoil());
|
||||
continue;
|
||||
}
|
||||
PaperCard alternativeCardPrint = data.getAlternativeCardPrint(card, releaseDatePivotEdition,
|
||||
@@ -420,22 +412,20 @@ public class Deck extends DeckBase implements Iterable<Entry<DeckSection, CardPo
|
||||
isExpansionTheMajorityInThePool,
|
||||
isPoolModernFramed);
|
||||
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
|
||||
addCardToPool(newPool, alternativeCardPrint, totalToAddToPool, card.isFoil(), multiArtPrint);
|
||||
addCardToPool(newPool, alternativeCardPrint, totalToAddToPool, card.isFoil());
|
||||
}
|
||||
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();
|
||||
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!
|
||||
else {
|
||||
int artCount = 1;
|
||||
if (multiArtPrint)
|
||||
artCount = data.getCardArtCount(card);
|
||||
int artCount = data.getCardArtCount(card);
|
||||
if (artCount > 1)
|
||||
addAlternativeCardPrintInPoolWithMultipleArt(card, pool, totalToAdd, artCount);
|
||||
else
|
||||
|
||||
@@ -83,23 +83,29 @@ public class DeckRecognizer {
|
||||
// only used for card tokens
|
||||
private PaperCard card = 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,
|
||||
final DeckSection section) {
|
||||
return new Token(TokenType.LEGAL_CARD, count, card, section);
|
||||
final DeckSection section, final boolean cardRequestHasSetCode) {
|
||||
return new Token(TokenType.LEGAL_CARD, count, card, section, cardRequestHasSetCode);
|
||||
}
|
||||
|
||||
public static Token LimitedCard(final PaperCard card, final int count,
|
||||
final DeckSection section, final LimitedCardType limitedType){
|
||||
return new Token(TokenType.LIMITED_CARD, count, card, section, limitedType);
|
||||
final DeckSection section, final LimitedCardType 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) {
|
||||
return new Token(TokenType.CARD_FROM_NOT_ALLOWED_SET, count, card);
|
||||
public static Token NotAllowedCard(final PaperCard card, final int count, final boolean cardRequestHasSetCode) {
|
||||
return new Token(TokenType.CARD_FROM_NOT_ALLOWED_SET, count, card, cardRequestHasSetCode);
|
||||
}
|
||||
|
||||
public static Token CardInInvalidSet(final PaperCard card, final int count) {
|
||||
return new Token(TokenType.CARD_FROM_INVALID_SET, count, card);
|
||||
public static Token CardInInvalidSet(final PaperCard card, final int count, final boolean cardRequestHasSetCode) {
|
||||
return new Token(TokenType.CARD_FROM_INVALID_SET, count, card, cardRequestHasSetCode);
|
||||
}
|
||||
|
||||
// WARNING MESSAGES
|
||||
@@ -154,26 +160,27 @@ public class DeckRecognizer {
|
||||
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.type = type1;
|
||||
this.text = String.format("%s [%s] #%s",
|
||||
tokenCard.getName(), tokenCard.getEdition(), tokenCard.getCollectorNumber());
|
||||
this.text = "";
|
||||
this.card = tokenCard;
|
||||
this.tokenSection = null;
|
||||
this.limitedCardType = null;
|
||||
this.cardRequestHasSetCode = cardRequestHasSetCode;
|
||||
}
|
||||
|
||||
private Token(final TokenType type1, final int count, final PaperCard tokenCard,
|
||||
final DeckSection section) {
|
||||
this(type1, count, tokenCard);
|
||||
final DeckSection section, boolean cardRequestHasSetCode) {
|
||||
this(type1, count, tokenCard, cardRequestHasSetCode);
|
||||
this.tokenSection = section;
|
||||
this.limitedCardType = null;
|
||||
}
|
||||
|
||||
private Token(final TokenType type1, final int count, final PaperCard tokenCard,
|
||||
final DeckSection section, final LimitedCardType limitedCardType1) {
|
||||
this(type1, count, tokenCard);
|
||||
final DeckSection section, final LimitedCardType limitedCardType1,
|
||||
boolean cardRequestHasSetCode) {
|
||||
this(type1, count, tokenCard, cardRequestHasSetCode);
|
||||
this.tokenSection = section;
|
||||
this.limitedCardType = limitedCardType1;
|
||||
}
|
||||
@@ -189,6 +196,9 @@ public class DeckRecognizer {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -204,16 +214,26 @@ public class DeckRecognizer {
|
||||
return this.number;
|
||||
}
|
||||
|
||||
public final boolean cardRequestHasNoCode() {
|
||||
return !(this.cardRequestHasSetCode);
|
||||
}
|
||||
|
||||
public final DeckSection getTokenSection() { return this.tokenSection; }
|
||||
|
||||
public void resetTokenSection(DeckSection referenceDeckSection) {
|
||||
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; }
|
||||
|
||||
/**
|
||||
* 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:
|
||||
* LEGAL_CARD, LIMITED_CARD, CARD_FROM_NOT_ALLOWED_SET and CARD_FROM_INVALID_SET.
|
||||
* False otherwise.
|
||||
@@ -236,6 +256,15 @@ public class DeckRecognizer {
|
||||
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,
|
||||
* only used for Decklist formatting.
|
||||
@@ -635,7 +664,8 @@ public class DeckRecognizer {
|
||||
PaperCard pc = data.getCardFromSet(cardName, edition, collectorNumber, artIndex, isFoil);
|
||||
if (pc != null)
|
||||
// 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
|
||||
return Token.UnknownCard(cardName, setCode, cardCount);
|
||||
}
|
||||
@@ -654,7 +684,8 @@ public class DeckRecognizer {
|
||||
|
||||
if (pc != null) {
|
||||
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
|
||||
@@ -677,25 +708,26 @@ public class DeckRecognizer {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Token checkAndSetCardToken(PaperCard pc, CardEdition edition, int cardCount,
|
||||
String deckSecFromCardLine, DeckSection referenceSection) {
|
||||
private Token checkAndSetCardToken(final PaperCard pc, final CardEdition edition, final int cardCount,
|
||||
final String deckSecFromCardLine, final DeckSection referenceSection,
|
||||
final boolean cardRequestHasSetCode) {
|
||||
// Note: Always Check Allowed Set First to avoid accidentally importing invalid cards
|
||||
// e.g. Banned Cards from not-allowed sets!
|
||||
if (IsIllegalInFormat(edition.getCode()))
|
||||
// Mark as illegal card
|
||||
return Token.NotAllowedCard(pc, cardCount);
|
||||
return Token.NotAllowedCard(pc, cardCount, cardRequestHasSetCode);
|
||||
|
||||
if (isNotCompliantWithReleaseDateRestrictions(edition))
|
||||
return Token.CardInInvalidSet(pc, cardCount);
|
||||
return Token.CardInInvalidSet(pc, cardCount, cardRequestHasSetCode);
|
||||
|
||||
DeckSection tokenSection = getTokenSection(deckSecFromCardLine, referenceSection, 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))
|
||||
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
|
||||
|
||||
@@ -412,7 +412,7 @@ public class GameAction {
|
||||
CardCollection cards = new CardCollection(c.getMergedCards());
|
||||
// replace top card with copied card for correct name for human to choose.
|
||||
cards.set(cards.indexOf(c), copied);
|
||||
// 721.3b
|
||||
// 723.3b
|
||||
if (cause != null && zoneTo.getZoneType() == ZoneType.Exile) {
|
||||
cards = (CardCollection) cause.getHostCard().getController().getController().orderMoveToZoneList(cards, zoneTo.getZoneType(), cause);
|
||||
} else {
|
||||
|
||||
@@ -179,7 +179,7 @@ public final class GameActionUtil {
|
||||
final Cost disturbCost = new Cost(k[1], true);
|
||||
|
||||
SpellAbility newSA;
|
||||
if (source.getAlternateState().getType().isEnchantment()) {
|
||||
if (source.getAlternateState().getType().hasSubtype("Aura")) {
|
||||
newSA = source.getAlternateState().getFirstAbility().copyWithManaCostReplaced(activator,
|
||||
disturbCost);
|
||||
} else {
|
||||
|
||||
@@ -575,14 +575,14 @@ public abstract class SpellAbilityEffect {
|
||||
FCollection<GameEntity> defs = null;
|
||||
// important to update defenders here, maybe some PW got removed
|
||||
combat.initConstraints();
|
||||
if ("True".equalsIgnoreCase(attacking)) {
|
||||
defs = (FCollection<GameEntity>) combat.getDefenders();
|
||||
} else if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
|
||||
if (sa.hasParam("ChoosePlayerOrPlaneswalker")) {
|
||||
PlayerCollection defendingPlayers = AbilityUtils.getDefinedPlayers(host, attacking, sa);
|
||||
defs = new FCollection<>();
|
||||
for (Player p : defendingPlayers) {
|
||||
defs.addAll(game.getCombat().getDefendersControlledBy(p));
|
||||
}
|
||||
} else if ("True".equalsIgnoreCase(attacking)) {
|
||||
defs = (FCollection<GameEntity>) combat.getDefenders();
|
||||
} else {
|
||||
defs = AbilityUtils.getDefinedEntities(host, attacking, sa);
|
||||
}
|
||||
@@ -593,8 +593,7 @@ public abstract class SpellAbilityEffect {
|
||||
Player chooser;
|
||||
if (sa.hasParam("Chooser")) {
|
||||
chooser = Iterables.getFirst(AbilityUtils.getDefinedPlayers(host, sa.getParam("Chooser"), sa), null);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
chooser = controller;
|
||||
}
|
||||
defender = chooser.getController().chooseSingleEntityForEffect(defs, sa,
|
||||
|
||||
@@ -137,15 +137,17 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
|
||||
final int libraryPos = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : 0;
|
||||
|
||||
if ((destination == ZoneType.Library || destination == ZoneType.PlanarDeck)
|
||||
&& !sa.hasParam("Shuffle") && cards.size() >= 2 && !random) {
|
||||
Player p = AbilityUtils.getDefinedPlayers(source, sa.getParamOrDefault("DefinedPlayer", "You"), sa).get(0);
|
||||
cards = (CardCollection) p.getController().orderMoveToZoneList(cards, destination, sa);
|
||||
//the last card in this list will be the closest to the top, but we want the first card to be closest.
|
||||
//so reverse it here before moving them to the library.
|
||||
java.util.Collections.reverse(cards);
|
||||
} else {
|
||||
cards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, cards, destination, sa);
|
||||
if (!random) {
|
||||
if ((destination == ZoneType.Library || destination == ZoneType.PlanarDeck)
|
||||
&& !sa.hasParam("Shuffle") && cards.size() >= 2) {
|
||||
Player p = AbilityUtils.getDefinedPlayers(source, sa.getParamOrDefault("DefinedPlayer", "You"), sa).get(0);
|
||||
cards = (CardCollection) p.getController().orderMoveToZoneList(cards, destination, sa);
|
||||
//the last card in this list will be the closest to the top, but we want the first card to be closest.
|
||||
//so reverse it here before moving them to the library.
|
||||
java.util.Collections.reverse(cards);
|
||||
} else {
|
||||
cards = (CardCollection) GameActionUtil.orderCardsByTheirOwners(game, cards, destination, sa);
|
||||
}
|
||||
}
|
||||
|
||||
if (destination.equals(ZoneType.Library) && random) {
|
||||
|
||||
@@ -164,7 +164,6 @@ public class DigUntilEffect extends SpellAbilityEffect {
|
||||
game.getAction().reveal(revealed, p, false);
|
||||
}
|
||||
|
||||
|
||||
if (foundDest != null) {
|
||||
// Allow ordering of found cards
|
||||
if ((foundDest.isKnown()) && found.size() >= 2 && !foundDest.equals(ZoneType.Exile)) {
|
||||
|
||||
@@ -52,7 +52,7 @@ public class DrawEffect extends SpellAbilityEffect {
|
||||
|
||||
int actualNum = numCards;
|
||||
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);
|
||||
|
||||
@@ -2628,8 +2628,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
sbCost.append(cost.toSimpleString());
|
||||
sbAfter.append(sbCost).append(" (").append(inst.getReminderText()).append(")");
|
||||
sbAfter.append("\r\n");
|
||||
} else if (keyword.equals("CARDNAME can't be countered.") ||
|
||||
keyword.equals("Remove CARDNAME from your deck before playing if you're not playing for ante.")) {
|
||||
} else if (keyword.equals("CARDNAME can't be countered.") || keyword.equals("This spell can't be " +
|
||||
"countered.") || keyword.equals("Remove CARDNAME from your deck before playing if you're not " +
|
||||
"playing for ante.")) {
|
||||
sbBefore.append(keyword);
|
||||
sbBefore.append("\r\n");
|
||||
} else if (keyword.startsWith("Haunt")) {
|
||||
|
||||
@@ -256,7 +256,8 @@ public class CardFactoryUtil {
|
||||
* @return a boolean.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
return new Predicate<Card>() {
|
||||
@Override
|
||||
|
||||
@@ -25,6 +25,7 @@ import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperCard;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
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")) {
|
||||
if (!card.sharesPermanentTypeWith(source)) {
|
||||
return false;
|
||||
@@ -1462,6 +1468,14 @@ public class CardProperty {
|
||||
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")) {
|
||||
return null == combat || !combat.isAttacking(card);
|
||||
} else if (property.equals("attackedThisCombat")) {
|
||||
|
||||
@@ -179,6 +179,8 @@ public enum CounterEnumType {
|
||||
|
||||
JAVELIN("JAVLN", 180, 206, 172),
|
||||
|
||||
JUDGMENT("JUDGM", 249, 220, 52),
|
||||
|
||||
KI("KI", 190, 189, 255),
|
||||
|
||||
KNOWLEDGE("KNOWL", 0, 115, 255),
|
||||
|
||||
@@ -832,12 +832,7 @@ public class Cost implements Serializable {
|
||||
sb.append(Cost.NUM_NAMES[i]);
|
||||
}
|
||||
|
||||
sb.append(" ");
|
||||
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);
|
||||
sb.append(" ").append(type);
|
||||
if (1 != i) {
|
||||
sb.append("s");
|
||||
}
|
||||
|
||||
@@ -199,7 +199,9 @@ public class CostDiscard extends CostPartWithList {
|
||||
// discard itself for cycling cost
|
||||
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)
|
||||
|
||||
@@ -132,7 +132,6 @@ public class CostReturn extends CostPartWithList {
|
||||
return "ReturnedCards";
|
||||
}
|
||||
|
||||
|
||||
public <T> T accept(ICostVisitor<T> visitor) {
|
||||
return visitor.visit(this);
|
||||
}
|
||||
|
||||
@@ -1774,6 +1774,10 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
if (land.isFaceDown()) {
|
||||
land.turnFaceUp(null);
|
||||
}
|
||||
|
||||
Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(land);
|
||||
runParams.put(AbilityKey.Origin, land.getZone().getZoneType().name());
|
||||
|
||||
game.copyLastState();
|
||||
final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, cause);
|
||||
game.updateLastStateForCard(c);
|
||||
@@ -1782,7 +1786,6 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
game.fireEvent(new GameEventLandPlayed(this, land));
|
||||
|
||||
// Run triggers
|
||||
Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(land);
|
||||
runParams.put(AbilityKey.SpellAbility, cause);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false);
|
||||
game.getStack().unfreezeStack();
|
||||
|
||||
@@ -294,7 +294,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
}
|
||||
|
||||
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);
|
||||
if (first == null) {
|
||||
return false;
|
||||
@@ -324,11 +324,11 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this.getActivationLimit() != -1) && (sa.getActivationsThisTurn() >= this.getActivationLimit())) {
|
||||
if (this.getActivationLimit() != -1 && sa.getActivationsThisTurn() >= this.getActivationLimit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this.getGameActivationLimit() != -1) && (sa.getActivationsThisGame() >= this.getGameActivationLimit())) {
|
||||
if (this.getGameActivationLimit() != -1 && sa.getActivationsThisGame() >= this.getGameActivationLimit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
}
|
||||
|
||||
if (this.getColorToCheck() != null) {
|
||||
if (!sa.getHostCard().hasChosenColor(this.getColorToCheck())) {
|
||||
if (!host.hasChosenColor(this.getColorToCheck())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -365,7 +365,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
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);
|
||||
int right = AbilityUtils.calculateAmount(host, rightString, sa);
|
||||
@@ -378,9 +378,9 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
if (this.getPlayerContains() != null) {
|
||||
List<Player> list = new ArrayList<>();
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
@@ -417,7 +417,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
boolean result = false;
|
||||
|
||||
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;
|
||||
break;
|
||||
}
|
||||
@@ -448,7 +448,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(getManaSpent())) {
|
||||
SpellAbility castSa = sa.getHostCard().getCastSA();
|
||||
SpellAbility castSa = host.getCastSA();
|
||||
if (castSa == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -457,7 +457,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(getManaNotSpent())) {
|
||||
SpellAbility castSa = sa.getHostCard().getCastSA();
|
||||
SpellAbility castSa = host.getCastSA();
|
||||
if (castSa != null && castSa.getPayingColors().hasAllColors(ColorSet.fromNames(getManaNotSpent().split(" ")).getColor())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ package forge.game.trigger;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -67,6 +69,19 @@ public class TriggerLandPlayed extends Trigger {
|
||||
* @param runParams*/
|
||||
@Override
|
||||
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))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -290,6 +290,7 @@ public class GuiDesktop implements IGuiBase {
|
||||
@Override
|
||||
public void clearImageCache() {
|
||||
ImageCache.clear();
|
||||
ImageKeys.clearMissingCards();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -96,6 +96,7 @@ public class ImageCache {
|
||||
public static void clear() {
|
||||
_CACHE.invalidateAll();
|
||||
_missingIconKeys.clear();
|
||||
ImageKeys.clearMissingCards();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -201,6 +201,9 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
private final FCheckBox cardArtPrefHasFilterCheckBox = new FCheckBox(Localizer.getInstance()
|
||||
.getMessage("lblPrefArtExpansionOnly"), false);
|
||||
|
||||
// Smart Card Art Optimisation
|
||||
private final FCheckBox smartCardArtCheckBox = new FCheckBox(Localizer.getInstance().getMessage("lblUseSmartCardArt"), false);
|
||||
|
||||
// Format Filter
|
||||
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 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 VStatisticsImporter statsView;
|
||||
private final CStatisticsImporter cStatsView;
|
||||
@@ -224,6 +229,8 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
GameType currentGameType = g.getGameType();
|
||||
this.controller = new DeckImportController(dateTimeCheck, monthDropdown, yearDropdown, currentDeckIsNotEmpty);
|
||||
this.controller.setGameFormat(currentGameType);
|
||||
if (currentDeckIsNotEmpty)
|
||||
this.controller.setCurrentDeckInEditor(this.host.getDeckController().getCurrentDeckInEditor());
|
||||
// Get the list of allowed Sections
|
||||
List<DeckSection> supportedSections = new ArrayList<>();
|
||||
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");
|
||||
|
||||
// B2. Card Art Preference Filter
|
||||
|
||||
final String latestOpt = Localizer.getInstance().getMessage("latestArtOpt");
|
||||
final String originalOpt = Localizer.getInstance().getMessage("originalArtOpt");
|
||||
final String [] choices = {latestOpt, originalOpt};
|
||||
@@ -393,6 +401,10 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
artPrefInfoLabel.setFont(FSkin.getItalicFont());
|
||||
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.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.cardArtPrefsComboBox, "cell 0 0, w 10%, left, split 2");
|
||||
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
|
||||
// ----------------
|
||||
@@ -420,6 +433,15 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
this.cardArtPrefsComboBox.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");
|
||||
|
||||
// B3. Block Filter
|
||||
@@ -531,6 +553,9 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
controller.setCreateNewDeck(createNewDeck);
|
||||
String cmdAcceptLabel = createNewDeck ? CREATE_NEW_DECK_CMD_LABEL : IMPORT_CARDS_CMD_LABEL;
|
||||
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);
|
||||
if (tokenKey == null)
|
||||
return;
|
||||
StaticData data = StaticData.instance();
|
||||
PaperCard card = data.fetchCard(tokenKey.cardName, tokenKey.setCode, tokenKey.collectorNumber);
|
||||
PaperCard card = StaticData.instance().fetchCard(tokenKey.cardName, tokenKey.setCode,
|
||||
tokenKey.collectorNumber);
|
||||
if (card != null) {
|
||||
// no need to check for card that has Image because CardPicturePanel
|
||||
// has automatic integration with cardFetch
|
||||
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);
|
||||
DeckRecognizer.Token mockToken = createMockTokenFromTokenKey(card, tokenKey);
|
||||
setupCardImagePreviewPanel(card, mockToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
this.cardPreviewLabel.setText(Localizer.getInstance().getMessage("lblCardPreview"));
|
||||
this.cardImagePreview.setItem(ImageCache.getDefaultImage());
|
||||
@@ -641,7 +678,9 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
}
|
||||
|
||||
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);
|
||||
updateSummaries(tokens);
|
||||
}
|
||||
@@ -685,27 +724,39 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
PaperCard cardDisplayed = (PaperCard) displayedCardInPanel;
|
||||
// this will return either the same card instance or its [un]foiled version
|
||||
// null will be returned if not found in card list anymore
|
||||
cardDisplayed = this.controller.getCardFromDecklist(cardDisplayed);
|
||||
if (cardDisplayed == null)
|
||||
this.resetCardImagePreviewPanel(); // current displayed card is not in decklist
|
||||
else {
|
||||
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();
|
||||
PaperCard cardFromDecklist = this.controller.getCardFromDecklist(cardDisplayed);
|
||||
if (cardFromDecklist == null) {
|
||||
cardFromDecklist = this.controller.getCardFromDecklistByName(cardDisplayed.getName());
|
||||
if (cardFromDecklist == null)
|
||||
this.resetCardImagePreviewPanel(); // current displayed card is not in decklist
|
||||
else {
|
||||
DeckRecognizer.Token cardToken = controller.getTokenFromCardInDecklist(cardFromDecklist);
|
||||
setupCardImagePreviewPanel(cardFromDecklist, cardToken);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if (token == null)
|
||||
return "";
|
||||
@@ -714,7 +765,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
return "";
|
||||
String tokenStatus = getTokenStatusMessage(token);
|
||||
String cssClass = getTokenCSSClass(token.getType());
|
||||
if (tokenStatus == null)
|
||||
if (tokenStatus.length() == 0)
|
||||
tokenMsg = padEndWithHTMLSpaces(tokenMsg, 2*PADDING_TOKEN_MSG_LENGTH+10);
|
||||
else {
|
||||
tokenMsg = padEndWithHTMLSpaces(tokenMsg, PADDING_TOKEN_MSG_LENGTH);
|
||||
@@ -830,7 +881,7 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
|
||||
case LEGAL_CARD:
|
||||
case UNKNOWN_TEXT:
|
||||
default:
|
||||
return null;
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,14 @@ public class DeckController<T extends DeckBase> {
|
||||
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) {
|
||||
// Getting Latest among the earliest editions in catalog!
|
||||
CardEdition referenceEdition = StaticData.instance().getEditions().getTheLatestOfAllTheOriginalEditionsOfCardsIn(catalog);
|
||||
@@ -182,8 +190,7 @@ public class DeckController<T extends DeckBase> {
|
||||
if (card == null)
|
||||
continue;
|
||||
int countToAdd = countByName.get(cardName);
|
||||
card = StaticData.instance().getAlternativeCardPrint(card, referenceReleaseDate,
|
||||
true, true);
|
||||
card = StaticData.instance().getAlternativeCardPrint(card, referenceReleaseDate);
|
||||
if (card != null)
|
||||
targetSection.add(card.getName(), card.getEdition(), countToAdd);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import forge.model.FModel;
|
||||
import forge.player.GamePlayerUtil;
|
||||
import forge.screens.deckeditor.CDeckEditorUI;
|
||||
import forge.screens.deckeditor.controllers.CEditorTokenViewer;
|
||||
import forge.sound.MusicPlaylist;
|
||||
import forge.sound.SoundSystem;
|
||||
import forge.toolbox.*;
|
||||
import forge.util.Localizer;
|
||||
@@ -27,6 +28,8 @@ import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.io.File;
|
||||
@@ -262,6 +265,8 @@ public enum CSubmenuPreferences implements ICDoc {
|
||||
initializeAutoUpdaterComboBox();
|
||||
initializeMulliganRuleComboBox();
|
||||
initializeAiProfilesComboBox();
|
||||
initializeSoundSetsComboBox();
|
||||
initializeMusicSetsComboBox();
|
||||
initializeStackAdditionsComboBox();
|
||||
initializeLandPlayedComboBox();
|
||||
initializeColorIdentityCombobox();
|
||||
@@ -466,6 +471,35 @@ public enum CSubmenuPreferences implements ICDoc {
|
||||
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() {
|
||||
final String latestOpt = Localizer.getInstance().getMessage("latestArtOpt");
|
||||
final String originalOpt = Localizer.getInstance().getMessage("originalArtOpt");
|
||||
|
||||
@@ -128,6 +128,8 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
|
||||
private final FComboBoxPanel<String> cbpCardArtFormat = new FComboBoxPanel<>(localizer.getMessage("cbpCardArtFormat")+":");
|
||||
private final FComboBoxPanel<String> cbpCardArtPreference = new FComboBoxPanel<>(localizer.getMessage("lblPreferredArt")+":");
|
||||
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> cbpStackAdditions = new FComboBoxPanel<>(localizer.getMessage("cbpStackAdditions")+":");
|
||||
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(new NoteLabel(localizer.getMessage("nlEnableSounds")), descriptionConstraints);
|
||||
|
||||
pnlPrefs.add(cbpSoundSets, comboBoxConstraints);
|
||||
pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpSoundSets")), descriptionConstraints);
|
||||
|
||||
pnlPrefs.add(cbEnableMusic, titleConstraints);
|
||||
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(new NoteLabel(localizer.getMessage("nlAltSoundSystem")), descriptionConstraints);
|
||||
pnlPrefs.add(cbSROptimize, titleConstraints);
|
||||
@@ -740,6 +748,14 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
|
||||
return cbpMulliganRule;
|
||||
}
|
||||
|
||||
public FComboBoxPanel<String> getSoundSetsComboBoxPanel() {
|
||||
return cbpSoundSets;
|
||||
}
|
||||
|
||||
public FComboBoxPanel<String> getMusicSetsComboBoxPanel() {
|
||||
return cbpMusicSets;
|
||||
}
|
||||
|
||||
public FComboBoxPanel<String> getAiProfilesComboBoxPanel() {
|
||||
return cbpAiProfiles;
|
||||
}
|
||||
|
||||
@@ -40,9 +40,6 @@ import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
import com.google.common.io.Files;
|
||||
import com.sipgate.mp3wav.Converter;
|
||||
|
||||
import forge.localinstance.properties.ForgeConstants;
|
||||
|
||||
|
||||
/**
|
||||
* SoundSystem - a simple sound playback system for Forge.
|
||||
* 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) {
|
||||
File fSound = new File(ForgeConstants.SOUND_DIR, fileName);
|
||||
File fSound = new File(SoundSystem.instance.getSoundDirectory(), fileName);
|
||||
return fSound.exists();
|
||||
}
|
||||
|
||||
@@ -195,7 +192,7 @@ public class AudioClip implements IAudioClip {
|
||||
}
|
||||
|
||||
private Clip createClip(String filename) {
|
||||
File fSound = new File(ForgeConstants.SOUND_DIR, filename);
|
||||
File fSound = new File(SoundSystem.instance.getSoundDirectory(), filename);
|
||||
if (!fSound.exists()) {
|
||||
throw new IllegalArgumentException("Sound file " + fSound.toString() + " does not exist, cannot make a clip of it");
|
||||
}
|
||||
|
||||
@@ -1114,6 +1114,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertTrue(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "TMP");
|
||||
assertEquals(tokenCard.getCollectorNumber(), "78");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "4x Power Sink TMP 78";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1125,6 +1126,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(tokenCard.getName(), "Power Sink");
|
||||
assertFalse(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "TMP");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
assertEquals(tokenCard.getCollectorNumber(), "78");
|
||||
|
||||
lineRequest = "4x TMP Power Sink 78";
|
||||
@@ -1138,6 +1140,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertFalse(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "TMP");
|
||||
assertEquals(tokenCard.getCollectorNumber(), "78");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "4x TMP Power Sink";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1150,6 +1153,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertFalse(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "TMP");
|
||||
assertEquals(tokenCard.getCollectorNumber(), "78");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "4x Power Sink TMP";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1162,6 +1166,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertFalse(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "TMP");
|
||||
assertEquals(tokenCard.getCollectorNumber(), "78");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "Power Sink TMP";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1174,6 +1179,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertFalse(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "TMP");
|
||||
assertEquals(tokenCard.getCollectorNumber(), "78");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "[TMP] Power Sink";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1186,6 +1192,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertFalse(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "TMP");
|
||||
assertEquals(tokenCard.getCollectorNumber(), "78");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Relax Set Preference
|
||||
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");
|
||||
assertFalse(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "VMA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "4x Power Sink+";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1211,6 +1219,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(tokenCard.getName(), "Power Sink");
|
||||
assertTrue(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "VMA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "Power Sink+";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1222,6 +1231,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(tokenCard.getName(), "Power Sink");
|
||||
assertTrue(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "VMA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testSingleWordCardNameMatchesCorrectly(){
|
||||
@@ -1236,6 +1246,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getQuantity(), 2);
|
||||
assertEquals(tokenCard.getName(), "Counterspell");
|
||||
assertEquals(tokenCard.getEdition(), "ICE");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Remove Set code
|
||||
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(tokenCard.getName(), "Counterspell");
|
||||
assertEquals(tokenCard.getEdition(), "MH2");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
}
|
||||
|
||||
@@ -1271,6 +1283,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertTrue(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "TMP");
|
||||
assertEquals(tokenCard.getCollectorNumber(), "78");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
/*==================================
|
||||
@@ -1402,6 +1415,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(tokenCard.getEdition(), "MIR");
|
||||
assertEquals(tokenCard.getArtIndex(), 3);
|
||||
assertEquals(tokenCard.getCollectorNumber(), "345");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testCollectorNumberIsNotConfusedAsArtIndexInstead(){
|
||||
@@ -1418,6 +1432,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(tokenCard.getEdition(), "MIR");
|
||||
assertEquals(tokenCard.getArtIndex(), 1);
|
||||
assertEquals(tokenCard.getCollectorNumber(), "3");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testCardRequestWithWrongCollectorNumberStillReturnsTheCardFromSetIfAny(){
|
||||
@@ -1433,6 +1448,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(card.getName(), "Jayemdae Tome");
|
||||
assertEquals(card.getEdition(), "LEB");
|
||||
assertEquals(card.getCollectorNumber(), "255");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// No Match - Unknown card
|
||||
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(StaticData.instance().getCommonCards().getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
|
||||
assertEquals(ancestralCard.getEdition(), "2ED");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Counterspell editions
|
||||
lineRequest = "Counterspell";
|
||||
@@ -1498,6 +1515,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getQuantity(), 1);
|
||||
assertEquals(counterSpellCard.getName(), "Counterspell");
|
||||
assertEquals(counterSpellCard.getEdition(), "MMQ");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testInvalidCardRequestWhenReleaseDateConstraintsAreUp(){
|
||||
@@ -1515,12 +1533,14 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getQuantity(), 1);
|
||||
assertEquals(counterSpellCard.getName(), "Counterspell");
|
||||
assertEquals(counterSpellCard.getEdition(), "MH2");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
recognizer.setDateConstraint(1999, 10);
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
assertNotNull(cardToken);
|
||||
assertEquals(cardToken.getType(), TokenType.CARD_FROM_INVALID_SET);
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
/*======================================
|
||||
@@ -1542,6 +1562,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
PaperCard tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Counterspell");
|
||||
assertEquals(tc.getEdition(), "MH2");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Setting Original Core
|
||||
recognizer.setArtPreference(CardDb.CardArtPreference.ORIGINAL_ART_ALL_EDITIONS);
|
||||
@@ -1555,6 +1576,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Counterspell");
|
||||
assertEquals(tc.getEdition(), "LEA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testCardRequestVariesUponChangesInArtPreference(){
|
||||
@@ -1571,6 +1593,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(tokenCard.getName(), "Power Sink");
|
||||
assertTrue(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "VMA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
recognizer.setArtPreference(CardDb.CardArtPreference.ORIGINAL_ART_CORE_EXPANSIONS_REPRINT_ONLY);
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1582,6 +1605,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(tokenCard.getName(), "Power Sink");
|
||||
assertTrue(tokenCard.isFoil());
|
||||
assertEquals(tokenCard.getEdition(), "LEA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Check that result is persistent - and consistent with change
|
||||
lineRequest = "Power Sink";
|
||||
@@ -1594,6 +1618,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(tokenCard.getName(), "Power Sink");
|
||||
assertFalse(tokenCard.isFoil());
|
||||
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);
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertNull(cardToken.getTokenSection());
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "2x Counterspell"; // It does not exist any Counterspell in Urza's block
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1619,6 +1645,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getType(), TokenType.CARD_FROM_NOT_ALLOWED_SET);
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertNull(cardToken.getTokenSection());
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testRequestingCardWithRestrictionsOnDeckFormat(){
|
||||
@@ -1634,6 +1661,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(ancestralCard.getName(), "Ancestral Recall");
|
||||
assertEquals(StaticData.instance().getCommonCards().getCardArtPreference(), CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS);
|
||||
assertEquals(ancestralCard.getEdition(), "VMA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders);
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1643,6 +1671,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getTokenSection(), DeckSection.Main);
|
||||
assertNotNull(cardToken.getLimitedCardType());
|
||||
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testCardRequestUnderGameConstraints(){
|
||||
@@ -1668,6 +1697,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Bloodstained Mire");
|
||||
assertEquals(cardToken.getCard().getEdition(), "ONS");
|
||||
assertEquals(cardToken.getText(), "Bloodstained Mire [ONS] #313");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
String noEditionCardRequest = "4 Bloodstained Mire";
|
||||
cardToken = recognizer.recogniseCardToken(noEditionCardRequest, null);
|
||||
@@ -1681,6 +1711,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Bloodstained Mire");
|
||||
assertEquals(cardToken.getCard().getEdition(), "KTK");
|
||||
assertEquals(cardToken.getText(), "Bloodstained Mire [KTK] #230");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testGameFormatRestrictionsAlsoWithRestrictedCardList(){
|
||||
@@ -1712,6 +1743,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Ancestral Recall");
|
||||
assertEquals(cardToken.getQuantity(), 1);
|
||||
assertEquals(cardToken.getCard().getEdition(), "VMA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
cardRequest = "4x Ancestral Recall";
|
||||
cardToken = recognizer.recogniseCardToken(cardRequest, null);
|
||||
@@ -1723,6 +1755,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Ancestral Recall");
|
||||
assertEquals(cardToken.getQuantity(), 4);
|
||||
assertEquals(cardToken.getCard().getEdition(), "VMA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testSettingPartialConstraintsOnGameFormatsAreStillApplied(){
|
||||
@@ -1746,6 +1779,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker");
|
||||
assertEquals(cardToken.getQuantity(), 1);
|
||||
assertEquals(cardToken.getCard().getEdition(), "MB1");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
cardRequest = "4x Viashino Sandstalker";
|
||||
cardToken = recognizer.recogniseCardToken(cardRequest, null);
|
||||
@@ -1757,6 +1791,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker");
|
||||
assertEquals(cardToken.getQuantity(), 4);
|
||||
assertEquals(cardToken.getCard().getEdition(), "MB1");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Requesting now what will be a Banned card later in this test
|
||||
cardRequest = "Squandered Resources";
|
||||
@@ -1768,6 +1803,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Squandered Resources");
|
||||
assertEquals(cardToken.getQuantity(), 1);
|
||||
assertEquals(cardToken.getCard().getEdition(), "VIS");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// == ALLOWED SETS ONLY
|
||||
recognizer.setGameFormatConstraint(allowedSetCodes, null, null);
|
||||
@@ -1781,6 +1817,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker");
|
||||
assertEquals(cardToken.getQuantity(), 4);
|
||||
assertEquals(cardToken.getCard().getEdition(), "VIS");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// == BANNED CARDS ONLY
|
||||
recognizer.setGameFormatConstraint(null, bannedCards, null);
|
||||
@@ -1794,6 +1831,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker");
|
||||
assertEquals(cardToken.getQuantity(), 4);
|
||||
assertEquals(cardToken.getCard().getEdition(), "MB1");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
cardRequest = "Squandered Resources";
|
||||
cardToken = recognizer.recogniseCardToken(cardRequest, null);
|
||||
@@ -1806,6 +1844,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Squandered Resources");
|
||||
assertEquals(cardToken.getQuantity(), 1);
|
||||
assertEquals(cardToken.getCard().getEdition(), "VIS");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// ALLOWED SET CODES AND RESTRICTED
|
||||
recognizer.setGameFormatConstraint(allowedSetCodes, null, restrictedCards);
|
||||
@@ -1820,6 +1859,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getCard().getName(), "Viashino Sandstalker");
|
||||
assertEquals(cardToken.getQuantity(), 4);
|
||||
assertEquals(cardToken.getCard().getEdition(), "VIS");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
/*======================================
|
||||
@@ -1840,6 +1880,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
PaperCard tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Lightning Dragon");
|
||||
assertEquals(tc.getEdition(), "VMA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
recognizer.setDateConstraint(2000, 0); // Jan 2000
|
||||
// Setting Fantasy Constructed Game Format: Urza's Block Format (no promo)
|
||||
@@ -1855,6 +1896,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Lightning Dragon");
|
||||
assertEquals(tc.getEdition(), "USG");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Relaxing Constraint on Set
|
||||
lineRequest = "2x Lightning Dragon";
|
||||
@@ -1866,6 +1908,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Lightning Dragon");
|
||||
assertEquals(tc.getEdition(), "USG"); // the latest available within set requested
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Now setting a tighter date constraint
|
||||
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.getQuantity(), 2);
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "2x Lightning Dragon";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1883,6 +1927,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getQuantity(), 2);
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertEquals(cardToken.getText(), "Lightning Dragon [USG] #202");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Now relaxing date constraint but removing USG from allowed sets
|
||||
// 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.getQuantity(), 2);
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "2x Lightning Dragon";
|
||||
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.getQuantity(), 2);
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Now relaxing date constraint but removing USG from allowed sets
|
||||
// VMA release date: 2014-06-16
|
||||
@@ -1916,6 +1963,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Lightning Dragon");
|
||||
assertEquals(tc.getEdition(), "VMA");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testCardMatchWithDateANDdeckFormatConstraints(){
|
||||
@@ -1933,6 +1981,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
PaperCard tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Flash");
|
||||
assertEquals(tc.getEdition(), "A25");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
recognizer.setDateConstraint(2012, 0); // Jan 2012
|
||||
recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders);
|
||||
@@ -1946,6 +1995,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getTokenSection(), DeckSection.Main);
|
||||
assertNotNull(cardToken.getLimitedCardType());
|
||||
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "2x Cancel";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1956,6 +2006,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Cancel");
|
||||
assertEquals(tc.getEdition(), "M12"); // the latest within date constraint
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "2x Cancel|M21";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -1964,6 +2015,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getQuantity(), 2);
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertEquals(cardToken.getText(), "Cancel [M21] #46");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testCardMatchWithGameANDdeckFormatConstraints(){
|
||||
@@ -1981,6 +2033,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
PaperCard tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Flash");
|
||||
assertEquals(tc.getEdition(), "A25");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
recognizer.setGameFormatConstraint(Arrays.asList("MIR", "VIS", "WTH"), null, null);
|
||||
recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders);
|
||||
@@ -1993,6 +2046,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getTokenSection(), DeckSection.Main);
|
||||
assertNotNull(cardToken.getLimitedCardType());
|
||||
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "2x Femeref Knight";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -2003,6 +2057,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Femeref Knight");
|
||||
assertEquals(tc.getEdition(), "MIR");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "2x Incinerate";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -2013,6 +2068,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Incinerate");
|
||||
assertEquals(tc.getEdition(), "MIR");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "Noble Elephant";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -2024,6 +2080,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getTokenSection(), DeckSection.Main);
|
||||
assertNotNull(cardToken.getLimitedCardType());
|
||||
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "Incinerate|ICE";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -2032,6 +2089,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getQuantity(), 1);
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertEquals(cardToken.getText(), "Incinerate [ICE] #194");
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
@Test void testCardMatchWitDateANDgameANDdeckFormatConstraints(){
|
||||
@@ -2049,6 +2107,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
PaperCard tc = cardToken.getCard();
|
||||
assertEquals(tc.getName(), "Flash");
|
||||
assertEquals(tc.getEdition(), "A25");
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
recognizer.setGameFormatConstraint(Arrays.asList("MIR", "VIS", "WTH"), null, null);
|
||||
recognizer.setDeckFormatConstraint(DeckFormat.TinyLeaders);
|
||||
@@ -2063,6 +2122,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getTokenSection(), DeckSection.Main);
|
||||
assertNotNull(cardToken.getLimitedCardType());
|
||||
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "Ardent Militia";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -2073,6 +2133,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getTokenSection(), DeckSection.Main);
|
||||
assertNotNull(cardToken.getLimitedCardType());
|
||||
assertEquals(cardToken.getLimitedCardType(), DeckRecognizer.LimitedCardType.BANNED);
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "Buried Alive|UMA";
|
||||
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
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertEquals(cardToken.getText(), "Buried Alive [UMA] #88"); // within set constraints
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "Buried Alive";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -2088,6 +2150,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertEquals(cardToken.getCard().getName(), "Buried Alive");
|
||||
assertEquals(cardToken.getCard().getEdition(), "WTH"); // within set constraints
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
recognizer.setDateConstraint(1997, 2); // March '97 - before WTH
|
||||
lineRequest = "Buried Alive";
|
||||
@@ -2096,6 +2159,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(cardToken.getType(), TokenType.CARD_FROM_INVALID_SET);
|
||||
assertNotNull(cardToken.getCard());
|
||||
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.getEdition(), "BNG");
|
||||
assertTrue(aspectOfHydraCard.isFoil());
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "18 Forest <254> [THB] (F)";
|
||||
cardToken = recognizer.recogniseCardToken(lineRequest, null);
|
||||
@@ -2177,6 +2242,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(forestCard.getName(), "Forest");
|
||||
assertEquals(forestCard.getEdition(), "THB");
|
||||
assertTrue(forestCard.isFoil());
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
// === TappedOut Markdown Format
|
||||
@@ -2258,6 +2324,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(acCard.getName(), "Amoeboid Changeling");
|
||||
assertEquals(acCard.getEdition(), "LRW");
|
||||
assertEquals(acCard.getCollectorNumber(), "51");
|
||||
assertFalse(xmageCardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
// === Deckstats Commander
|
||||
@@ -2276,6 +2343,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(soCard.getName(), "Sliver Overlord");
|
||||
assertEquals(soCard.getEdition(), "SLD");
|
||||
assertEquals(soCard.getCollectorNumber(), "10");
|
||||
assertTrue(deckStatsToken.cardRequestHasNoCode());
|
||||
|
||||
// Check that deck section is made effective even if we're currently in Main
|
||||
deckStatsToken = recognizer.recognizeLine(deckstatsCommanderRequest, DeckSection.Main);
|
||||
@@ -2287,6 +2355,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertNotNull(deckStatsToken.getCard());
|
||||
soCard = deckStatsToken.getCard();
|
||||
assertEquals(soCard.getName(), "Sliver Overlord");
|
||||
assertTrue(deckStatsToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
// === Double-Sided Cards
|
||||
@@ -2306,6 +2375,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
PaperCard leftSideCard = cardToken.getCard();
|
||||
assertEquals(leftSideCard.getName(), leftSideRequest);
|
||||
assertEquals(cardToken.getQuantity(), 1);
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Check Right side first
|
||||
cardToken = recognizer.recogniseCardToken(rightSideRequest, null);
|
||||
@@ -2316,6 +2386,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
PaperCard rightSideCard = cardToken.getCard();
|
||||
assertEquals(rightSideCard.getName(), leftSideRequest); // NOTE: this is not a blunder! Back side will result in front side name
|
||||
assertEquals(cardToken.getQuantity(), 1);
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
|
||||
// Check double side
|
||||
cardToken = recognizer.recogniseCardToken(doubleSideRequest, null);
|
||||
@@ -2326,6 +2397,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
PaperCard doubleSideCard = cardToken.getCard();
|
||||
assertEquals(doubleSideCard.getName(), leftSideRequest);
|
||||
assertEquals(cardToken.getQuantity(), 1);
|
||||
assertTrue(cardToken.cardRequestHasNoCode());
|
||||
}
|
||||
|
||||
/*=================================
|
||||
@@ -2928,6 +3000,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertNotNull(token.getCard());
|
||||
assertNotNull(token.getTokenSection());
|
||||
assertEquals(token.getTokenSection(), DeckSection.Sideboard);
|
||||
assertTrue(token.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "SB:Ancestral Recall";
|
||||
token = recognizer.recognizeLine(lineRequest, null);
|
||||
@@ -2937,6 +3010,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertNotNull(token.getCard());
|
||||
assertNotNull(token.getTokenSection());
|
||||
assertEquals(token.getTokenSection(), DeckSection.Sideboard);
|
||||
assertTrue(token.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "Ancestral Recall";
|
||||
token = recognizer.recognizeLine(lineRequest, null);
|
||||
@@ -2944,6 +3018,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertEquals(token.getType(), TokenType.LEGAL_CARD);
|
||||
assertEquals(token.getText(), "Ancestral Recall [VMA] #1");
|
||||
assertNotNull(token.getCard());
|
||||
assertTrue(token.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "* 4 [Counterspell](http://tappedout.nethttp://tappedout.net/mtg-card/counterspell/)";
|
||||
token = recognizer.recognizeLine(lineRequest, null);
|
||||
@@ -2952,6 +3027,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
assertNotNull(token.getText());
|
||||
assertNotNull(token.getCard());
|
||||
assertEquals(token.getQuantity(), 4);
|
||||
assertTrue(token.cardRequestHasNoCode());
|
||||
|
||||
lineRequest = "### Instant (14)";
|
||||
token = recognizer.recognizeLine(lineRequest, null);
|
||||
@@ -2993,6 +3069,7 @@ public class DeckRecognizerTest extends ForgeCardMockTestCase {
|
||||
Token cardToken = tokens.get(2);
|
||||
assertTrue(cardToken.isCardToken());
|
||||
assertNotNull(cardToken.getCard());
|
||||
assertFalse(cardToken.cardRequestHasNoCode());
|
||||
assertEquals(cardToken.getQuantity(), 4);
|
||||
assertEquals(cardToken.getCard().getName(), "Incinerate");
|
||||
assertEquals(cardToken.getCard().getEdition(), "ICE");
|
||||
|
||||
@@ -79,6 +79,7 @@ public class Forge implements ApplicationListener {
|
||||
public static String CJK_Font = "";
|
||||
public static int hoveredCount = 0;
|
||||
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) {
|
||||
app = new Forge();
|
||||
@@ -780,6 +781,7 @@ public class Forge implements ApplicationListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
mouseButtonID = button;
|
||||
return super.touchDown(x, y, pointer, button);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,7 @@ import forge.screens.LoadingOverlay;
|
||||
import forge.screens.match.MatchController;
|
||||
import forge.screens.quest.QuestMenu;
|
||||
import forge.screens.settings.GuiDownloader;
|
||||
import forge.sound.AudioClip;
|
||||
import forge.sound.AudioMusic;
|
||||
import forge.sound.IAudioClip;
|
||||
import forge.sound.IAudioMusic;
|
||||
import forge.sound.*;
|
||||
import forge.toolbox.FOptionPane;
|
||||
import forge.toolbox.GuiChoose;
|
||||
import forge.util.*;
|
||||
@@ -272,7 +269,7 @@ public class GuiMobile implements IGuiBase {
|
||||
|
||||
@Override
|
||||
public IAudioClip createAudioClip(final String filename) {
|
||||
return AudioClip.createClip(ForgeConstants.SOUND_DIR + filename);
|
||||
return AudioClip.createClip(SoundSystem.instance.getSoundDirectory() + filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -288,6 +285,7 @@ public class GuiMobile implements IGuiBase {
|
||||
@Override
|
||||
public void clearImageCache() {
|
||||
ImageCache.clear();
|
||||
ImageKeys.clearMissingCards();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -113,6 +113,7 @@ public class ImageCache {
|
||||
|
||||
public static void clear() {
|
||||
missingIconKeys.clear();
|
||||
ImageKeys.clearMissingCards();
|
||||
}
|
||||
|
||||
public static void disposeTexture(){
|
||||
|
||||
@@ -403,15 +403,26 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
|
||||
addItem(new FMenuItem(localizer.getMessage("lblImportFromClipboard"), Forge.hdbuttons ? FSkinImage.HDIMPORT : FSkinImage.OPEN, new FEventHandler() {
|
||||
@Override
|
||||
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
|
||||
public void run(Deck importedDeck) {
|
||||
getMainDeckPage().setCards(importedDeck.getMain());
|
||||
if (getSideboardPage() != null) {
|
||||
getSideboardPage().setCards(importedDeck.getOrCreate(DeckSection.Sideboard));
|
||||
if (deck != null && importedDeck.hasName()) {
|
||||
deck.setName(importedDeck.getName());
|
||||
lblName.setText(importedDeck.getName());
|
||||
}
|
||||
if (getCommanderPage() != null) {
|
||||
getCommanderPage().setCards(importedDeck.getOrCreate(DeckSection.Commander));
|
||||
if (dialog.createNewDeck()) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -41,11 +41,14 @@ import forge.util.Localizer;
|
||||
|
||||
|
||||
public class FDeckImportDialog extends FDialog {
|
||||
private final Callback<Deck> callback;
|
||||
private Callback<Deck> callback;
|
||||
|
||||
private final FTextArea txtInput = add(new FTextArea(true));
|
||||
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 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
|
||||
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
|
||||
@@ -57,14 +60,14 @@ public class FDeckImportDialog extends FDialog {
|
||||
private final FComboBox<Integer> yearDropdown = add(new FComboBox<>());
|
||||
|
||||
private final boolean showOptions;
|
||||
private final boolean currentDeckIsEmpty;
|
||||
private boolean createNewDeckControl;
|
||||
private final DeckImportController controller;
|
||||
|
||||
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);
|
||||
|
||||
callback = callback0;
|
||||
controller = new DeckImportController(dateTimeCheck, monthDropdown, yearDropdown, replacingDeck);
|
||||
String contents = Forge.getClipboard().getContents();
|
||||
if (contents == null)
|
||||
@@ -84,6 +87,10 @@ public class FDeckImportDialog extends FDialog {
|
||||
|
||||
onlyCoreExpCheck.setSelected(StaticData.instance().isCoreExpansionOnlyFilterSet());
|
||||
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() {
|
||||
@Override
|
||||
@@ -93,21 +100,23 @@ public class FDeckImportDialog extends FDialog {
|
||||
public void run() {
|
||||
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
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (DeckRecognizer.Token token : tokens) {
|
||||
if (TokenType.CARD_FROM_NOT_ALLOWED_SET.equals(token.getType())
|
||||
|| TokenType.CARD_FROM_INVALID_SET.equals(token.getType())
|
||||
|| TokenType.UNKNOWN_CARD.equals(token.getType())
|
||||
|| TokenType.UNSUPPORTED_CARD.equals(token.getType())) {
|
||||
if (sb.length() > 0) {
|
||||
if (token.getType() == TokenType.CARD_FROM_NOT_ALLOWED_SET
|
||||
|| token.getType() == TokenType.CARD_FROM_INVALID_SET
|
||||
|| token.getType() == TokenType.UNKNOWN_CARD
|
||||
|| token.getType() == TokenType.UNSUPPORTED_CARD) {
|
||||
if (sb.length() > 0)
|
||||
sb.append("\n");
|
||||
}
|
||||
sb.append(token.getQuantity()).append(" ").append(token.getText());
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -118,9 +127,9 @@ public class FDeckImportDialog extends FDialog {
|
||||
FThreads.invokeInEdtLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
deck.optimizeMainCardArt();
|
||||
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());
|
||||
StaticData data = StaticData.instance();
|
||||
if (controller.isSmartCardArtEnabled())
|
||||
tokens = controller.optimiseCardArtInTokens();
|
||||
//ensure at least one known card found on clipboard
|
||||
for (DeckRecognizer.Token token : tokens) {
|
||||
if (token.getType() == TokenType.LEGAL_CARD) {
|
||||
@@ -155,6 +165,19 @@ public class FDeckImportDialog extends FDialog {
|
||||
@Override
|
||||
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();
|
||||
setArtPreferenceInController();
|
||||
return;
|
||||
@@ -178,6 +201,12 @@ public class FDeckImportDialog extends FDialog {
|
||||
yearDropdown.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void setCallback(Callback<Deck> callback0){
|
||||
callback = callback0;
|
||||
}
|
||||
|
||||
public boolean createNewDeck(){ return this.createNewDeckControl; }
|
||||
|
||||
@Override
|
||||
public void drawOverlay(Graphics g) {
|
||||
super.drawOverlay(g);
|
||||
@@ -198,7 +227,8 @@ public class FDeckImportDialog extends FDialog {
|
||||
h = monthDropdown.getHeight();
|
||||
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;
|
||||
dateTimeCheck.setBounds(x, y, w, h);
|
||||
y += h + fieldPadding;
|
||||
@@ -208,7 +238,12 @@ public class FDeckImportDialog extends FDialog {
|
||||
yearDropdown.setBounds(x + dropDownWidth + fieldPadding, y, dropDownWidth, h);
|
||||
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;
|
||||
}
|
||||
h = txtInput.getPreferredHeight(w);
|
||||
|
||||
@@ -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*/
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Input;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
|
||||
import forge.Forge;
|
||||
import forge.Graphics;
|
||||
import forge.card.CardRenderer.CardStackPosition;
|
||||
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
|
||||
@Override
|
||||
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
|
||||
FThreads.invokeInEdtLater(new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,7 @@ import forge.screens.TabPageScreen;
|
||||
import forge.screens.TabPageScreen.TabPage;
|
||||
import forge.screens.home.HomeScreen;
|
||||
import forge.screens.match.MatchController;
|
||||
import forge.sound.MusicPlaylist;
|
||||
import forge.sound.SoundSystem;
|
||||
import forge.toolbox.FCheckBox;
|
||||
import forge.toolbox.FGroupList;
|
||||
@@ -612,6 +613,29 @@ public class SettingsPage extends TabPage<SettingsScreen> {
|
||||
localizer.getMessage("nlVibrateAfterLongPress")),
|
||||
6);
|
||||
//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,
|
||||
localizer.getMessage("cbAdjustSoundsVolume"),
|
||||
localizer.getMessage("nlAdjustSoundsVolume"),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#Add one announcement per line
|
||||
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?
|
||||
Innistrad: Midnight Hunt (MID) and Innistrad: Midnight Hunt Commander (MIC) are 100% supported
|
||||
Various improvements have been made to the desktop and mobile user interface to enhance the experience
|
||||
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.
|
||||
Planar Conquest now features a new plane - Forgotten Realms, based on the AFR and AFC sets.
|
||||
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. ***
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
Name:Abrupt Decay
|
||||
ManaCost:B G
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -5,8 +5,8 @@ PT:*/4
|
||||
K:Vigilance
|
||||
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
|
||||
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.
|
||||
SVar:TrigToken:DB$ Token | TokenAmount$ Y | TokenScript$ w_1_1_human | TokenTapped$ True | TokenAttacking$ True | ChoosePlayerOrPlaneswalker$ True | TokenOwner$ You
|
||||
SVar:Y:PlayerCountOpponents$Amount
|
||||
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:DBRepeat:DB$ RepeatEach | RepeatPlayers$ Opponent | ChangeZoneTable$ True | RepeatSubAbility$ DBToken
|
||||
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human | TokenTapped$ True | TokenAttacking$ Remembered | ChoosePlayerOrPlaneswalker$ True | TokenOwner$ You
|
||||
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.
|
||||
|
||||
@@ -8,5 +8,5 @@ K:Protection from white
|
||||
K:Protection from blue
|
||||
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: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.)
|
||||
|
||||
@@ -2,11 +2,10 @@ Name:Altered Ego
|
||||
ManaCost:X 2 G U
|
||||
Types:Creature Shapeshifter
|
||||
PT:0/0
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
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:DBAddCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | ETB$ True | CounterNum$ X
|
||||
SVar:X:Count$xPaid
|
||||
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.
|
||||
@@ -3,6 +3,6 @@ ManaCost:X R
|
||||
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.
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Creature Cat Cleric
|
||||
PT:2/3
|
||||
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.
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -2,7 +2,6 @@ Name:Blurred Mongoose
|
||||
ManaCost:1 G
|
||||
Types:Creature Mongoose
|
||||
PT:2/1
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
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.)
|
||||
|
||||
@@ -2,8 +2,7 @@ Name:Carnage Tyrant
|
||||
ManaCost:4 G G
|
||||
Types:Creature Dinosaur
|
||||
PT:7/6
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
K:Trample
|
||||
K:Hexproof
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/carnage_tyrant.jpg
|
||||
Oracle:This spell can't be countered.\nTrample, hexproof
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Chandra, Awakened Inferno
|
||||
Types:Legendary Planeswalker Chandra
|
||||
ManaCost:4 R R
|
||||
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."
|
||||
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
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Legendary Creature Elder Dragon
|
||||
PT:7/7
|
||||
K:Flash
|
||||
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.
|
||||
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.
|
||||
@@ -1,8 +1,7 @@
|
||||
Name:Combust
|
||||
ManaCost:1 R
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Commence the Endgame
|
||||
ManaCost:4 U U
|
||||
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
|
||||
SVar:DBAmass:DB$ Amass | Num$ X
|
||||
DeckHints:Ability$Amass & Type$Zombie
|
||||
|
||||
@@ -5,5 +5,5 @@ A:SP$ DealDamage | Cost$ X X R | ValidTgts$ Creature,Player,Planeswalker | TgtPr
|
||||
SVar:MaxTgts:PlayerCountPlayers$Amount/Plus.NumCreatures
|
||||
SVar:NumCreatures:Count$Valid Creature,Planeswalker
|
||||
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.)
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Control Win Condition
|
||||
ManaCost:4 U U
|
||||
Types:Creature Whale
|
||||
PT:*/*
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
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 you’ve taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.)
|
||||
SVar:X:Count$YourTurns
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
Name:Counterflux
|
||||
ManaCost:U U R
|
||||
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$ 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.")
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:5 G G
|
||||
Types:Creature Beast
|
||||
PT:6/6
|
||||
K:Kicker:2 G
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
K:Hexproof
|
||||
K:Haste
|
||||
K:etbCounter:P1P1:4:CheckSVar$ WasKicked:If CARDNAME was kicked, it enters the battlefield with four +1/+1 counters on it.
|
||||
|
||||
@@ -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.
|
||||
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
|
||||
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: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.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
Name:Demonic Tutor
|
||||
ManaCost:1 B
|
||||
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).
|
||||
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.
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:G
|
||||
Types:Creature Human Druid
|
||||
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.
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Dovin's Veto
|
||||
ManaCost:W U
|
||||
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.
|
||||
Oracle:This spell can't be countered.\nCounter target noncreature spell.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Dragonlord Dromoka
|
||||
ManaCost:4 G W
|
||||
Types:Legendary Creature Elder Dragon
|
||||
PT:5/7
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
K:Flying
|
||||
K:Lifelink
|
||||
S:Mode$ CantBeCast | ValidCard$ Card | Condition$ PlayerTurn | Caster$ Opponent | Description$ Your opponents can't cast spells during your turn.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Dragonlord's Prerogative
|
||||
ManaCost:4 U U
|
||||
Types:Instant
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -4,6 +4,6 @@ Types:Artifact
|
||||
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.
|
||||
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
|
||||
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.
|
||||
@@ -2,7 +2,7 @@ Name:Emrakul, the Aeons Torn
|
||||
ManaCost:15
|
||||
Types:Legendary Creature Eldrazi
|
||||
PT:15/15
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
K:Flying
|
||||
K:Protection:Spell.nonColorless:Protection from colored spells
|
||||
K:Annihilator:6
|
||||
|
||||
@@ -2,6 +2,6 @@ Name:Exquisite Firecraft
|
||||
ManaCost:1 R R
|
||||
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.
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -4,7 +4,7 @@ Types:Creature Cat Beast
|
||||
PT:4/6
|
||||
K:Vigilance
|
||||
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
|
||||
DeckHints:Ability$LifeGain
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/felidar_sovereign.jpg
|
||||
|
||||
@@ -2,8 +2,7 @@ Name:Followed Footsteps
|
||||
ManaCost:3 U U
|
||||
Types:Enchantment Aura
|
||||
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.
|
||||
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:Picture:http://www.wizards.com/global/images/magic/general/followed_footsteps.jpg
|
||||
SVar:TrigCopy:DB$ CopyPermanent | Defined$ Enchanted | SpellDescription$ At 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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Fry
|
||||
ManaCost:1 R
|
||||
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.
|
||||
AI:RemoveDeck:Random
|
||||
Oracle:This spell can't be countered.\nFry deals 5 damage to target creature or planeswalker that's white or blue.
|
||||
|
||||
@@ -2,8 +2,7 @@ Name:Gaea's Revenge
|
||||
ManaCost:5 G G
|
||||
Types:Creature Elemental
|
||||
PT:8/5
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Giant Opportunity
|
||||
ManaCost:2 G
|
||||
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: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
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:3 G G
|
||||
Types:Creature Insect
|
||||
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.
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -4,6 +4,5 @@ Types:Creature Elk
|
||||
PT:3/3
|
||||
K:Protection from blue
|
||||
K:Protection from black
|
||||
K:CARDNAME can't be countered.
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/great_sable_stag.jpg
|
||||
K:This spell can't be countered.
|
||||
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.)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Heated Debate
|
||||
ManaCost:2 R
|
||||
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.
|
||||
Oracle:This spell can't be countered. (This includes by the ward ability.)\nHeated Debate deals 4 damage to target creature or planeswalker.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Hunter's Mark
|
||||
ManaCost:3 G
|
||||
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.
|
||||
SVar:CostReduction:Count$Compare CheckTgt GE1.3.0
|
||||
SVar:CheckTgt:TargetedObjects$Valid Card.Blue+YouDontCtrl
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Inescapable Blaze
|
||||
ManaCost:4 R R
|
||||
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.
|
||||
Oracle:This spell can't be countered.\nInescapable Blaze deals 6 damage to any target.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Inferno of the Star Mounts
|
||||
ManaCost:4 R R
|
||||
Types:Legendary Creature Dragon
|
||||
PT:6/6
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
K:Flying
|
||||
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.
|
||||
|
||||
@@ -2,9 +2,8 @@ Name:Isao, Enlightened Bushi
|
||||
ManaCost:2 G
|
||||
Types:Legendary Creature Human Samurai
|
||||
PT:2/1
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
K:Bushido:2
|
||||
A:AB$ Regenerate | Cost$ 2 | ValidTgts$ Samurai | TgtPrompt$ Select target Samurai. | SpellDescription$ Regenerate target 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.
|
||||
|
||||
@@ -2,8 +2,7 @@ Name:Kavu Chameleon
|
||||
ManaCost:3 G G
|
||||
Types:Creature Kavu
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Koma, Cosmos Serpent
|
||||
ManaCost:3 G G U U
|
||||
Types:Legendary Creature Serpent
|
||||
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.
|
||||
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ komas_coil | TokenOwner$ You
|
||||
A:AB$ Charm | Cost$ Sac<1/Serpent.Other/another Serpent> | Choices$ DBTap,DBPump
|
||||
|
||||
@@ -2,6 +2,5 @@ Name:Last Word
|
||||
ManaCost:2 U U
|
||||
Types:Instant
|
||||
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.
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/last_word.jpg
|
||||
K:This spell can't be countered.
|
||||
Oracle:This spell can't be countered.\nCounter target spell.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Lightning Mare
|
||||
ManaCost:R R
|
||||
Types:Creature Elemental Horse
|
||||
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.
|
||||
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.
|
||||
@@ -2,7 +2,7 @@ Name:Loxodon Smiter
|
||||
ManaCost:1 G W
|
||||
Types:Creature Elephant Soldier
|
||||
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.
|
||||
SVar:SurpriseETB:DB$ ChangeZone | DefinedPlayer$ ReplacedPlayer | Defined$ ReplacedCard | Origin$ Hand | Destination$ Battlefield
|
||||
SVar:DiscardMeByOpp:2
|
||||
|
||||
@@ -5,7 +5,7 @@ PT:2/1
|
||||
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.
|
||||
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:PlayMain1:TRUE
|
||||
DeckNeeds:Type$Dwarf
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name:Magic Missile
|
||||
ManaCost:1 R R
|
||||
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.
|
||||
Oracle:This spell can't be countered.\nMagic Missile deals 3 damage divided as you choose among one, two, or three targets.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Mistcutter Hydra
|
||||
ManaCost:X G
|
||||
Types:Creature Hydra
|
||||
PT:0/0
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
K:Haste
|
||||
K:Protection from blue
|
||||
K:etbCounter:P1P1:X
|
||||
|
||||
@@ -2,9 +2,7 @@ Name:Nahiri, the Harbinger
|
||||
ManaCost:2 R W
|
||||
Types:Legendary Planeswalker Nahiri
|
||||
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.
|
||||
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
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.
|
||||
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.
|
||||
SVar:DBPump:DB$ Animate | Keywords$ Haste | Duration$ Permanent | AtEOT$ Hand | Defined$ Remembered | SubAbility$ DBCleanup
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Nezahal, Primal Tide
|
||||
ManaCost:5 U U
|
||||
Types:Legendary Creature Elder Dinosaur
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:U U U R R R
|
||||
Types:Legendary Creature Dragon Wizard
|
||||
PT:5/5
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -2,6 +2,5 @@ Name:Obliterate
|
||||
ManaCost:6 R R
|
||||
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.
|
||||
K:CARDNAME can't be countered.
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/obliterate.jpg
|
||||
K:This spell can't be countered.
|
||||
Oracle:This spell can't be countered.\nDestroy all artifacts, creatures, and lands. They can't be regenerated.
|
||||
|
||||
@@ -3,11 +3,9 @@ ManaCost:1 B R
|
||||
Types:Legendary Creature Vampire Knight
|
||||
PT:3/3
|
||||
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.
|
||||
SVar:TrigDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBPutCounter
|
||||
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 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBAnimate
|
||||
SVar:DBAnimate:DB$ Animate | Defined$ TriggeredCard | Types$ Vampire | Duration$ Permanent | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
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:AB$ PutCounter | Cost$ Discard<1/Card> | Defined$ TriggeredCardLKICopy | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBPump
|
||||
SVar:DBPump:DB$ Pump | Defined$ TriggeredCard | KW$ Haste | SubAbility$ DBAnimate
|
||||
SVar:DBAnimate:DB$ Animate | Defined$ TriggeredCard | Types$ Vampire | Duration$ Permanent
|
||||
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.
|
||||
|
||||
@@ -3,6 +3,5 @@ ManaCost:2 U U
|
||||
Types:Instant
|
||||
K:Surge:U U
|
||||
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.
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/overwhelming_denial.jpg
|
||||
K:This spell can't be countered.
|
||||
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.
|
||||
|
||||
@@ -3,7 +3,7 @@ ManaCost:3 R
|
||||
Types:Creature Elemental Cat
|
||||
PT:2/3
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -4,7 +4,6 @@ Types:Creature Leviathan
|
||||
PT:6/7
|
||||
K:Flash
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Petrified Wood-Kin
|
||||
ManaCost:6 G
|
||||
Types:Creature Elemental Warrior
|
||||
PT:3/3
|
||||
K:CARDNAME can't be countered.
|
||||
K:This spell can't be countered.
|
||||
K:Bloodthirst:X
|
||||
K:Protection:Instant:Protection from instants
|
||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/petrified_wood_kin.jpg
|
||||
|
||||
@@ -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: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$ 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
|
||||
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.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user