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

View File

@@ -714,7 +714,24 @@ public class AiController {
// Check a predefined condition
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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)
*/

View File

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

View File

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

View File

@@ -354,7 +354,7 @@ public class StaticData {
}
/**
* Determins whether the input String corresponds to an MTG Card Name (in any available card database)
* Determines whether the input String corresponds to an MTG Card Name (in any available card database)
* @param cardName Name of the Card to verify (CASE SENSITIVE)
* @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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -201,6 +201,9 @@ public class DeckImport<TModel extends DeckBase> extends FDialog {
private final FCheckBox cardArtPrefHasFilterCheckBox = new FCheckBox(Localizer.getInstance()
.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 "";
}

View File

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

View File

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

View File

@@ -128,6 +128,8 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
private final FComboBoxPanel<String> cbpCardArtFormat = new FComboBoxPanel<>(localizer.getMessage("cbpCardArtFormat")+":");
private final FComboBoxPanel<String> 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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -403,15 +403,26 @@ public class FDeckEditor extends TabPageScreen<FDeckEditor> {
addItem(new FMenuItem(localizer.getMessage("lblImportFromClipboard"), Forge.hdbuttons ? FSkinImage.HDIMPORT : FSkinImage.OPEN, new FEventHandler() {
@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));
}
}
});

View File

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

View File

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

View File

@@ -6,8 +6,10 @@ import java.util.List;
import java.util.Map;
import 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,5 +5,5 @@ A:SP$ DealDamage | Cost$ X X R | ValidTgts$ Creature,Player,Planeswalker | TgtPr
SVar:MaxTgts:PlayerCountPlayers$Amount/Plus.NumCreatures
SVar: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.)

View File

@@ -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 youve taken this game. (If this is in your deck, please keep track of your turns. This means you, Mark.)
SVar:X:Count$YourTurns

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ Types:Sorcery
A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | ConditionCheckSVar$ Y | ConditionSVarCompare$ GE1 | RememberDamaged$ True | ReplaceDyingDefined$ Remembered | SubAbility$ DBDemonfire | SpellDescription$ CARDNAME deals X damage to any target. If a creature dealt damage this way would die this turn, exile it instead.
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ PT:2/1
S:Mode$ Continuous | Affected$ Dwarf.Other+YouCtrl | AddPower$ 1 | Description$ Other Dwarves you control get +1/+0.
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | RememberObjects$ Remembered
SVar:STPlay:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ Until the end of your next turn, you may play that card.
SVar: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