diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java
index 10c511ed5c6..000443c6304 100644
--- a/forge-ai/src/main/java/forge/ai/AiAttackController.java
+++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java
@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
@@ -42,7 +42,6 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.combat.GlobalAttackRestrictions;
import forge.game.keyword.Keyword;
-import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
@@ -60,7 +59,7 @@ import forge.util.collect.FCollectionView;
*
* ComputerUtil_Attack2 class.
*
- *
+ *
* @author Forge
* @version $Id$
*/
@@ -72,10 +71,10 @@ public class AiAttackController {
private List oppList; // holds human player creatures
private List myList; // holds computer creatures
-
+
private final Player ai;
private Player defendingOpponent;
-
+
private int aiAggression = 0; // added by Masher, how aggressive the ai is attack will be depending on circumstances
private final boolean nextTurn;
@@ -108,7 +107,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, Card attacker) {
this.ai = ai;
- this.defendingOpponent = choosePreferredDefenderPlayer(ai);
+ this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>();
@@ -118,7 +117,7 @@ public class AiAttackController {
this.blockers = getPossibleBlockers(oppList, this.attackers);
this.nextTurn = false;
} // overloaded constructor to evaluate single specified attacker
-
+
public static List getOpponentCreatures(final Player defender) {
List defenders = new ArrayList<>(defender.getCreaturesInPlay());
Predicate canAnimate = new Predicate() {
@@ -133,7 +132,7 @@ public class AiAttackController {
}
for (SpellAbility sa : c.getSpellAbilities()) {
if (sa.getApi() == ApiType.Animate) {
- if (ComputerUtilCost.canPayCost(sa, defender)
+ if (ComputerUtilCost.canPayCost(sa, defender)
&& sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
Card animatedCopy = AnimateAi.becomeAnimated(c, sa);
defenders.add(animatedCopy);
@@ -143,7 +142,7 @@ public class AiAttackController {
}
return defenders;
}
-
+
public void removeBlocker(Card blocker) {
this.oppList.remove(blocker);
}
@@ -200,7 +199,7 @@ public class AiAttackController {
*
* isEffectiveAttacker.
*
- *
+ *
* @param attacker
* a {@link forge.game.card.Card} object.
* @param combat
@@ -501,7 +500,7 @@ public class AiAttackController {
// Conservative prediction for vehicles: the AI tries to acknowledge the fact that
// at least one creature will tap to crew a blocking vehicle when predicting if an
// alpha strike for lethal is viable
- int maxBlockersAfterCrew = remainingBlockers.size();
+ int maxBlockersAfterCrew = remainingBlockers.size();
for (Card c : this.blockers) {
CardTypeView cardType = c.getCurrentState().getType();
CardCollectionView oppBattlefield = c.getController().getCardsIn(ZoneType.Battlefield);
@@ -514,7 +513,7 @@ public class AiAttackController {
} else if (c.getName().equals("Peacewalker Colossus")) {
// can activate other vehicles for {1}{W}
// TODO: the AI should ideally predict how many times it can activate
- // for now, unless the opponent is tapped out, break at this point
+ // for now, unless the opponent is tapped out, break at this point
// and do not predict the blocker limit (which is safer)
if (!CardLists.filter(oppBattlefield, Predicates.and(CardPredicates.Presets.UNTAPPED, CardPredicates.Presets.LANDS)).isEmpty()) {
maxBlockersAfterCrew = Integer.MAX_VALUE;
@@ -600,7 +599,7 @@ public class AiAttackController {
maxBlockersAfterCrew--;
}
unblockedAttackers.addAll(remainingAttackers);
-
+
int trampleDamage = 0;
for (Card attacker : blockedAttackers) {
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
@@ -674,7 +673,7 @@ public class AiAttackController {
*
* Getter for the field attackers.
*
- *
+ *
* @return a {@link forge.game.combat.Combat} object.
*/
public final void declareAttackers(final Combat combat) {
@@ -742,14 +741,9 @@ public class AiAttackController {
// TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack
mustAttack = true;
} else {
- for (KeywordInterface inst : attacker.getKeywords()) {
- String s = inst.getOriginal();
- if (s.equals("CARDNAME attacks each turn if able.")
- || s.startsWith("CARDNAME attacks specific player each combat if able")
- || s.equals("CARDNAME attacks each combat if able.")) {
- mustAttack = true;
- break;
- }
+ // TODO move to static Ability
+ if (attacker.hasKeyword("CARDNAME attacks each combat if able.") || attacker.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
+ mustAttack = true;
}
}
if (mustAttack || attacker.getController().getMustAttackEntity() != null || attacker.getController().getMustAttackEntityThisTurn() != null) {
@@ -799,7 +793,7 @@ public class AiAttackController {
}
}
}
-
+
// Exalted
if (combat.getAttackers().isEmpty()) {
boolean exalted = ai.countExaltedBonus() > 2;
@@ -857,7 +851,7 @@ public class AiAttackController {
// examine the potential forces
final List nextTurnAttackers = new ArrayList<>();
int candidateCounterAttackDamage = 0;
-
+
final Player opp = this.defendingOpponent;
// get the potential damage and strength of the AI forces
final List candidateAttackers = new ArrayList<>();
@@ -1115,7 +1109,7 @@ public class AiAttackController {
*
* getAttack.
*
- *
+ *
* @param c
* a {@link forge.game.card.Card} object.
* @return a int.
@@ -1134,7 +1128,7 @@ public class AiAttackController {
*
* shouldAttack.
*
- *
+ *
* @param attacker
* a {@link forge.game.card.Card} object.
* @param defenders
@@ -1179,7 +1173,7 @@ public class AiAttackController {
}
boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator");
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
- boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
+ boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
|| "Blocked".equals(attacker.getSVar("HasAttackEffect"));
// contains only the defender's blockers that can actually block the attacker
@@ -1201,13 +1195,9 @@ public class AiAttackController {
int defPower = CardLists.getTotalPower(validBlockers, true, false);
if (!hasCombatEffect) {
- for (KeywordInterface inst : attacker.getKeywords()) {
- String keyword = inst.getOriginal();
- if (keyword.equals("Wither") || keyword.equals("Infect")
- || keyword.equals("Lifelink") || keyword.startsWith("Afflict")) {
- hasCombatEffect = true;
- break;
- }
+ if (attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT)
+ || attacker.hasKeyword(Keyword.LIFELINK) || attacker.hasKeyword(Keyword.AFFLICT)) {
+ hasCombatEffect = true;
}
}
@@ -1262,7 +1252,7 @@ public class AiAttackController {
}
}
}
-
+
if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
canKillAllDangerous = false;
canBeKilled = true;
@@ -1272,7 +1262,7 @@ public class AiAttackController {
} else if ((canKillAllDangerous || !canBeKilled) && ComputerUtilCard.canBeBlockedProfitably(defendingOpponent, attacker)) {
canKillAllDangerous = false;
canBeKilled = true;
- }
+ }
// if the creature cannot block and can kill all opponents they might as
// well attack, they do nothing staying back
@@ -1286,7 +1276,7 @@ public class AiAttackController {
return true;
}
- if (numberOfPossibleBlockers > 2
+ if (numberOfPossibleBlockers > 2
|| (numberOfPossibleBlockers >= 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1, this.defendingOpponent))
|| (numberOfPossibleBlockers == 2 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 2, this.defendingOpponent))) {
canBeBlocked = true;
@@ -1416,7 +1406,7 @@ public class AiAttackController {
return exerters;
}
-
+
/**
* Find a protection type that will make an attacker unblockable.
* @param sa ability belonging to ApiType.Protection
@@ -1485,10 +1475,10 @@ public class AiAttackController {
CardCollection attSorted = new CardCollection(attackersLeft);
CardCollection attUnsafe = new CardCollection();
CardLists.sortByToughnessDesc(attSorted);
-
+
int i = numForcedAttackers;
int refPowerValue = 0; // Aggro profiles do not account for the possible blockers' power, conservative profiles do.
-
+
if (!playAggro && this.blockers.size() > 0) {
// Conservative play: check to ensure that the card can't be killed off while damaged
// TODO: currently sorting a copy of this.blockers, but it looks safe to operate on this.blockers directly?
@@ -1498,7 +1488,7 @@ public class AiAttackController {
CardLists.sortByPowerDesc(blkSorted);
refPowerValue += blkSorted.get(0).getCurrentPower();
}
-
+
for (Card cre : attSorted) {
i++;
if (i + refPowerValue >= cre.getCurrentToughness()) {
@@ -1507,7 +1497,7 @@ public class AiAttackController {
continue;
}
}
-
+
attackersLeft.removeAll(attUnsafe);
}
diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java
index 56a8514c9d8..87701edbc94 100644
--- a/forge-ai/src/main/java/forge/ai/AiBlockController.java
+++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java
@@ -38,6 +38,7 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
+import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
@@ -183,9 +184,7 @@ public class AiBlockController {
List currentAttackers = new ArrayList<>(attackersLeft);
for (final Card attacker : attackersLeft) {
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
- || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
- || attacker.hasKeyword(Keyword.MENACE)) {
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) {
continue;
}
@@ -296,8 +295,7 @@ public class AiBlockController {
// 6. Blockers that don't survive until the next turn anyway
for (final Card attacker : attackersLeft) {
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)
- || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) {
continue;
}
@@ -323,7 +321,17 @@ public class AiBlockController {
attackersLeft = (new ArrayList<>(currentAttackers));
}
- static final Predicate rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT"));
+ private Predicate rampagesOrNeedsManyToBlock(final Combat combat) {
+ return Predicates.or(CardPredicates.hasKeyword(Keyword.RAMPAGE), new Predicate() {
+
+ @Override
+ public boolean apply(Card input) {
+ // select creature that has a max blocker
+ return StaticAbilityCantAttackBlock.getMinMaxBlocker(input, combat.getDefenderPlayerByAttacker(input)).getRight() < Integer.MAX_VALUE;
+ }
+
+ });
+ }
// Good Gang Blocks means a good trade or no trade
/**
@@ -334,7 +342,7 @@ public class AiBlockController {
* @param combat a {@link forge.game.combat.Combat} object.
*/
private void makeGangBlocks(final Combat combat) {
- List currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock));
+ List currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
List blockers;
// Try to block an attacker without first strike with a gang of first strikers
@@ -528,7 +536,7 @@ public class AiBlockController {
// Try to block a Menace attacker with two blockers, neither of which will die
for (final Card attacker : attackersLeft) {
- if (!attacker.hasKeyword(Keyword.MENACE) && !attacker.hasStartOfKeyword("CantBeBlockedByAmount LT2")) {
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() <= 1) {
continue;
}
@@ -584,9 +592,7 @@ public class AiBlockController {
List killingBlockers;
for (final Card attacker : attackersLeft) {
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
- || attacker.hasKeyword(Keyword.MENACE)
- || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) {
continue;
}
if (ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
@@ -635,10 +641,8 @@ public class AiBlockController {
Card attacker = attackers.get(0);
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
- || attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
- || attacker.hasKeyword(Keyword.MENACE)
|| ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
attackers.remove(0);
makeChumpBlocks(combat, attackers);
@@ -686,9 +690,7 @@ public class AiBlockController {
List currentAttackers = new ArrayList<>(attackersLeft);
for (final Card attacker : currentAttackers) {
- if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
- && !attacker.hasKeyword(Keyword.MENACE)
- && !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() <= 1) {
continue;
}
List possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
@@ -720,14 +722,14 @@ public class AiBlockController {
List chumpBlockers;
List tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
- tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
+ tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
// TODO - should check here for a "rampage-like" trigger that replaced the keyword:
// "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
for (final Card attacker : tramplingAttackers) {
- if (((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) && !combat.isBlocked(attacker))
+ if (((StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, combat.getDefenderPlayerByAttacker(attacker)).getLeft() > 1) && !combat.isBlocked(attacker))
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
@@ -751,7 +753,7 @@ public class AiBlockController {
private void reinforceBlockersToKill(final Combat combat) {
List safeBlockers;
List blockers;
- List targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock));
+ List targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock(combat)));
// TODO - should check here for a "rampage-like" trigger that replaced
// the keyword: "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it."
@@ -1076,8 +1078,7 @@ public class AiBlockController {
}
// assign blockers that have to block
- chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able.");
- chumpBlockers.addAll(CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able."));
+ chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each combat if able.");
// if an attacker with lure attacks - all that can block
for (final Card blocker : blockersLeft) {
if (CombatUtil.mustBlockAnAttacker(blocker, combat, null)) {
@@ -1091,7 +1092,6 @@ public class AiBlockController {
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker)
&& (CombatUtil.mustBlockAnAttacker(blocker, combat, null)
- || blocker.hasKeyword("CARDNAME blocks each turn if able.")
|| blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
combat.addBlocker(attacker, blocker);
if (blocker.getMustBlockCards() != null) {
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
index 0a4b0174478..e849022b9c9 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
@@ -3025,7 +3025,7 @@ public class ComputerUtil {
}
}
if (!containsAttacker) {
- return false;
+ continue;
}
AiBlockController block = new AiBlockController(ai);
block.assignBlockersForCombat(combat);
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
index 0e8c2ea385e..5a069e44a4a 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
@@ -1654,10 +1654,11 @@ public class ComputerUtilCard {
Card pumped = CardFactory.copyCard(c, false);
pumped.setSickness(c.hasSickness());
final long timestamp = c.getGame().getNextTimestamp();
- final List kws = new ArrayList<>();
+ final List kws = Lists.newArrayList();
+ final List hiddenKws = Lists.newArrayList();
for (String kw : keywords) {
if (kw.startsWith("HIDDEN")) {
- pumped.addHiddenExtrinsicKeyword(kw);
+ hiddenKws.add(kw.substring(7));
} else {
kws.add(kw);
}
@@ -1686,7 +1687,13 @@ public class ComputerUtilCard {
pumped.addNewPT(c.getCurrentPower(), c.getCurrentToughness(), timestamp);
pumped.setPTBoost(c.getPTBoostTable());
pumped.addPTBoost(power + berserkPower, toughness, timestamp, 0);
- pumped.addChangedCardKeywords(kws, null, false, false, timestamp, 0);
+
+ if (!kws.isEmpty()) {
+ pumped.addChangedCardKeywords(kws, null, false, false, timestamp, 0);
+ }
+ if (!hiddenKws.isEmpty()) {
+ pumped.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKws);
+ }
Set types = c.getCounters().keySet();
for(CounterType ct : types) {
pumped.addCounterFireNoEvents(ct, c.getCounters(ct), ai, sa, true, null);
@@ -1699,14 +1706,10 @@ public class ComputerUtilCard {
KeywordCollection copiedKeywords = new KeywordCollection();
copiedKeywords.insertAll(pumped.getKeywords());
List toCopy = Lists.newArrayList();
- for (KeywordInterface k : c.getKeywords()) {
+ for (KeywordInterface k : c.getUnhiddenKeywords()) {
KeywordInterface copiedKI = k.copy(c, true);
if (!copiedKeywords.contains(copiedKI.getOriginal())) {
- if (copiedKI.getHidden()) {
- pumped.addHiddenExtrinsicKeyword(copiedKI);
- } else {
- toCopy.add(copiedKI);
- }
+ toCopy.add(copiedKI);
}
}
final long timestamp2 = c.getGame().getNextTimestamp(); //is this necessary or can the timestamp be re-used?
@@ -1744,7 +1747,7 @@ public class ComputerUtilCard {
if (!stAb.hasParam("AddPower") && !stAb.hasParam("AddToughness")) {
continue;
}
- if (!vCard.isValid(stAb.getParam("Affected").split(","), c.getController(), c, stAb)) {
+ if (!stAb.matchesValidParam("Affected", vCard)) {
continue;
}
int att = 0;
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
index b025e10deca..1513b2c917b 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
@@ -110,7 +110,17 @@ public class ComputerUtilCombat {
return false;
}
- for (final KeywordInterface inst : attacker.getKeywords()) {
+ // TODO replace with Static Ability
+ for (final String keyword : attacker.getHiddenExtrinsicKeywords()) {
+ if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
+ final String defined = keyword.split(":")[1];
+ final Player player = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
+ if (!defender.equals(player)) {
+ return false;
+ }
+ }
+ }
+ for (final KeywordInterface inst : attacker.getKeywords(Keyword.UNDEFINED)) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
final String defined = keyword.split(":")[1];
diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
index 201f1436785..252800e9338 100644
--- a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
+++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
@@ -8,7 +8,6 @@ import forge.game.card.Card;
import forge.game.card.CounterEnumType;
import forge.game.cost.CostPayEnergy;
import forge.game.keyword.Keyword;
-import forge.game.keyword.KeywordInterface;
import forge.game.spellability.SpellAbility;
public class CreatureEvaluator implements Function {
@@ -35,16 +34,15 @@ public class CreatureEvaluator implements Function {
}
int power = getEffectivePower(c);
final int toughness = getEffectiveToughness(c);
- for (KeywordInterface kw : c.getKeywords()) {
- String keyword = kw.getOriginal();
- if (keyword.equals("Prevent all combat damage that would be dealt by CARDNAME.")
- || keyword.equals("Prevent all damage that would be dealt by CARDNAME.")
- || keyword.equals("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
- || keyword.equals("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
- power = 0;
- break;
- }
+
+ // TODO replace with ReplacementEffect checks
+ if (c.hasKeyword("Prevent all combat damage that would be dealt by CARDNAME.")
+ || c.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")
+ || c.hasKeyword("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
+ || c.hasKeyword("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
+ power = 0;
}
+
if (considerPT) {
value += addValue(power * 15, "power");
value += addValue(toughness * 10, "toughness: " + toughness);
@@ -157,8 +155,7 @@ public class CreatureEvaluator implements Function {
}
if (c.hasKeyword("CARDNAME can't block.")) {
value -= subValue(10, "cant-block");
- } else if (c.hasKeyword("CARDNAME attacks each turn if able.")
- || c.hasKeyword("CARDNAME attacks each combat if able.")) {
+ } else if (c.hasKeyword("CARDNAME attacks each combat if able.")) {
value -= subValue(10, "must-attack");
} else if (c.hasStartOfKeyword("CARDNAME attacks specific player each combat if able")) {
value -= subValue(10, "must-attack-player");
diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java
index 93fc295c084..628345369fa 100644
--- a/forge-ai/src/main/java/forge/ai/GameState.java
+++ b/forge-ai/src/main/java/forge/ai/GameState.java
@@ -1043,7 +1043,12 @@ public abstract class GameState {
return;
}
- sa = c.getFirstSpellAbility();
+ if (!c.getName().equals(spellDef) && c.hasAlternateState() && spellDef.equals(c.getAlternateState().getName())) {
+ sa = c.getAlternateState().getFirstSpellAbility();
+ } else {
+ sa = c.getFirstSpellAbility();
+ }
+
sa.setActivatingPlayer(activator);
handleScriptedTargetingForSA(game, sa, tgtID);
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
index c8cd1122060..5229a00e729 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
@@ -16,7 +16,6 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.ai.AiAttackController;
-import forge.ai.AiBlockController;
import forge.ai.AiCardMemory;
import forge.ai.AiController;
import forge.ai.AiProps;
@@ -642,16 +641,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
* @return Card
*/
private static Card chooseCreature(final Player ai, CardCollection list) {
- // Creating a new combat for testing purposes.
- final Player opponent = ai.getWeakestOpponent();
- Combat combat = new Combat(opponent);
- for (Card att : opponent.getCreaturesInPlay()) {
- combat.addAttacker(att, ai);
- }
- AiBlockController block = new AiBlockController(ai);
- block.assignBlockersForCombat(combat);
-
- if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
+ if (ComputerUtil.aiLifeInDanger(ai, false, 0)) {
// need something AI can cast now
ComputerUtilCard.sortByEvaluateCreature(list);
for (Card c : list) {
@@ -1099,7 +1089,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false;
}
-
if (!sa.hasParam("AITgtOwnCards")) {
list = CardLists.filterControlledBy(list, ai.getOpponents());
list = CardLists.filter(list, new Predicate() {
@@ -1819,7 +1808,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
source, sa);
list = CardLists.filterControlledBy(list, aiPlayer.getOpponents());
if (list.isEmpty()) {
- return false; // no valid targets
+ return false; // no valid targets
}
Map> data = Maps.newHashMap();
diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java
index 7b7ee50f573..b2249e45ebc 100644
--- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java
@@ -137,7 +137,6 @@ public class EffectAi extends SpellAbilityAi {
}
randomReturn = true;
} else if (logic.equals("Evasion")) {
-
if (!phase.isPlayerTurn(ai)) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
index 5950f124f0f..58d4cea4fbe 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
@@ -24,7 +24,6 @@ import forge.game.card.CardFactory;
import forge.game.card.CounterType;
import forge.game.card.token.TokenInfo;
import forge.game.combat.Combat;
-import forge.game.keyword.KeywordInterface;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -295,8 +294,8 @@ public class GameCopier {
newCard.setChangedCardNames(c.getChangedCardNames());
// TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
- for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
- newCard.addHiddenExtrinsicKeyword(kw);
+ //for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
+ // newCard.addHiddenExtrinsicKeyword(kw);
if (c.isTapped()) {
newCard.setTapped(true);
}
diff --git a/forge-core/src/main/java/forge/ImageKeys.java b/forge-core/src/main/java/forge/ImageKeys.java
index e825c09750a..bee1a0743ec 100644
--- a/forge-core/src/main/java/forge/ImageKeys.java
+++ b/forge-core/src/main/java/forge/ImageKeys.java
@@ -65,6 +65,10 @@ public final class ImageKeys {
return tokenKey.substring(ImageKeys.TOKEN_PREFIX.length());
}
+ private static final Map cachedCards = new HashMap<>(50000);
+ public static File getCachedCardsFile(String key) {
+ return cachedCards.get(key);
+ }
public static File getImageFile(String key) {
if (StringUtils.isEmpty(key))
return null;
@@ -97,101 +101,142 @@ public final class ImageKeys {
dir = CACHE_CARD_PICS_DIR;
}
- File file = findFile(dir, filename);
- if (file != null) { return file; }
+ File cachedFile = cachedCards.get(filename);
+ if (cachedFile != null) {
+ return cachedFile;
+ } else {
+ File file = findFile(dir, filename);
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
- // AE -> Ae and Ae -> AE for older cards with different file names
- // on case-sensitive file systems
- if (filename.contains("Ae")) {
- file = findFile(dir, TextUtil.fastReplace(filename, "Ae", "AE"));
- if (file != null) { return file; }
- } else if (filename.contains("AE")) {
- file = findFile(dir, TextUtil.fastReplace(filename, "AE", "Ae"));
- if (file != null) { return file; }
- }
- //try fullborder...
- if (filename.contains(".full")) {
- String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder");
- file = findFile(dir, fullborderFile);
- if (file != null) { return file; }
- // if there's a 1st art variant try without it for .fullborder images
- file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
- if (file != null) { return file; }
- // if there's a 1st art variant try with it for .fullborder images
- file = findFile(dir, fullborderFile.replaceAll("[0-9]*.fullborder", "1.fullborder"));
- if (file != null) { return file; }
- // if there's an art variant try without it for .full images
- file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
- if (file != null) { return file; }
- // if there's a 1st art variant try with it for .full images
- file = findFile(dir, filename.replaceAll("[0-9]*.full", "1.full"));
- if (file != null) { return file; }
- //setlookup
- if (!StaticData.instance().getSetLookup().isEmpty()) {
- for (String setKey : StaticData.instance().getSetLookup().keySet()) {
- if (filename.startsWith(setKey)) {
- for (String setLookup : StaticData.instance().getSetLookup().get(setKey)) {
- //.fullborder lookup
- file = findFile(dir, TextUtil.fastReplace(fullborderFile, setKey, getSetFolder(setLookup)));
- if (file != null) { return file; }
- file = findFile(dir, TextUtil.fastReplace(fullborderFile, setKey, getSetFolder(setLookup)).replaceAll("[0-9]*.fullborder", "1.fullborder"));
- if (file != null) { return file; }
- //.full lookup
- file = findFile(dir, TextUtil.fastReplace(filename, setKey, getSetFolder(setLookup)));
- if (file != null) { return file; }
- file = findFile(dir, TextUtil.fastReplace(filename, setKey, getSetFolder(setLookup)).replaceAll("[0-9]*.full", "1.full"));
- if (file != null) { return file; }
+ // AE -> Ae and Ae -> AE for older cards with different file names
+ // on case-sensitive file systems
+ if (filename.contains("Ae")) {
+ file = findFile(dir, TextUtil.fastReplace(filename, "Ae", "AE"));
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ } else if (filename.contains("AE")) {
+ file = findFile(dir, TextUtil.fastReplace(filename, "AE", "Ae"));
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ }
+ //try fullborder...
+ if (filename.contains(".full")) {
+ String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder");
+ file = findFile(dir, fullborderFile);
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ // if there's a 1st art variant try without it for .fullborder images
+ file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ // if there's a 1st art variant try with it for .fullborder images
+ file = findFile(dir, fullborderFile.replaceAll("[0-9]*.fullborder", "1.fullborder"));
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ // if there's an art variant try without it for .full images
+ file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ // if there's a 1st art variant try with it for .full images
+ file = findFile(dir, filename.replaceAll("[0-9]*.full", "1.full"));
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ //setlookup
+ file = setLookUpFile(filename, fullborderFile);
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ }
+ //if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder
+ if (!filename.contains(".full")) {
+ file = findFile(dir, TextUtil.addSuffix(filename,".full"));
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ file = findFile(dir, TextUtil.addSuffix(filename,".fullborder"));
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ }
+ if (dir.equals(CACHE_TOKEN_PICS_DIR)) {
+ int index = filename.lastIndexOf('_');
+ if (index != -1) {
+ String setlessFilename = filename.substring(0, index);
+ String setCode = filename.substring(index + 1);
+ // try with upper case set
+ file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase());
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ // try with lower case set
+ file = findFile(dir, setlessFilename + "_" + setCode.toLowerCase());
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ // try without set name
+ file = findFile(dir, setlessFilename);
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ // if there's an art variant try without it
+ if (setlessFilename.matches(".*[0-9]*$")) {
+ file = findFile(dir, setlessFilename.replaceAll("[0-9]*$", ""));
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
}
}
}
+ } else if (filename.contains("/")) {
+ String setlessFilename = filename.substring(filename.indexOf('/') + 1);
+ file = findFile(dir, setlessFilename);
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+
+ if (setlessFilename.contains(".full")) {
+ //try fullborder
+ String fullborderFile = TextUtil.fastReplace(setlessFilename, ".full", ".fullborder");
+ file = findFile(dir, fullborderFile);
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ // try lowering the art index to the minimum for regular cards
+ file = findFile(dir, setlessFilename.replaceAll("[0-9]*[.]full", "1.full"));
+ if (file != null) {
+ cachedCards.put(filename, file);
+ return file;
+ }
+ }
}
}
- //if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder
- if (!filename.contains(".full")) {
- file = findFile(dir, TextUtil.addSuffix(filename,".full"));
- if (file != null) { return file; }
- file = findFile(dir, TextUtil.addSuffix(filename,".fullborder"));
- if (file != null) { return file; }
- }
-
- if (dir.equals(CACHE_TOKEN_PICS_DIR)) {
- int index = filename.lastIndexOf('_');
- if (index != -1) {
- String setlessFilename = filename.substring(0, index);
- String setCode = filename.substring(index + 1);
- // try with upper case set
- file = findFile(dir, setlessFilename + "_" + setCode.toUpperCase());
- if (file != null) { return file; }
- // try with lower case set
- file = findFile(dir, setlessFilename + "_" + setCode.toLowerCase());
- if (file != null) { return file; }
- // try without set name
- file = findFile(dir, setlessFilename);
- if (file != null) { return file; }
- // if there's an art variant try without it
- if (setlessFilename.matches(".*[0-9]*$")) {
- file = findFile(dir, setlessFilename.replaceAll("[0-9]*$", ""));
- if (file != null) { return file; }
- }
- }
- } else if (filename.contains("/")) {
- String setlessFilename = filename.substring(filename.indexOf('/') + 1);
- file = findFile(dir, setlessFilename);
- if (file != null) { return file; }
-
- if (setlessFilename.contains(".full")) {
- //try fullborder
- String fullborderFile = TextUtil.fastReplace(setlessFilename, ".full", ".fullborder");
- file = findFile(dir, fullborderFile);
- if (file != null) { return file; }
- // try lowering the art index to the minimum for regular cards
- file = findFile(dir, setlessFilename.replaceAll("[0-9]*[.]full", "1.full"));
- if (file != null) { return file; }
- }
- }
// System.out.println("File not found, no image created: " + key);
-
return null;
}
@@ -200,16 +245,72 @@ public final class ImageKeys {
? StaticData.instance().getEditions().getCode2ByCode(edition) // by default 2-letter codes from MWS are used
: CACHE_CARD_PICS_SUBDIR.get(edition); // may use custom paths though
}
-
- private static File findFile(String dir, String filename) {
- for (String ext : FILE_EXTENSIONS) {
- File file = new File(dir, filename + ext);
- if (file.exists()) {
- if (file.isDirectory()) {
- file.delete();
- continue;
+ private static File setLookUpFile(String filename, String fullborderFile) {
+ if (!StaticData.instance().getSetLookup().isEmpty()) {
+ for (String setKey : StaticData.instance().getSetLookup().keySet()) {
+ if (filename.startsWith(setKey)) {
+ for (String setLookup : StaticData.instance().getSetLookup().get(setKey)) {
+ String lookupDirectory = CACHE_CARD_PICS_DIR + setLookup;
+ File f = new File(lookupDirectory);
+ String[] cardNames = f.list();
+ if (cardNames != null) {
+ Set cardList = new HashSet<>(Arrays.asList(cardNames));
+ for (String ext : FILE_EXTENSIONS) {
+ if (ext.equals(""))
+ continue;
+ String fb1 = fullborderFile.replace(setKey+"/","")+ext;
+ if (cardList.contains(fb1)) {
+ return new File(lookupDirectory+"/"+fb1);
+ }
+ String fb2 = fullborderFile.replace(setKey+"/","").replaceAll("[0-9]*.fullborder", "1.fullborder")+ext;
+ if (cardList.contains(fb2)) {
+ return new File(lookupDirectory+"/"+fb2);
+ }
+ String f1 = filename.replace(setKey+"/","")+ext;
+ if (cardList.contains(f1)) {
+ return new File(lookupDirectory+"/"+f1);
+ }
+ String f2 = filename.replace(setKey+"/","").replaceAll("[0-9]*.full", "1.full")+ext;
+ if (cardList.contains(f2)) {
+ return new File(lookupDirectory+"/"+f2);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private static File findFile(String dir, String filename) {
+ if (dir.equals(CACHE_CARD_PICS_DIR)) {
+ File f = new File(dir+"/"+filename);
+ String initialDirectory = f.getParent();
+ String cardName = f.getName();
+ File parentDir = new File(initialDirectory);
+ String[] cardNames = parentDir.list();
+ if (cardNames != null) {
+ Set cardList = new HashSet<>(Arrays.asList(cardNames));
+ for (String ext : FILE_EXTENSIONS) {
+ if (ext.equals(""))
+ continue;
+ String cardLookup = cardName+ext;
+ if (cardList.contains(cardLookup)) {
+ return new File(parentDir+"/"+cardLookup);
+ }
+ }
+ }
+ } else {
+ //old method for tokens and others
+ for (String ext : FILE_EXTENSIONS) {
+ File file = new File(dir, filename + ext);
+ if (file.exists()) {
+ if (file.isDirectory()) {
+ file.delete();
+ continue;
+ }
+ return file;
}
- return file;
}
}
return null;
@@ -217,8 +318,11 @@ public final class ImageKeys {
//shortcut for determining if a card image exists for a given card
//should only be called from PaperCard.hasImage()
- static HashMap> cachedContent=new HashMap<>();
+ static HashMap> cachedContent=new HashMap<>(50000);
public static boolean hasImage(PaperCard pc) {
+ return hasImage(pc, false);
+ }
+ public static boolean hasImage(PaperCard pc, boolean update) {
Boolean editionHasImage = editionImageLookup.get(pc.getEdition());
if (editionHasImage == null) {
String setFolder = getSetFolder(pc.getEdition());
@@ -232,6 +336,10 @@ public final class ImageKeys {
if (!filename.endsWith(".jpg") && !filename.endsWith(".png"))
continue; // not image - not interested
setFolderContent.add(filename.split("\\.")[0]); // get rid of any full or fullborder
+ //preload cachedCards at startUp
+ String key = setFolder + "/" + filename.replace(".fullborder", ".full").replace(".jpg", "").replace(".png", "");
+ File value = new File(CACHE_CARD_PICS_DIR + setFolder + "/" + filename);
+ cachedCards.put(key, value);
}
cachedContent.put(setFolder, setFolderContent);
}
@@ -239,6 +347,13 @@ public final class ImageKeys {
String[] keyParts = StringUtils.split(pc.getCardImageKey(), "//");
if (keyParts.length != 2)
return false;
+ if (update && editionHasImage) {
+ try {
+ cachedContent.get(getSetFolder(pc.getEdition())).add(pc.getName());
+ } catch (Exception e) {
+ System.err.println(e);
+ }
+ }
HashSet content = cachedContent.getOrDefault(keyParts[0], null);
//avoid checking for file if edition doesn't have any images
return editionHasImage && hitCache(content, keyParts[1]);
diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java
index 590d4f0f63f..780005a845e 100644
--- a/forge-core/src/main/java/forge/card/CardDb.java
+++ b/forge-core/src/main/java/forge/card/CardDb.java
@@ -561,9 +561,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
// Before returning make sure that actual candidate has Image.
// If not, try to replace current candidate with one having image,
// so to align this implementation with old one.
- while (!candidate.hasImage() && candidatesIterator.hasNext()) {
+ // If none will have image, the original candidate will be retained!
+ PaperCard firstCandidate = candidate;
+ while (!candidate.hasImage() && candidatesIterator.hasNext())
candidate = candidatesIterator.next();
- }
+ candidate = candidate.hasImage() ? candidate : firstCandidate;
return isFoil ? candidate.getFoiled() : candidate;
}
@@ -767,10 +769,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
final Iterator editionIterator = acceptedEditions.iterator();
CardEdition ed = editionIterator.next();
PaperCard candidate = candidatesCard.get(ed.getCode());
+ PaperCard firstCandidate = candidate;
while (!candidate.hasImage() && editionIterator.hasNext()) {
ed = editionIterator.next();
candidate = candidatesCard.get(ed.getCode());
}
+ candidate = candidate.hasImage() ? candidate : firstCandidate;
//If any, we're sure that at least one candidate is always returned despite it having any image
return cr.isFoil ? candidate.getFoiled() : candidate;
}
diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java
index 1ec639ba940..95d0b8a8f2e 100644
--- a/forge-core/src/main/java/forge/item/PaperCard.java
+++ b/forge-core/src/main/java/forge/item/PaperCard.java
@@ -136,8 +136,11 @@ public final class PaperCard implements Comparable, InventoryItemFro
}
public boolean hasImage() {
- if (hasImage == null) { //cache value since it's not free to calculate
- hasImage = ImageKeys.hasImage(this);
+ return hasImage(false);
+ }
+ public boolean hasImage(boolean update) {
+ if (hasImage == null || update) { //cache value since it's not free to calculate
+ hasImage = ImageKeys.hasImage(this, update);
}
return hasImage;
}
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index 754462a481c..2ddb495f86c 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -74,6 +74,7 @@ import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.staticability.StaticAbility;
+import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.staticability.StaticAbilityLayer;
import forge.game.trigger.TriggerType;
import forge.game.zone.PlayerZone;
@@ -682,17 +683,25 @@ public class GameAction {
// need to refresh ability text for affected cards
for (final StaticAbility stAb : c.getStaticAbilities()) {
- if (stAb.isSecondary() ||
- !stAb.getParam("Mode").equals("CantBlockBy") ||
- stAb.isSuppressed() || !stAb.checkConditions() ||
- !stAb.hasParam("ValidAttacker") ||
- (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
+ if (stAb.isSuppressed() || !stAb.checkConditions()) {
continue;
}
- final Card host = stAb.getHostCard();
- for (Card creature : Iterables.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
- if (creature.isValid(stAb.getParam("ValidAttacker").split(","), host.getController(), host, stAb)) {
- creature.updateAbilityTextForView();
+
+ if (stAb.getParam("Mode").equals("CantBlockBy")) {
+ if (!stAb.hasParam("ValidAttacker") || (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
+ continue;
+ }
+ for (Card creature : Iterables.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
+ if (stAb.matchesValidParam("ValidAttacker", creature)) {
+ creature.updateAbilityTextForView();
+ }
+ }
+ }
+ if (stAb.getParam("Mode").equals(StaticAbilityCantAttackBlock.MinMaxBlockerMode)) {
+ for (Card creature : Iterables.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES)) {
+ if (stAb.matchesValidParam("ValidCard", creature)) {
+ creature.updateAbilityTextForView();
+ }
}
}
}
@@ -1720,7 +1729,6 @@ public class GameAction {
return false;
}
-
if (sa != null) {
activator = sa.getActivatingPlayer();
}
diff --git a/forge-game/src/main/java/forge/game/StaticEffect.java b/forge-game/src/main/java/forge/game/StaticEffect.java
index 7b53983812a..40e795e0cb2 100644
--- a/forge-game/src/main/java/forge/game/StaticEffect.java
+++ b/forge-game/src/main/java/forge/game/StaticEffect.java
@@ -174,18 +174,8 @@ public class StaticEffect {
final CardCollectionView affectedCards = getAffectedCards();
final List affectedPlayers = getAffectedPlayers();
- boolean setPT = false;
- String[] addHiddenKeywords = null;
boolean removeMayPlay = false;
- if (hasParam("SetPower") || hasParam("SetToughness")) {
- setPT = true;
- }
-
- if (hasParam("AddHiddenKeyword")) {
- addHiddenKeywords = getParam("AddHiddenKeyword").split(" & ");
- }
-
if (hasParam("MayPlay")) {
removeMayPlay = true;
}
@@ -220,7 +210,7 @@ public class StaticEffect {
affectedCard.removeChangedTextColorWord(getTimestamp(), ability.getId());
// remove set P/T
- if (setPT) {
+ if (hasParam("SetPower") || hasParam("SetToughness")) {
affectedCard.removeNewPT(getTimestamp());
}
@@ -240,10 +230,8 @@ public class StaticEffect {
affectedCard.removeCantHaveKeyword(getTimestamp());
}
- if (addHiddenKeywords != null) {
- for (final String k : addHiddenKeywords) {
- affectedCard.removeHiddenExtrinsicKeyword(k);
- }
+ if (hasParam("AddHiddenKeyword")) {
+ affectedCard.removeHiddenExtrinsicKeywords(timestamp, ability.getId());
}
// remove abilities
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java
index f4e4227b0dc..311fec449fd 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java
@@ -155,7 +155,6 @@ public enum AbilityKey {
}
}
return null;
-
}
public static EnumMap newMap() {
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
index 36ed77f5824..796bffca76e 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
@@ -451,7 +451,6 @@ public class AbilityUtils {
public static int calculateAmount(final Card card, String amount, final CardTraitBase ability) {
return calculateAmount(card, amount, ability, false);
}
-
public static int calculateAmount(final Card card, String amount, final CardTraitBase ability, boolean maxto) {
// return empty strings and constants
if (StringUtils.isBlank(amount)) { return 0; }
@@ -2120,7 +2119,7 @@ public class AbilityUtils {
// Count$IfCastInOwnMainPhase.. // 7/10
if (sq[0].contains("IfCastInOwnMainPhase")) {
- final PhaseHandler cPhase = player.getGame().getPhaseHandler();
+ final PhaseHandler cPhase = game.getPhaseHandler();
final boolean isMyMain = cPhase.getPhase().isMain() && cPhase.isPlayerTurn(player) && c.getCastFrom() != null;
return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), expr, c, ctb);
}
@@ -3585,7 +3584,6 @@ public class AbilityUtils {
return tot;
}
-
private static CardCollectionView getCardListForXCount(final Card c, final Player cc, final String[] sq, CardTraitBase ctb) {
final List opps = cc.getOpponents();
CardCollection someCards = new CardCollection();
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java
index 44d7d2cbd43..7c37e2a7fb9 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java
@@ -154,7 +154,7 @@ public class AnimateAllEffect extends AnimateEffectBase {
@Override
public void run() {
- doUnanimate(c, sa, hiddenKeywords, timestamp);
+ doUnanimate(c, timestamp);
game.fireEvent(new GameEventCardStatsChanged(c));
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
index c86fe5e34ae..71c4de1eec3 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
@@ -103,8 +103,8 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
c.addCantHaveKeyword(timestamp, Keyword.setValueOf(sa.getParam("CantHaveKeyword")));
}
- for (final String k : hiddenKeywords) {
- c.addHiddenExtrinsicKeyword(k);
+ if (!hiddenKeywords.isEmpty()) {
+ c.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKeywords);
}
c.addColor(colors, !sa.hasParam("OverwriteColors"), timestamp, false);
@@ -157,7 +157,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
@Override
public void run() {
- doUnanimate(c, sa, hiddenKeywords, timestamp);
+ doUnanimate(c, timestamp);
c.removeChangedName(timestamp);
c.updateStateForView();
@@ -217,8 +217,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
* @param timestamp
* a long.
*/
- static void doUnanimate(final Card c, SpellAbility sa,
- final List hiddenKeywords, final long timestamp) {
+ static void doUnanimate(final Card c, final long timestamp) {
c.removeNewPT(timestamp);
@@ -231,9 +230,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
c.removeCantHaveKeyword(timestamp);
- for (final String k : hiddenKeywords) {
- c.removeHiddenExtrinsicKeyword(k);
- }
+ c.removeHiddenExtrinsicKeywords(timestamp, 0);
// any other unanimate cleanup
if (!c.isCreature()) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CamouflageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CamouflageEffect.java
index c8528f91eff..ac5b0a5a32a 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CamouflageEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CamouflageEffect.java
@@ -13,6 +13,7 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
+import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.util.Localizer;
public class CamouflageEffect extends SpellAbilityEffect {
@@ -36,7 +37,7 @@ public class CamouflageEffect extends SpellAbilityEffect {
continue;
}
- if (attacker.hasKeyword("CantBeBlockedByAmount GT1") && blockers.size() > 1) {
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defender).getRight() < blockers.size()) {
// If no more than one creature can block, order the player to choose one to block
Card chosen = declarer.getController().chooseCardsForEffect(blockers, sa,
Localizer.getInstance().getMessage("lblChooseBlockerForAttacker", attacker.toString()), 1, 1, false, null).get(0);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java
index 9705fa0d885..8f05f4e8a3b 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java
@@ -128,10 +128,11 @@ public class ControlGainEffect extends SpellAbilityEffect {
}
final List kws = Lists.newArrayList();
+ final List hiddenKws = Lists.newArrayList();
if (null != keywords) {
for (final String kw : keywords) {
if (kw.startsWith("HIDDEN")) {
- tgtC.addHiddenExtrinsicKeyword(kw);
+ hiddenKws.add(kw.substring(7));
} else {
kws.add(kw);
}
@@ -142,6 +143,9 @@ public class ControlGainEffect extends SpellAbilityEffect {
tgtC.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, tStamp, 0);
game.fireEvent(new GameEventCardStatsChanged(tgtC));
}
+ if (hiddenKws.isEmpty()) {
+ tgtC.addHiddenExtrinsicKeywords(tStamp, 0, hiddenKws);
+ }
if (remember && !source.isRemembered(tgtC)) {
source.addRemembered(tgtC);
@@ -191,14 +195,8 @@ public class ControlGainEffect extends SpellAbilityEffect {
@Override
public void run() {
- if (keywords.size() > 0) {
- for (String kw : keywords) {
- if (kw.startsWith("HIDDEN")) {
- tgtC.removeHiddenExtrinsicKeyword(kw);
- }
- }
- tgtC.removeChangedCardKeywords(tStamp, 0);
- }
+ tgtC.removeHiddenExtrinsicKeywords(tStamp, 0);
+ tgtC.removeChangedCardKeywords(tStamp, 0);
}
};
game.getEndOfTurn().addUntil(untilKeywordEOT);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java
index e845cc1d8ae..377c885a196 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java
@@ -32,6 +32,7 @@ import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerController;
import forge.game.spellability.SpellAbility;
+import forge.game.staticability.StaticAbilityAdapt;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
@@ -288,8 +289,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
// Adapt need extra logic
if (sa.hasParam("Adapt")) {
- if (!(gameCard.getCounters(CounterEnumType.P1P1) == 0
- || gameCard.hasKeyword("CARDNAME adapts as though it had no +1/+1 counters"))) {
+ if (!(gameCard.getCounters(CounterEnumType.P1P1) == 0 || StaticAbilityAdapt.anyWithAdapt(sa, gameCard))) {
continue;
}
}
@@ -362,8 +362,6 @@ public class CountersPutEffect extends SpellAbilityEffect {
game.getTriggerHandler().runTrigger(TriggerType.BecomeRenowned, AbilityKey.mapFromCard(gameCard), false);
}
if (sa.hasParam("Adapt")) {
- // need to remove special keyword
- gameCard.removeHiddenExtrinsicKeyword("CARDNAME adapts as though it had no +1/+1 counters");
game.getTriggerHandler().runTrigger(TriggerType.Adapt, AbilityKey.mapFromCard(gameCard), false);
}
} else {
@@ -422,7 +420,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
List keywords = Arrays.asList(sa.getParam("SharedKeywords").split(" & "));
List zones = ZoneType.listValueOf(sa.getParam("SharedKeywordsZone"));
String[] restrictions = sa.hasParam("SharedRestrictions") ? sa.getParam("SharedRestrictions").split(",") : new String[]{"Card"};
- keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, card);
+ keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, card, sa);
for (String k : keywords) {
resolvePerType(sa, placer, CounterType.getType(k), counterAmount, table);
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java
index 239d59f47d7..d349a45d5e0 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java
@@ -175,7 +175,6 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
String title = Localizer.getInstance().getMessage("lblSelectRemoveCountersNumberOfTarget", type);
cntToRemove = pc.chooseNumber(sa, title, 0, cntToRemove, params);
}
-
}
if (cntToRemove > 0) {
gameCard.subtractCounter(counterType, cntToRemove);
@@ -199,7 +198,6 @@ public class CountersRemoveEffect extends SpellAbilityEffect {
}
}
-
protected void removeAnyType(GameEntity entity, int cntToRemove, SpellAbility sa) {
boolean rememberRemoved = sa.hasParam("RememberRemoved");
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java
index 72f4a9c40bf..187a68dc0c3 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DebuffEffect.java
@@ -13,6 +13,7 @@ import forge.card.MagicColor;
import forge.game.Game;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.spellability.SpellAbility;
@@ -72,17 +73,16 @@ public class DebuffEffect extends SpellAbilityEffect {
final List removedKW = Lists.newArrayList();
if (tgtC.isInPlay() && (!sa.usesTargeting() || tgtC.canBeTargetedBy(sa))) {
if (sa.hasParam("AllSuffixKeywords")) {
- String suffix = sa.getParam("AllSuffixKeywords");
- for (final KeywordInterface kw : tgtC.getKeywords()) {
- String keyword = kw.getOriginal();
- if (keyword.endsWith(suffix)) {
- kws.add(keyword);
+ // this only for walk abilities, may to try better
+ if (sa.getParam("AllSuffixKeywords").equals("walk")) {
+ for (final KeywordInterface kw : tgtC.getKeywords(Keyword.LANDWALK)) {
+ removedKW.add(kw.getOriginal());
}
}
}
// special for Protection:Card.:Protection from :*
- for (final KeywordInterface inst : tgtC.getKeywords()) {
+ for (final KeywordInterface inst : tgtC.getUnhiddenKeywords()) {
String keyword = inst.getOriginal();
if (keyword.startsWith("Protection:")) {
for (final String kw : kws) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java
index ad67308edc1..52e2896424d 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java
@@ -45,8 +45,7 @@ public class DigEffect extends SpellAbilityEffect {
if (tgtPlayers.contains(host.getController())) {
sb.append("their ");
- }
- else {
+ } else {
for (final Player p : tgtPlayers) {
sb.append(Lang.getInstance().getPossesive(p.getName())).append(" ");
}
@@ -323,8 +322,7 @@ public class DigEffect extends SpellAbilityEffect {
libraryPosition = zone.size();
}
c = game.getAction().moveTo(zone, c, libraryPosition, sa);
- }
- else {
+ } else {
c = game.getAction().moveTo(zone, c, sa);
if (destZone1.equals(ZoneType.Battlefield)) {
if (sa.hasParam("Tapped")) {
@@ -383,8 +381,7 @@ public class DigEffect extends SpellAbilityEffect {
Card m;
if (destZone2 == ZoneType.Library) {
m = game.getAction().moveToLibrary(c, libraryPosition2, sa);
- }
- else {
+ } else {
m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa);
}
if (m != null && !origin.equals(m.getZone().getZoneType())) {
@@ -394,8 +391,7 @@ public class DigEffect extends SpellAbilityEffect {
host.addRemembered(m);
}
}
- }
- else {
+ } else {
// just move them randomly
for (int i = 0; i < rest.size(); i++) {
Card c = rest.get(i);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java
index 537d7011279..123cdcffbff 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java
@@ -141,7 +141,6 @@ public class ManaEffect extends SpellAbilityEffect {
abMana.setExpressChoice(choice);
}
else if (abMana.isSpecialMana()) {
-
String type = abMana.getOrigProduced().split("Special ")[1];
if (type.equals("EnchantedManaCost")) {
@@ -151,12 +150,12 @@ public class ManaEffect extends SpellAbilityEffect {
StringBuilder sb = new StringBuilder();
int generic = enchanted.getManaCost().getGenericCost();
- if( generic > 0 )
+ if (generic > 0)
sb.append(generic);
for (ManaCostShard s : enchanted.getManaCost()) {
ColorSet cs = ColorSet.fromMask(s.getColorMask());
- if(cs.isColorless())
+ if (cs.isColorless())
continue;
sb.append(' ');
if (cs.isMonoColor())
@@ -200,7 +199,7 @@ public class ManaEffect extends SpellAbilityEffect {
for (Card c : AbilityUtils.getDefinedCards(card, res, sa)) {
for (ManaCostShard s : c.getManaCost()) {
ColorSet cs = ColorSet.fromMask(s.getColorMask());
- if(cs.isColorless())
+ if (cs.isColorless())
continue;
sb.append(' ');
if (cs.isMonoColor())
diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java
index 156cea21c08..4501345e813 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java
@@ -31,7 +31,7 @@ public class PumpAllEffect extends SpellAbilityEffect {
for (String kw : keywords) {
if (kw.startsWith("HIDDEN")) {
- hiddenkws.add(kw);
+ hiddenkws.add(kw.substring(7));
} else {
kws.add(kw);
}
@@ -57,13 +57,15 @@ public class PumpAllEffect extends SpellAbilityEffect {
redrawPT = true;
}
- tgtC.addChangedCardKeywords(kws, null, false, false, timestamp, 0);
+ if (!kws.isEmpty()) {
+ tgtC.addChangedCardKeywords(kws, null, false, false, timestamp, 0);
+ }
if (redrawPT) {
tgtC.updatePowerToughnessForView();
}
- for (String kw : hiddenkws) {
- tgtC.addHiddenExtrinsicKeyword(kw);
+ if (!hiddenkws.isEmpty()) {
+ tgtC.addHiddenExtrinsicKeywords(timestamp, 0, hiddenkws);
}
if (sa.hasParam("RememberAllPumped")) {
@@ -79,10 +81,8 @@ public class PumpAllEffect extends SpellAbilityEffect {
public void run() {
tgtC.removePTBoost(timestamp, 0);
tgtC.removeChangedCardKeywords(timestamp, 0);
+ tgtC.removeHiddenExtrinsicKeywords(timestamp, 0);
- for (String kw : hiddenkws) {
- tgtC.removeHiddenExtrinsicKeyword(kw);
- }
tgtC.updatePowerToughnessForView();
game.fireEvent(new GameEventCardStatsChanged(tgtC));
@@ -156,7 +156,7 @@ public class PumpAllEffect extends SpellAbilityEffect {
String[] restrictions = new String[] {"Card"};
if (sa.hasParam("SharedRestrictions"))
restrictions = sa.getParam("SharedRestrictions").split(",");
- keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard());
+ keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard(), sa);
}
applyPumpAll(sa, list, a, d, keywords, affectedZones);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java
index e92d031ec0e..7921dc15703 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java
@@ -20,7 +20,6 @@ import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardUtil;
import forge.game.event.GameEventCardStatsChanged;
-import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility;
@@ -50,11 +49,12 @@ public class PumpEffect extends SpellAbilityEffect {
return;
}
final List kws = Lists.newArrayList();
+ final List hiddenKws = Lists.newArrayList();
boolean redrawPT = false;
for (String kw : keywords) {
if (kw.startsWith("HIDDEN")) {
- gameCard.addHiddenExtrinsicKeyword(kw);
+ hiddenKws.add(kw.substring(7));
redrawPT |= kw.contains("CARDNAME's power and toughness are switched");
} else {
kws.add(kw);
@@ -66,7 +66,12 @@ public class PumpEffect extends SpellAbilityEffect {
redrawPT = true;
}
- gameCard.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, timestamp, 0);
+ if (!kws.isEmpty()) {
+ gameCard.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, timestamp, 0);
+ }
+ if (!hiddenKws.isEmpty()) {
+ gameCard.addHiddenExtrinsicKeywords(timestamp, 0, hiddenKws);
+ }
if (redrawPT) {
gameCard.updatePowerToughnessForView();
}
@@ -95,12 +100,7 @@ public class PumpEffect extends SpellAbilityEffect {
updateText |= gameCard.removeCanBlockAdditional(timestamp);
if (keywords.size() > 0) {
-
- for (String kw : keywords) {
- if (kw.startsWith("HIDDEN")) {
- gameCard.removeHiddenExtrinsicKeyword(kw);
- }
- }
+ gameCard.removeHiddenExtrinsicKeywords(timestamp, 0);
gameCard.removeChangedCardKeywords(timestamp, 0);
}
gameCard.updatePowerToughnessForView();
@@ -244,7 +244,7 @@ public class PumpEffect extends SpellAbilityEffect {
if (sa.hasParam("SharedKeywordsZone")) {
List zones = ZoneType.listValueOf(sa.getParam("SharedKeywordsZone"));
String[] restrictions = sa.hasParam("SharedRestrictions") ? sa.getParam("SharedRestrictions").split(",") : new String[]{"Card"};
- keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard());
+ keywords = CardFactoryUtil.sharedKeywords(keywords, restrictions, zones, sa.getHostCard(), sa);
}
List tgts = Lists.newArrayList();
@@ -290,9 +290,10 @@ public class PumpEffect extends SpellAbilityEffect {
List choice = Lists.newArrayList();
List total = Lists.newArrayList(keywords);
if (sa.hasParam("NoRepetition")) {
- for (KeywordInterface inst : tgtCards.get(0).getKeywords()) {
- final String kws = inst.getOriginal();
- total.remove(kws);
+ for (String kw : keywords) {
+ if (tgtCards.get(0).hasKeyword(kw)) {
+ total.remove(kw);
+ }
}
}
final int min = Math.min(total.size(), numkw);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java
index 0aec8a69275..1b2cb597848 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ReorderZoneEffect.java
@@ -34,8 +34,7 @@ public class ReorderZoneEffect extends SpellAbilityEffect {
if (shuffle) {
Collections.shuffle(list, MyRandom.getRandom());
p.getZone(zone).setCards(list);
- }
- else {
+ } else {
p.getController().orderMoveToZoneList(list, zone, sa);
}
}
diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java
index 4db52c941bc..b5b109b83ef 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -55,6 +55,7 @@ import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
+import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.Zone;
@@ -99,7 +100,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
private CardDamageHistory damageHistory = new CardDamageHistory();
// Hidden keywords won't be displayed on the card
- private final KeywordCollection hiddenExtrinsicKeyword = new KeywordCollection();
+ // x=timestamp y=StaticAbility id
+ private final Table> hiddenExtrinsicKeywords = TreeBasedTable.create();
// cards attached or otherwise linked to this card
private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards, encodedCards;
@@ -1867,7 +1869,37 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
// get the text that does not belong to a cards abilities (and is not really
// there rules-wise)
public final String getNonAbilityText() {
- return keywordsToText(getHiddenExtrinsicKeywords());
+ final StringBuilder sb = new StringBuilder();
+ final StringBuilder sbLong = new StringBuilder();
+
+ for (String keyword : getHiddenExtrinsicKeywords()) {
+ if (keyword.startsWith("CantBeCounteredBy")) {
+ final String[] p = keyword.split(":");
+ sbLong.append(p[2]).append("\r\n");
+ } else if (keyword.equals("Unblockable")) {
+ sbLong.append(getName()).append(" can't be blocked.\r\n");
+ } else if (keyword.equals("AllNonLegendaryCreatureNames")) {
+ sbLong.append(getName()).append(" has all names of nonlegendary creature cards.\r\n");
+ } else if (keyword.startsWith("IfReach")) {
+ String[] k = keyword.split(":");
+ sbLong.append(getName()).append(" can block ")
+ .append(CardType.getPluralType(k[1]))
+ .append(" as though it had reach.\r\n");
+ } else {
+ sbLong.append(keyword).append("\r\n");
+ }
+ }
+ if (sb.length() > 0) {
+ sb.append("\r\n");
+ if (sbLong.length() > 0) {
+ sb.append("\r\n");
+ }
+ }
+ if (sbLong.length() > 0) {
+ sbLong.append("\r\n");
+ }
+ sb.append(sbLong);
+ return CardTranslation.translateMultipleDescriptionText(sb.toString(), getName());
}
// convert a keyword list to the String that should be displayed in game
@@ -2120,9 +2152,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
|| keyword.startsWith("Encore") || keyword.startsWith("Mutate") || keyword.startsWith("Dungeon")
|| keyword.startsWith("Class") || keyword.startsWith("Saga")) {
// keyword parsing takes care of adding a proper description
- } else if (keyword.startsWith("CantBeBlockedByAmount")) {
- sbLong.append(getName()).append(" can't be blocked ");
- sbLong.append(getTextForKwCantBeBlockedByAmount(keyword));
} else if (keyword.equals("Unblockable")) {
sbLong.append(getName()).append(" can't be blocked.\r\n");
} else if (keyword.equals("AllNonLegendaryCreatureNames")) {
@@ -2173,14 +2202,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
return CardTranslation.translateMultipleDescriptionText(sb.toString(), getName());
}
- private static String getTextForKwCantBeBlockedByAmount(final String keyword) {
- final String restriction = keyword.split(" ", 2)[1];
- final boolean isLT = "LT".equals(restriction.substring(0,2));
- final String byClause = isLT ? "except by " : "by more than ";
- final int cnt = Integer.parseInt(restriction.substring(2));
- return byClause + Lang.nounWithNumeral(cnt, isLT ? "or more creature" : "creature");
- }
-
// get the text of the abilities of a card
public String getAbilityText() {
return getAbilityText(currentState);
@@ -2367,15 +2388,27 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
continue;
}
for (final StaticAbility stAb : ca.getStaticAbilities()) {
- if (stAb.isSecondary() ||
- !stAb.getParam("Mode").equals("CantBlockBy") ||
- stAb.isSuppressed() || !stAb.checkConditions() ||
- !stAb.hasParam("ValidAttacker") ||
- (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
+ if (stAb.isSuppressed() || !stAb.checkConditions()) {
continue;
}
- final Card host = stAb.getHostCard();
- if (isValid(stAb.getParam("ValidAttacker").split(","), host.getController(), host, stAb)) {
+
+ boolean found = false;
+ if (stAb.getParam("Mode").equals("CantBlockBy")) {
+ if (!stAb.hasParam("ValidAttacker") || (stAb.hasParam("ValidBlocker") && stAb.getParam("ValidBlocker").equals("Creature.Self"))) {
+ continue;
+ }
+ if (stAb.matchesValidParam("ValidAttacker", this)) {
+ found = true;
+ }
+ } else if (stAb.getParam("Mode").equals(StaticAbilityCantAttackBlock.MinMaxBlockerMode)) {
+ if (stAb.matchesValidParam("ValidCard", this)) {
+ found = true;
+ }
+ }
+
+ if (found) {
+ final Card host = stAb.getHostCard();
+
String currentName = host.getName();
String desc1 = TextUtil.fastReplace(stAb.toString(), "CARDNAME", currentName);
String desc = TextUtil.fastReplace(desc1,"NICKNAME", currentName.split(",")[0]);
@@ -4119,7 +4152,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
// of lists. Optimizes common operations such as hasKeyword().
public final void visitKeywords(CardState state, Visitor visitor) {
visitUnhiddenKeywords(state, visitor);
- visitHiddenExtrinsicKeywords(visitor);
}
@Override
@@ -4140,8 +4172,10 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
// shortcut for hidden keywords
- if (this.hiddenExtrinsicKeyword.contains(keyword)) {
- return true;
+ for (List kw : this.hiddenExtrinsicKeywords.values()) {
+ if (kw.contains(keyword)) {
+ return true;
+ }
}
HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, false);
@@ -4418,41 +4452,33 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
// Hidden Keywords will be returned without the indicator HIDDEN
- public final List getHiddenExtrinsicKeywords() {
- ListKeywordVisitor visitor = new ListKeywordVisitor();
- visitHiddenExtrinsicKeywords(visitor);
- return visitor.getKeywords();
- }
- private void visitHiddenExtrinsicKeywords(Visitor visitor) {
- for (KeywordInterface inst : hiddenExtrinsicKeyword.getValues()) {
- if (!visitor.visit(inst)) {
- return;
- }
- }
+ public final Iterable getHiddenExtrinsicKeywords() {
+ return Iterables.concat(this.hiddenExtrinsicKeywords.values());
}
- public final void addHiddenExtrinsicKeyword(String s) {
- if (s.startsWith("HIDDEN")) {
- s = s.substring(7);
- }
- if (hiddenExtrinsicKeyword.add(s) != null) {
- view.updateNonAbilityText(this);
- updateKeywords();
- }
+ public final void addHiddenExtrinsicKeywords(long timestamp, long staticId, Iterable keywords) {
+ // TODO if some keywords aren't removed anymore, then no need for extra Array List
+ hiddenExtrinsicKeywords.put(timestamp, staticId, Lists.newArrayList(keywords));
+
+ view.updateNonAbilityText(this);
+ updateKeywords();
}
- public final void addHiddenExtrinsicKeyword(KeywordInterface k) {
- if (hiddenExtrinsicKeyword.insert(k)) {
+ public final void removeHiddenExtrinsicKeywords(long timestamp, long staticId) {
+ if (hiddenExtrinsicKeywords.remove(timestamp, staticId) != null) {
view.updateNonAbilityText(this);
updateKeywords();
}
}
public final void removeHiddenExtrinsicKeyword(String s) {
- if (s.startsWith("HIDDEN")) {
- s = s.substring(7);
+ boolean updated = false;
+ for (List list : hiddenExtrinsicKeywords.values()) {
+ if (list.remove(s)) {
+ updated = true;
+ }
}
- if (hiddenExtrinsicKeyword.remove(s)) {
+ if (updated) {
view.updateNonAbilityText(this);
updateKeywords();
}
@@ -4710,6 +4736,12 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
return hasStartOfKeyword(keyword, currentState);
}
public final boolean hasStartOfKeyword(String keyword, CardState state) {
+ for (String s : this.getHiddenExtrinsicKeywords()) {
+ if (s.startsWith(keyword)) {
+ return true;
+ }
+ }
+
HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true);
visitKeywords(state, visitor);
return visitor.getResult();
@@ -4741,9 +4773,10 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
return getAmountOfKeyword(k, currentState);
}
public final int getAmountOfKeyword(final String k, CardState state) {
+ int count = Iterables.frequency(this.getHiddenExtrinsicKeywords(), k);
CountKeywordVisitor visitor = new CountKeywordVisitor(k);
visitKeywords(state, visitor);
- return visitor.getCount();
+ return count + visitor.getCount();
}
public final int getAmountOfKeyword(final Keyword k) {
@@ -5619,11 +5652,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
final boolean colorlessDamage = damageSource && source.hasKeyword("Colorless Damage Source");
- for (final KeywordInterface inst : getKeywords()) {
+ for (final KeywordInterface inst : getKeywords(Keyword.PROTECTION)) {
String kw = inst.getOriginal();
- if (!kw.startsWith("Protection")) {
- continue;
- }
if (kw.equals("Protection from white")) {
if (source.isWhite() && !colorlessDamage) {
return true;
@@ -5698,11 +5728,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
public String getProtectionKey() {
String protectKey = "";
boolean pR = false; boolean pG = false; boolean pB = false; boolean pU = false; boolean pW = false;
- for (final KeywordInterface inst : getKeywords()) {
+ for (final KeywordInterface inst : getKeywords(Keyword.PROTECTION)) {
String kw = inst.getOriginal();
- if (!kw.startsWith("Protection")) {
- continue;
- }
if (kw.contains("Protection from red")) {
if (!pR) {
pR = true;
@@ -5755,11 +5782,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
public String getHexproofKey() {
String hexproofKey = "";
boolean hR = false; boolean hG = false; boolean hB = false; boolean hU = false; boolean hW = false;
- for (final KeywordInterface inst : getKeywords()) {
+ for (final KeywordInterface inst : getKeywords(Keyword.HEXPROOF)) {
String kw = inst.getOriginal();
- if (!kw.startsWith("Hexproof")) {
- continue;
- }
if (kw.equals("Hexproof")) {
hexproofKey += "generic:";
}
@@ -5859,6 +5883,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
final Card source = sa.getHostCard();
if (sa.isSpell()) {
+ // TODO replace with Static Ability
for(KeywordInterface inst : source.getKeywords()) {
String kw = inst.getOriginal();
if(!kw.startsWith("SpellCantTarget")) {
@@ -6506,23 +6531,16 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
private static final class CountKeywordVisitor extends Visitor {
private String keyword;
private int count;
- private boolean startOf;
private CountKeywordVisitor(String keyword) {
this.keyword = keyword;
this.count = 0;
- this.startOf = false;
- }
-
- private CountKeywordVisitor(String keyword, boolean startOf) {
- this(keyword);
- this.startOf = startOf;
}
@Override
public boolean visit(KeywordInterface inst) {
final String kw = inst.getOriginal();
- if ((startOf && kw.startsWith(keyword)) || kw.equals(keyword)) {
+ if (kw.equals(keyword)) {
count++;
}
return true;
@@ -6551,7 +6569,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
return result.isFalse();
}
-
public boolean getResult() {
return result.isTrue();
}
diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
index c7710814b49..57a5f070984 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
@@ -42,6 +42,7 @@ import forge.card.ICardFace;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
+import forge.game.CardTraitBase;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.GameLogEntryType;
@@ -264,8 +265,7 @@ public class CardFactoryUtil {
return false;
}
- for (KeywordInterface k : c.getKeywords()) {
- final String o = k.getOriginal();
+ for (String o : c.getHiddenExtrinsicKeywords()) {
if (o.startsWith("CantBeCounteredBy")) {
final String[] m = o.split(":");
if (sa.isValid(m[1].split(","), c.getController(), c, null)) {
@@ -510,7 +510,7 @@ public class CardFactoryUtil {
* @return a List.
*/
public static List sharedKeywords(final Iterable kw, final String[] restrictions,
- final Iterable zones, final Card host) {
+ final Iterable zones, final Card host, CardTraitBase ctb) {
final List filteredkw = Lists.newArrayList();
final Player p = host.getController();
CardCollectionView cardlist = p.getGame().getCardsIn(zones);
@@ -521,7 +521,7 @@ public class CardFactoryUtil {
final Set tramplekw = Sets.newHashSet();
final Set allkw = Sets.newHashSet();
- for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, null)) {
+ for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, ctb)) {
for (KeywordInterface inst : c.getKeywords()) {
final String k = inst.getOriginal();
if (k.endsWith("walk")) {
diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java
index ee03689e056..d8712ecacab 100644
--- a/forge-game/src/main/java/forge/game/card/CardPredicates.java
+++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java
@@ -21,6 +21,7 @@ import java.util.Comparator;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
import forge.card.CardStateName;
import forge.game.CardTraitBase;
@@ -31,6 +32,7 @@ import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone;
import forge.game.zone.ZoneType;
+import forge.util.PredicateString;
import forge.util.collect.FCollectionView;
@@ -109,6 +111,10 @@ public final class CardPredicates {
return new Predicate() {
@Override
public boolean apply(final Card c) {
+ if (Iterables.any(c.getHiddenExtrinsicKeywords(), PredicateString.contains(keyword))) {
+ return true;
+ }
+
for (KeywordInterface k : c.getKeywords()) {
if (k.getOriginal().contains(keyword)) {
return true;
diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java
index bb57f0115a5..397e7c98394 100644
--- a/forge-game/src/main/java/forge/game/card/CardUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardUtil.java
@@ -123,8 +123,7 @@ public final class CardUtil {
for (Player p : game.getRegisteredPlayers()) {
res.addAll(p.getZone(to).getCardsAddedThisTurn(from));
}
- }
- else {
+ } else {
res.addAll(game.getStackZone().getCardsAddedThisTurn(from));
}
return CardLists.getValidCardsAsList(res, valid, src.getController(), src, ctb);
@@ -376,7 +375,6 @@ public final class CardUtil {
public static CardState getFaceDownCharacteristic(Card c) {
return getFaceDownCharacteristic(c, CardStateName.FaceDown);
}
-
public static CardState getFaceDownCharacteristic(Card c, CardStateName state) {
final CardType type = new CardType(false);
type.add("Creature");
@@ -401,7 +399,6 @@ public final class CardUtil {
public static Set getReflectableManaColors(final SpellAbility sa) {
return getReflectableManaColors(sa, sa, Sets.newHashSet(), new CardCollection());
}
-
private static Set getReflectableManaColors(final SpellAbility abMana, final SpellAbility sa,
Set colors, final CardCollection parents) {
// Here's the problem with reflectable Mana. If more than one is out,
diff --git a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java
index bdbf033b9e2..2d5570323a0 100644
--- a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java
+++ b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java
@@ -52,18 +52,27 @@ public class AttackRequirement {
nAttackAnything += attacker.getGoaded().size();
}
+ // remove it when all of them are HIDDEN or static
for (final KeywordInterface inst : attacker.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
final String defined = keyword.split(":")[1];
final GameEntity mustAttack2 = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
defenderSpecific.add(mustAttack2);
- } else if (keyword.equals("CARDNAME attacks each combat if able.") ||
- (keyword.equals("CARDNAME attacks each turn if able.")
- && !attacker.getDamageHistory().getCreatureAttackedThisTurn())) {
+ } else if (keyword.equals("CARDNAME attacks each combat if able.")) {
nAttackAnything++;
}
}
+ for (final String keyword : attacker.getHiddenExtrinsicKeywords()) {
+ if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
+ final String defined = keyword.split(":")[1];
+ final GameEntity mustAttack2 = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
+ defenderSpecific.add(mustAttack2);
+ } else if (keyword.equals("CARDNAME attacks each combat if able.")) {
+ nAttackAnything++;
+ }
+ }
+
final GameEntity mustAttack3 = attacker.getMustAttackEntity();
if (mustAttack3 != null) {
defenderSpecific.add(mustAttack3);
@@ -149,7 +158,7 @@ public class AttackRequirement {
int violations = 0;
// first. check to see if "must attack X or Y with at least one creature" requirements are satisfied
- List toRemoveFromDefSpecific = Lists.newArrayList();
+ //List toRemoveFromDefSpecific = Lists.newArrayList();
if (!defenderOrPWSpecific.isEmpty()) {
for (GameEntity def : defenderOrPWSpecific.keySet()) {
if (defenderSpecificAlternatives.containsKey(def)) {
diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java
index c25d7ca22f5..2f9d61dad8e 100644
--- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java
+++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java
@@ -47,9 +47,9 @@ import forge.game.player.Player;
import forge.game.player.PlayerController.ManaPaymentPurpose;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
+import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
-import forge.util.Expressions;
import forge.util.TextUtil;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
@@ -251,12 +251,9 @@ public class CombatUtil {
}
// Keywords
- for (final KeywordInterface keyword : attacker.getKeywords()) {
- switch (keyword.getOriginal()) {
- case "CARDNAME can't attack.":
- case "CARDNAME can't attack or block.":
- return false;
- }
+ // replace with Static Ability if able
+ if (attacker.hasKeyword("CARDNAME can't attack.") || attacker.hasKeyword("CARDNAME can't attack or block.")) {
+ return false;
}
// CantAttack static abilities
@@ -505,7 +502,7 @@ public class CombatUtil {
}
if ( combat != null ) {
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) {
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defendingPlayer).getRight() == combat.getBlockers(attacker).size()) {
return false;
}
@@ -587,7 +584,7 @@ public class CombatUtil {
}
}
- for (final KeywordInterface inst : attacker.getKeywords()) {
+ for (final KeywordInterface inst : attacker.getKeywords(Keyword.LANDWALK)) {
String keyword = inst.getOriginal();
if (keyword.equals("Legendary landwalk")) {
walkTypes.add("Land.Legendary");
@@ -762,11 +759,11 @@ public class CombatUtil {
}
// "CARDNAME blocks each turn/combat if able."
- if (!blockers.contains(blocker) && (blocker.hasKeyword("CARDNAME blocks each turn if able.") || blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
+ if (!blockers.contains(blocker) && (blocker.hasKeyword("CARDNAME blocks each combat if able."))) {
for (final Card attacker : attackers) {
if (canBlock(attacker, blocker, combat)) {
boolean must = true;
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defending).getLeft() > 1) {
final List possibleBlockers = Lists.newArrayList(defendersArmy);
possibleBlockers.remove(blocker);
if (!canBeBlocked(attacker, possibleBlockers, combat)) {
@@ -774,8 +771,7 @@ public class CombatUtil {
}
}
if (must) {
- String unit = blocker.hasKeyword("CARDNAME blocks each combat if able.") ? "combat," : "turn,";
- return TextUtil.concatWithSpace(blocker.toString(),"must block each", unit, "but was not assigned to block any attacker now.");
+ return TextUtil.concatWithSpace(blocker.toString(),"must block each combat but was not assigned to block any attacker now.");
}
}
}
@@ -848,6 +844,7 @@ public class CombatUtil {
&& combat.getBlockers(attacker).size() < 2)) {
attackersWithLure.add(attacker);
} else {
+ // TODO replace with Hidden Keyword or Static Ability
for (KeywordInterface inst : attacker.getKeywords()) {
String keyword = inst.getOriginal();
// MustBeBlockedBy
@@ -875,8 +872,11 @@ public class CombatUtil {
for (final Card attacker : attackersWithLure) {
if (canBeBlocked(attacker, combat, defender) && canBlock(attacker, blocker)) {
boolean canBe = true;
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
- final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
+
+ Player defendingPlayer = combat.getDefenderPlayerByAttacker(attacker);
+
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defendingPlayer).getLeft() > 1) {
+ final List blockers = defendingPlayer.getCreaturesInPlay();
blockers.remove(blocker);
if (!canBeBlocked(attacker, blockers, combat)) {
canBe = false;
@@ -893,8 +893,9 @@ public class CombatUtil {
if (canBeBlocked(attacker, combat, defender) && canBlock(attacker, blocker)
&& combat.isAttacking(attacker)) {
boolean canBe = true;
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
- final List blockers = freeBlockers != null ? new CardCollection(freeBlockers) : combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
+ Player defendingPlayer = combat.getDefenderPlayerByAttacker(attacker);
+ if (StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defendingPlayer).getLeft() > 1) {
+ final List blockers = freeBlockers != null ? new CardCollection(freeBlockers) : defendingPlayer.getCreaturesInPlay();
blockers.remove(blocker);
if (!canBeBlocked(attacker, blockers, combat)) {
canBe = false;
@@ -968,6 +969,7 @@ public class CombatUtil {
return false;
}
+ // TODO remove with HiddenKeyword or Static Ability
boolean mustBeBlockedBy = false;
for (KeywordInterface inst : attacker.getKeywords()) {
String keyword = inst.getOriginal();
@@ -1085,60 +1087,16 @@ public class CombatUtil {
if (amount == 0)
return false; // no block
- List restrictions = Lists.newArrayList();
- for (KeywordInterface inst : attacker.getKeywords()) {
- String kw = inst.getOriginal();
- if (kw.startsWith("CantBeBlockedByAmount")) {
- restrictions.add(TextUtil.split(kw, ' ', 2)[1]);
- }
- if (kw.equals("Menace")) {
- restrictions.add("LT2");
- }
- }
- for (String res : restrictions) {
- int operand = Integer.parseInt(res.substring(2));
- String operator = res.substring(0,2);
- if (Expressions.compare(amount, operator, operand) )
- return false;
- }
- if (defender != null && attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
- return amount >= defender.getCreaturesInPlay().size();
+ Pair minMaxBlock = StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defender);
+
+ if (minMaxBlock.getLeft() > amount || minMaxBlock.getRight() < amount) {
+ return false;
}
return true;
}
public static int getMinNumBlockersForAttacker(Card attacker, Player defender) {
- List restrictions = Lists.newArrayList();
- for (KeywordInterface inst : attacker.getKeywords()) {
- String kw = inst.getOriginal();
- if (kw.startsWith("CantBeBlockedByAmount")) {
- restrictions.add(TextUtil.split(kw, ' ', 2)[1]);
- }
- if (kw.equals("Menace")) {
- restrictions.add("LT2");
- }
- }
- int minBlockers = 1;
- for (String res : restrictions) {
- int operand = Integer.parseInt(res.substring(2));
- String operator = res.substring(0, 2);
- if (operator.equals("LT") || operator.equals("GE")) {
- if (minBlockers < operand) {
- minBlockers = operand;
- }
- } else if (operator.equals("LE") || operator.equals("GT") || operator.equals("EQ")) {
- if (minBlockers < operand + 1) {
- minBlockers = operand + 1;
- }
- }
- }
- if (attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
- if (defender != null) {
- minBlockers = defender.getCreaturesInPlay().size();
- }
- }
-
- return minBlockers;
+ return StaticAbilityCantAttackBlock.getMinMaxBlocker(attacker, defender).getLeft();
}
} // end class CombatUtil
diff --git a/forge-game/src/main/java/forge/game/cost/Cost.java b/forge-game/src/main/java/forge/game/cost/Cost.java
index d696a6b1c3e..f0d7d5e69b6 100644
--- a/forge-game/src/main/java/forge/game/cost/Cost.java
+++ b/forge-game/src/main/java/forge/game/cost/Cost.java
@@ -684,7 +684,7 @@ public class Cost implements Serializable {
boolean first = true;
for (final CostPart part : this.costParts) {
if (!first) {
- cost.append(" and ");
+ cost.append(", ");
}
cost.append(part.toString());
first = false;
diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java
index db947d0c9aa..89a70a2862f 100644
--- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java
+++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java
@@ -284,12 +284,10 @@ public class CostAdjustment {
private static void adjustCostByOffering(final ManaCostBeingPaid cost, final SpellAbility sa) {
String offeringType = "";
- for (KeywordInterface inst : sa.getHostCard().getKeywords()) {
+ for (KeywordInterface inst : sa.getHostCard().getKeywords(Keyword.OFFERING)) {
final String kw = inst.getOriginal();
- if (kw.endsWith(" offering")) {
- offeringType = kw.split(" ")[0];
- break;
- }
+ offeringType = kw.split(" ")[0];
+ break;
}
Card toSac = null;
diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java
index d800a3ae0fb..0bd3c0a95df 100644
--- a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java
+++ b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java
@@ -100,10 +100,11 @@ public class CostRemoveAnyCounter extends CostPart {
public final String toString() {
final StringBuilder sb = new StringBuilder();
- String counters = this.counter == null ? "counter" : this.counter.getName() + " counter";
+ String counters = this.counter == null ? "counter" : this.counter.getName().toLowerCase() + " counter";
sb.append("Remove ");
sb.append(Cost.convertAmountTypeToWords(this.convertAmount(), this.getAmount(), counters));
+ sb.append(this.getAmount().equals("1") ? "" : "s");
final String desc = this.getTypeDescription() == null ? this.getType() : this.getTypeDescription();
sb.append(" from ").append(desc);
diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java b/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java
index 517d092ca6c..afbd85ba6d4 100644
--- a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java
+++ b/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java
@@ -108,10 +108,11 @@ public class CostRemoveCounter extends CostPart {
} else {
sb.append("Remove ");
final Integer i = this.convertAmount();
- sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), this.counter.getName() + " counter"));
-
- if (this.getAmount().equals("All")) {
- sb.append("s");
+ if (this.getAmount().equals("X")) {
+ sb.append("any number of counters");
+ } else {
+ sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), this.counter.getName().toLowerCase()));
+ sb.append(this.getAmount().equals("1") ? " counter" : " counters");
}
sb.append(" from ");
diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java
index 56a10e903c1..25f90dd9b5c 100644
--- a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java
+++ b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java
@@ -12,7 +12,6 @@ import forge.game.card.Card;
public class KeywordCollection implements Iterable {
- private boolean hidden = false;
private transient KeywordCollectionView view;
// don't use enumKeys it causes a slow down
@@ -21,11 +20,6 @@ public class KeywordCollection implements Iterable {
public KeywordCollection() {
super();
- this.hidden = false;
- }
- public KeywordCollection(boolean hidden) {
- super();
- this.hidden = hidden;
}
public boolean contains(Keyword keyword) {
@@ -50,7 +44,6 @@ public class KeywordCollection implements Iterable {
public KeywordInterface add(String k) {
KeywordInterface inst = Keyword.getInstance(k);
- inst.setHidden(hidden);
if (insert(inst)) {
return inst;
}
diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java b/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java
index d740b3640e9..ad1f528aa11 100644
--- a/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java
+++ b/forge-game/src/main/java/forge/game/keyword/KeywordInstance.java
@@ -23,8 +23,6 @@ public abstract class KeywordInstance> implements K
private Keyword keyword;
private String original;
- private boolean hidden;
-
private List triggers = Lists.newArrayList();
private List replacements = Lists.newArrayList();
private List abilities = Lists.newArrayList();
@@ -205,21 +203,6 @@ public abstract class KeywordInstance> implements K
staticAbilities.add(st);
}
- /* (non-Javadoc)
- * @see forge.game.keyword.KeywordInterface#getHidden()
- */
- @Override
- public boolean getHidden() {
- return hidden;
- }
- /* (non-Javadoc)
- * @see forge.game.keyword.KeywordInterface#setHidden(boolean)
- */
- @Override
- public void setHidden(boolean val) {
- hidden = val;
- }
-
/*
* (non-Javadoc)
* @see forge.game.keyword.KeywordInterface#getTriggers()
diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java b/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java
index 2832cdd2ee4..d508fbdab02 100644
--- a/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java
+++ b/forge-game/src/main/java/forge/game/keyword/KeywordInterface.java
@@ -18,10 +18,7 @@ public interface KeywordInterface extends Cloneable {
String getReminderText();
int getAmount();
-
- boolean getHidden();
- void setHidden(boolean val);
-
+
void createTraits(final Card host, final boolean intrinsic);
void createTraits(final Card host, final boolean intrinsic, final boolean clear);
diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
index 9f76c814a53..0c228121070 100644
--- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
+++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
@@ -165,8 +165,7 @@ public class PhaseHandler implements java.io.Serializable {
if (bRepeatCleanup) { // for when Cleanup needs to repeat itself
bRepeatCleanup = false;
- }
- else {
+ } else {
// If the phase that's ending has a stack of additional phases
// Take the LIFO one and move to that instead of the normal one
ExtraPhase extraPhase = null;
@@ -179,8 +178,7 @@ public class PhaseHandler implements java.io.Serializable {
extraPhases.remove(phase);
}
setPhase(nextPhase);
- }
- else {
+ } else {
turnEnded = PhaseType.isLast(phase, isTopsy);
setPhase(PhaseType.getNext(phase, isTopsy));
}
@@ -263,8 +261,7 @@ public class PhaseHandler implements java.io.Serializable {
if (isSkippingPhase(phase)) {
skipped = true;
givePriorityToPlayer = false;
- }
- else {
+ } else {
// Perform turn-based actions
switch (phase) {
case UNTAP:
@@ -999,7 +996,6 @@ public class PhaseHandler implements java.io.Serializable {
public void startFirstTurn(Player goesFirst) {
startFirstTurn(goesFirst, null);
}
-
public void startFirstTurn(Player goesFirst, Runnable startGameHook) {
StopWatch sw = new StopWatch();
@@ -1104,8 +1100,7 @@ public class PhaseHandler implements java.io.Serializable {
if (game.getStack().isEmpty()) {
if (playerTurn.hasLost()) {
setPriority(game.getNextPlayerAfter(playerTurn));
- }
- else {
+ } else {
setPriority(playerTurn);
}
diff --git a/forge-game/src/main/java/forge/game/phase/Untap.java b/forge-game/src/main/java/forge/game/phase/Untap.java
index c6894964c1f..b13a12a64b1 100644
--- a/forge-game/src/main/java/forge/game/phase/Untap.java
+++ b/forge-game/src/main/java/forge/game/phase/Untap.java
@@ -205,11 +205,12 @@ public class Untap extends Phase {
}
// Remove temporary keywords
+ // TODO Replace with Static Abilities
for (final Card c : player.getCardsIn(ZoneType.Battlefield)) {
c.removeHiddenExtrinsicKeyword("This card doesn't untap during your next untap step.");
if (c.hasKeyword("This card doesn't untap during your next two untap steps.")) {
c.removeHiddenExtrinsicKeyword("This card doesn't untap during your next two untap steps.");
- c.addHiddenExtrinsicKeyword("This card doesn't untap during your next untap step.");
+ c.addHiddenExtrinsicKeywords(game.getNextTimestamp(), 0, Lists.newArrayList("This card doesn't untap during your next untap step."));
}
}
diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java
index 3a6ae63f5e8..93ea1fbfa89 100644
--- a/forge-game/src/main/java/forge/game/player/Player.java
+++ b/forge-game/src/main/java/forge/game/player/Player.java
@@ -551,6 +551,13 @@ public class Player extends GameEntity implements Comparable {
}
if (toLose > 0) {
int oldLife = life;
+ // Run applicable replacement effects
+ final Map repParams = AbilityKey.mapFromAffected(this);
+ repParams.put(AbilityKey.Result, oldLife-toLose);
+ if (game.getReplacementHandler().run(ReplacementType.LifeReduced, repParams)
+ != ReplacementResult.NotReplaced) {
+ return 0;
+ }
life -= toLose;
view.updateLife(this);
lifeLost = toLose;
@@ -3007,7 +3014,7 @@ public class Player extends GameEntity implements Comparable {
game.getAction().checkStaticAbilities(false);
for (final Card c : getCardsIn(ZoneType.Sideboard)) {
- for (KeywordInterface inst : c.getKeywords()) {
+ for (KeywordInterface inst : c.getKeywords(Keyword.COMPANION)) {
if (!(inst instanceof Companion)) {
continue;
}
diff --git a/forge-game/src/main/java/forge/game/player/PlayerProperty.java b/forge-game/src/main/java/forge/game/player/PlayerProperty.java
index 2d56c9676e1..000d1ee46c4 100644
--- a/forge-game/src/main/java/forge/game/player/PlayerProperty.java
+++ b/forge-game/src/main/java/forge/game/player/PlayerProperty.java
@@ -169,7 +169,7 @@ public class PlayerProperty {
return false;
}
} else if (property.equals("Defending")) {
- if (!player.getGame().getCombat().getAttackersAndDefenders().values().contains(player)) {
+ if (!game.getCombat().getAttackersAndDefenders().values().contains(player)) {
return false;
}
} else if (property.equals("wasDealtCombatDamageThisTurn")) {
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java b/forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java
index c39f669d26e..0b3d35d3e33 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplaceGameLoss.java
@@ -12,7 +12,7 @@ import forge.game.card.Card;
public class ReplaceGameLoss extends ReplacementEffect {
/**
- * Instantiates a new replace gain life.
+ * Instantiates a new replace game loss.
*
* @param map the map
* @param host the host
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceLifeReduced.java b/forge-game/src/main/java/forge/game/replacement/ReplaceLifeReduced.java
new file mode 100644
index 00000000000..d6b6f770ec7
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/replacement/ReplaceLifeReduced.java
@@ -0,0 +1,44 @@
+package forge.game.replacement;
+
+import java.util.Map;
+
+import forge.game.ability.AbilityKey;
+import forge.game.card.Card;
+import forge.util.Expressions;
+
+/**
+ * TODO: Write javadoc for this type.
+ *
+ */
+public class ReplaceLifeReduced extends ReplacementEffect {
+
+ /**
+ * Instantiates a new replace life reduced.
+ *
+ * @param map the map
+ * @param host the host
+ */
+ public ReplaceLifeReduced(Map map, Card host, boolean intrinsic) {
+ super(map, host, intrinsic);
+ }
+
+ /* (non-Javadoc)
+ * @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
+ */
+ @Override
+ public boolean canReplace(Map runParams) {
+ if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
+ return false;
+ }
+ if (hasParam("Result")) {
+ final int n = (Integer)runParams.get(AbilityKey.Result);
+ String comparator = getParam("Result");
+ final String operator = comparator.substring(0, 2);
+ final int operandValue = Integer.parseInt(comparator.substring(2));
+ if (!Expressions.compare(n, operator, operandValue)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
index 1ddcd59b518..dd52d246a09 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
@@ -81,7 +81,6 @@ public class ReplacementHandler {
//private final List tmpEffects = new ArrayList();
public List getReplacementList(final ReplacementType event, final Map runParams, final ReplacementLayer layer) {
-
final CardCollection preList = new CardCollection();
boolean checkAgain = false;
Card affectedLKI = null;
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java b/forge-game/src/main/java/forge/game/replacement/ReplacementType.java
index 335564f6c62..4a15d9aa846 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementType.java
@@ -29,6 +29,7 @@ public enum ReplacementType {
GainLife(ReplaceGainLife.class),
GameLoss(ReplaceGameLoss.class),
Learn(ReplaceLearn.class),
+ LifeReduced(ReplaceLifeReduced.class),
Mill(ReplaceMill.class),
Moved(ReplaceMoved.class),
ProduceMana(ReplaceProduceMana.class),
diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
index 664a417122b..c0f5b542267 100644
--- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
+++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
@@ -339,8 +339,7 @@ public class AbilityManaPart implements java.io.Serializable {
if (sa.isAbility()) {
if (restriction.startsWith("Activated")) {
restriction = TextUtil.fastReplace(restriction, "Activated", "Card");
- }
- else {
+ } else {
continue;
}
}
@@ -633,8 +632,7 @@ public class AbilityManaPart implements java.io.Serializable {
return true;
}
}
- }
- else {
+ } else {
// treat special mana if it always can be paid
if (isSpecialMana()) {
return true;
@@ -649,5 +647,4 @@ public class AbilityManaPart implements java.io.Serializable {
return false;
}
-} // end class AbilityMana
-
+}
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java
new file mode 100644
index 00000000000..e052d645a69
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java
@@ -0,0 +1,38 @@
+package forge.game.staticability;
+
+import forge.game.Game;
+import forge.game.card.Card;
+import forge.game.spellability.SpellAbility;
+import forge.game.zone.ZoneType;
+
+public class StaticAbilityAdapt {
+
+ static String MODE = "CanAdapt";
+
+ public static boolean anyWithAdapt(final SpellAbility sa, final Card card) {
+ final Game game = card.getGame();
+ for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
+ for (final StaticAbility stAb : ca.getStaticAbilities()) {
+ if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) {
+ continue;
+ }
+ if (applyWithAdapt(stAb, sa, card)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ public static boolean applyWithAdapt(final StaticAbility stAb, final SpellAbility sa, final Card card) {
+ if (!stAb.matchesValidParam("ValidCard", card)) {
+ return false;
+ }
+
+ if (!stAb.matchesValidParam("ValidSA", sa)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java
index b5970550a0a..c8ce0364203 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttackBlock.java
@@ -17,14 +17,19 @@
*/
package forge.game.staticability;
+import org.apache.commons.lang3.tuple.MutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
import com.google.common.collect.Iterables;
+import forge.game.Game;
import forge.game.GameEntity;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardPredicates;
import forge.game.cost.Cost;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
@@ -34,6 +39,8 @@ import forge.game.zone.ZoneType;
*/
public class StaticAbilityCantAttackBlock {
+ public static String MinMaxBlockerMode = "MinMaxBlocker";
+
/**
* TODO Write javadoc for this method.
*
@@ -221,4 +228,43 @@ public class StaticAbilityCantAttackBlock {
}
return true;
}
+
+ public static Pair getMinMaxBlocker(final Card attacker, final Player defender) {
+ MutablePair result = MutablePair.of(1, Integer.MAX_VALUE);
+
+ // Menace keyword
+ if (attacker.hasKeyword(Keyword.MENACE)) {
+ result.setLeft(2);
+ }
+
+ final Game game = attacker.getGame();
+ for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
+ for (final StaticAbility stAb : ca.getStaticAbilities()) {
+ if (!stAb.getParam("Mode").equals(MinMaxBlockerMode) || stAb.isSuppressed() || !stAb.checkConditions()) {
+ continue;
+ }
+ applyMinMaxBlockerAbility(stAb, attacker, defender, result);
+ }
+ }
+ if (attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
+ if (defender != null) {
+ result.setLeft(defender.getCreaturesInPlay().size());
+ }
+ }
+ return result;
+ }
+
+ public static void applyMinMaxBlockerAbility(final StaticAbility stAb, final Card attacker, final Player defender, MutablePair result) {
+ if (!stAb.matchesValidParam("ValidCard", attacker)) {
+ return;
+ }
+
+ if (stAb.hasParam("Min")) {
+ result.setLeft(AbilityUtils.calculateAmount(stAb.getHostCard(), stAb.getParam("Min"), stAb));
+ }
+
+ if (stAb.hasParam("Max")) {
+ result.setRight(AbilityUtils.calculateAmount(stAb.getHostCard(), stAb.getParam("Max"), stAb));
+ }
+ }
}
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
index 0d705998262..a51c9bb1ef4 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
@@ -326,7 +326,7 @@ public final class StaticAbilityContinuous {
if (params.containsKey("SharedKeywordsZone")) {
List zones = ZoneType.listValueOf(params.get("SharedKeywordsZone"));
String[] restrictions = params.containsKey("SharedRestrictions") ? params.get("SharedRestrictions").split(",") : new String[] {"Card"};
- addKeywords = CardFactoryUtil.sharedKeywords(addKeywords, restrictions, zones, hostCard);
+ addKeywords = CardFactoryUtil.sharedKeywords(addKeywords, restrictions, zones, hostCard, stAb);
}
}
@@ -708,9 +708,7 @@ public final class StaticAbilityContinuous {
// add HIDDEN keywords
if (!addHiddenKeywords.isEmpty()) {
- for (final String k : addHiddenKeywords) {
- affectedCard.addHiddenExtrinsicKeyword(k);
- }
+ affectedCard.addHiddenExtrinsicKeywords(hostCard.getTimestamp(), stAb.getId(), addHiddenKeywords);
}
// add SVars
diff --git a/forge-gui-desktop/src/main/java/forge/ImageCache.java b/forge-gui-desktop/src/main/java/forge/ImageCache.java
index 8c4eb6a5a13..ea6ecd75648 100644
--- a/forge-gui-desktop/src/main/java/forge/ImageCache.java
+++ b/forge-gui-desktop/src/main/java/forge/ImageCache.java
@@ -206,6 +206,7 @@ public class ImageCache {
boolean noBorder = !useArtCrop && !isPreferenceEnabled(ForgePreferences.FPref.UI_RENDER_BLACK_BORDERS);
boolean fetcherEnabled = isPreferenceEnabled(ForgePreferences.FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER);
boolean isPlaceholder = (original == null) && fetcherEnabled;
+ String setCode = imageKey.split("/")[0].trim().toUpperCase();
// If the user has indicated that they prefer Forge NOT render a black border, round the image corners
// to account for JPEG images that don't have a transparency.
@@ -213,7 +214,6 @@ public class ImageCache {
// use a quadratic equation to calculate the needed radius from an image dimension
int radius;
float width = original.getWidth();
- String setCode = imageKey.split("/")[0].trim().toUpperCase();
if (setCode.equals("A")) { // Alpha
// radius = 100; // 745 x 1040
// radius = 68; // 488 x 680
@@ -239,6 +239,15 @@ public class ImageCache {
original = makeRoundedCorner(original, radius);
}
+ // if image has white corners, get try to crop it out
+ if (original != null && isWhite(FSkin.getColorFromPixel(original.getRGB(0, 0)))) {
+ if (!isWhiteBorderSet(setCode)) {
+ int xSpacing = original.getWidth() / 40;
+ int ySpacing = original.getHeight() / 57;
+ original = original.getSubimage(xSpacing, ySpacing, original.getWidth() - (2* xSpacing), original.getHeight() - (2* ySpacing));
+ }
+ }
+
// No image file exists for the given key so optionally associate with
// a default "not available" image, however do not add it to the cache,
// as otherwise it's problematic to update if the real image gets fetched.
@@ -268,6 +277,15 @@ public class ImageCache {
return Pair.of(original, isPlaceholder);
}
+ private static boolean isWhite(Color color) {
+ return color.getRed() > 200 && color.getBlue() > 200 && color.getGreen() > 200;
+ }
+
+ private static boolean isWhiteBorderSet(String setCode) {
+ return setCode.equals("U") || setCode.equals("R") || setCode.equals("4E") || setCode.equals("5E") ||
+ setCode.equals("6E") || setCode.equals("7E") || setCode.equals("8E") || setCode.equals("9E");
+ }
+
// cardView is for Emblem, since there is no paper card for them
public static BufferedImage scaleImage(String key, final int width, final int height, boolean useDefaultImage, CardView cardView) {
if (StringUtils.isEmpty(key) || (3 > width && -1 != width) || (3 > height && -1 != height)) {
diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java
index 8d66a6ee640..1412034efe3 100644
--- a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java
+++ b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java
@@ -1498,7 +1498,7 @@ public class FSkin {
* @param pixel
* @return
*/
- private static Color getColorFromPixel(final int pixel) {
+ public static Color getColorFromPixel(final int pixel) {
int r, g, b, a;
a = (pixel >> 24) & 0x000000ff;
r = (pixel >> 16) & 0x000000ff;
diff --git a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java
index 2789e4ca019..e47e1e9c7ff 100644
--- a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java
+++ b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java
@@ -172,7 +172,6 @@ public class SimulateMatch {
System.out.println("\tq - Quiet flag. Output just the game result, not the entire game log.");
}
-
public static void simulateSingleMatch(final Match mc, int iGame, boolean outputGamelog) {
final StopWatch sw = new StopWatch();
sw.start();
diff --git a/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java b/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java
index ecfda6b4c7b..e287ca01fef 100644
--- a/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java
+++ b/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java
@@ -7,14 +7,10 @@ import forge.item.PaperCard;
import forge.model.FModel;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
import static org.testng.Assert.*;
@@ -2246,5 +2242,4 @@ public class CardDbTestCase extends ForgeCardMockTestCase {
assertTrue(islandCard.isFoil());
}
-}
-
+}
\ No newline at end of file
diff --git a/forge-gui-desktop/src/test/java/forge/card/CardDbTestWithNoImage.java b/forge-gui-desktop/src/test/java/forge/card/CardDbTestWithNoImage.java
index 1545fc8431d..c3ac9a9fa0b 100644
--- a/forge-gui-desktop/src/test/java/forge/card/CardDbTestWithNoImage.java
+++ b/forge-gui-desktop/src/test/java/forge/card/CardDbTestWithNoImage.java
@@ -1,5 +1,6 @@
package forge.card;
+import forge.ImageCache;
import forge.ImageKeys;
import forge.item.PaperCard;
import org.mockito.Mockito;
@@ -7,6 +8,8 @@ import org.powermock.api.mockito.PowerMockito;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import javax.imageio.ImageIO;
+
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
@@ -26,11 +29,11 @@ public class CardDbTestWithNoImage extends CardDbTestCase {
}
@Override
- @BeforeMethod
- protected void initMocks() throws Exception {
+ protected void initCardImageMocks() {
+ PowerMockito.mockStatic(ImageIO.class);
+ PowerMockito.mockStatic(ImageCache.class);
PowerMockito.mockStatic(ImageKeys.class);
- PowerMockito.when(ImageKeys.hasImage(Mockito.any(PaperCard.class))).thenReturn(false);
- super.initMocks();
+ PowerMockito.when(ImageKeys.hasImage(Mockito.any(PaperCard.class), Mockito.anyBoolean())).thenReturn(false);
}
@Test
diff --git a/forge-gui-desktop/src/test/java/forge/card/ForgeCardMockTestCase.java b/forge-gui-desktop/src/test/java/forge/card/ForgeCardMockTestCase.java
index 61acc9cd91a..49859e7706d 100644
--- a/forge-gui-desktop/src/test/java/forge/card/ForgeCardMockTestCase.java
+++ b/forge-gui-desktop/src/test/java/forge/card/ForgeCardMockTestCase.java
@@ -114,7 +114,7 @@ public class ForgeCardMockTestCase extends PowerMockTestCase {
fLangDir.set(ForgeConstants.class, langDir);
}
- private void setMock(Localizer mock) {
+ protected void setMock(Localizer mock) {
try {
Field instance = Localizer.class.getDeclaredField("instance");
instance.setAccessible(true);
@@ -128,16 +128,14 @@ public class ForgeCardMockTestCase extends PowerMockTestCase {
protected void initMocks() throws Exception {
//Loading a card also automatically loads the image, which we do not want (even if it wouldn't cause exceptions).
//The static initializer block in ImageCache can't fully be mocked (https://code.google.com/p/powermock/issues/detail?id=256), so we also need to mess with ImageIO...
- //TODO: make sure that loading images only happens in a GUI environment, so we no longer need to mock this
- PowerMockito.mockStatic(ImageIO.class);
- PowerMockito.mockStatic(ImageCache.class);
- PowerMockito.mockStatic(ImageKeys.class);
+ initCardImageMocks();
initForgeConstants();
-
- // Always Has Image (there is a separated test case to cover the opposite case)
- PowerMockito.when(ImageKeys.hasImage(Mockito.any(PaperCard.class))).thenReturn(true);
-
//Mocking some more static stuff
+ initForgePreferences();
+ initializeStaticData();
+ }
+
+ protected void initForgePreferences() throws IllegalAccessException {
PowerMockito.mockStatic(Singletons.class);
PowerMockito.mockStatic(FModel.class);
ForgePreferences forgePreferences = new ForgePreferences();
@@ -161,7 +159,14 @@ public class ForgeCardMockTestCase extends PowerMockTestCase {
PowerMockito.field(Localizer.class, "resourceBundle").set(localizerMock, dummyResourceBundle);
PowerMockito.when(localizerMock.getMessage(Mockito.anyString())).thenReturn("any string");
PowerMockito.when(FModel.getPreferences()).thenReturn(forgePreferences);
- initializeStaticData();
+ }
+
+ protected void initCardImageMocks() {
+ //make sure that loading images only happens in a GUI environment, so we no longer need to mock this
+ PowerMockito.mockStatic(ImageIO.class);
+ PowerMockito.mockStatic(ImageCache.class);
+ PowerMockito.mockStatic(ImageKeys.class);
+ PowerMockito.when(ImageKeys.hasImage(Mockito.any(PaperCard.class), Mockito.anyBoolean())).thenReturn(true);
}
protected void initializeStaticData() {
diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java
index 4376d0476e3..ffcb2055a62 100644
--- a/forge-gui-mobile/src/forge/Forge.java
+++ b/forge-gui-mobile/src/forge/Forge.java
@@ -67,6 +67,7 @@ public class Forge implements ApplicationListener {
public static float heigtModifier = 0.0f;
private static boolean isloadingaMatch = false;
public static boolean showFPS = false;
+ public static boolean allowCardBG = false;
public static boolean altPlayerLayout = false;
public static boolean altZoneTabs = false;
public static String enableUIMask = "Crop";
@@ -111,6 +112,13 @@ public class Forge implements ApplicationListener {
GuiBase.setIsAndroid(Gdx.app.getType() == Application.ApplicationType.Android);
+ if (!GuiBase.isAndroid() || (androidVersion > 28 && totalDeviceRAM > 7000)) {
+ allowCardBG = true;
+ } else {
+ // don't allow to read and process
+ ForgeConstants.SPRITE_CARDBG_FILE = "";
+ }
+
graphics = new Graphics();
splashScreen = new SplashScreen();
frameRate = new FrameRate();
diff --git a/forge-gui-mobile/src/forge/assets/ImageCache.java b/forge-gui-mobile/src/forge/assets/ImageCache.java
index 099da2b5a12..c69c523b409 100644
--- a/forge-gui-mobile/src/forge/assets/ImageCache.java
+++ b/forge-gui-mobile/src/forge/assets/ImageCache.java
@@ -50,7 +50,6 @@ import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgePreferences;
import forge.model.FModel;
import forge.util.ImageUtil;
-import forge.util.TextUtil;
/**
* This class stores ALL card images in a cache with soft values. this means
@@ -113,8 +112,6 @@ public class ImageCache {
}
public static void clear() {
- cache.invalidateAll();
- cache.cleanUp();
missingIconKeys.clear();
}
@@ -158,17 +155,23 @@ public class ImageCache {
final String prefix = imageKey.substring(0, 2);
+ PaperCard paperCard = null;
if (prefix.equals(ImageKeys.CARD_PREFIX)) {
- PaperCard paperCard = ImageUtil.getPaperCardFromImageKey(imageKey);
+ try {
+ paperCard = ImageUtil.getPaperCardFromImageKey(imageKey);
+ } catch (Exception e) {
+ return false;
+ }
if (paperCard == null)
return false;
- final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
- final String cardfilename = backFace ? paperCard.getCardAltImageKey() : paperCard.getCardImageKey();
- if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".jpg").exists())
- if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + cardfilename + ".png").exists())
- if (!new File(ForgeConstants.CACHE_CARD_PICS_DIR + "/" + TextUtil.fastReplace(cardfilename,".full", ".fullborder") + ".jpg").exists())
- return false;
+ if (!FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ENABLE_ONLINE_IMAGE_FETCHER)) {
+ return paperCard.hasImage();
+ } else {
+ final boolean backFace = imageKey.endsWith(ImageKeys.BACKFACE_POSTFIX);
+ final String cardfilename = backFace ? paperCard.getCardAltImageKey() : paperCard.getCardImageKey();
+ return ImageKeys.getCachedCardsFile(cardfilename) != null;
+ }
} else if (prefix.equals(ImageKeys.TOKEN_PREFIX)) {
final String tokenfilename = imageKey.substring(2) + ".jpg";
@@ -236,11 +239,10 @@ public class ImageCache {
if (image == null) {
if (useDefaultIfNotFound) {
image = defaultImage;
- if (useOtherCache)
- otherCache.put(imageKey, defaultImage);
- else
- cache.put(imageKey, defaultImage);
- if (imageBorder.get(image.toString()) == null)
+ /*fix not loading image file since we intentionally not to update the cache in order for the
+ image fetcher to update automatically after the card image/s are downloaded*/
+ imageLoaded = false;
+ if (image != null && imageBorder.get(image.toString()) == null)
imageBorder.put(image.toString(), Pair.of(Color.valueOf("#171717").toString(), false)); //black border
}
}
diff --git a/forge-gui-mobile/src/forge/card/CardImageRenderer.java b/forge-gui-mobile/src/forge/card/CardImageRenderer.java
index a7b267366f5..af2e1b36e18 100644
--- a/forge-gui-mobile/src/forge/card/CardImageRenderer.java
+++ b/forge-gui-mobile/src/forge/card/CardImageRenderer.java
@@ -120,7 +120,7 @@ public class CardImageRenderer {
} else {
borderColors = CardDetailUtil.getBorderColors(state, canShow);
}
- Color[] colors = useCardBGTexture ? drawCardBackgroundTexture(state, g, borderColors, x, y, w, h) : fillColorBackground(g, borderColors, x, y, w, h);
+ Color[] colors = useCardBGTexture && Forge.allowCardBG ? drawCardBackgroundTexture(state, g, borderColors, x, y, w, h) : fillColorBackground(g, borderColors, x, y, w, h);
float artInset = blackBorderThickness * 0.5f;
float outerBorderThickness = 2 * blackBorderThickness - artInset;
diff --git a/forge-gui-mobile/src/forge/card/CardRenderer.java b/forge-gui-mobile/src/forge/card/CardRenderer.java
index af5ad43624f..37631642716 100644
--- a/forge-gui-mobile/src/forge/card/CardRenderer.java
+++ b/forge-gui-mobile/src/forge/card/CardRenderer.java
@@ -51,7 +51,6 @@ import forge.gui.card.CardDetailUtil;
import forge.gui.card.CardDetailUtil.DetailColors;
import forge.item.IPaperCard;
import forge.item.InventoryItem;
-import forge.localinstance.properties.ForgeConstants;
import forge.localinstance.properties.ForgeConstants.CounterDisplayType;
import forge.localinstance.properties.ForgePreferences;
import forge.localinstance.properties.ForgePreferences.FPref;
diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java
index d3590ddbe96..834c095d953 100644
--- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java
+++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java
@@ -27,7 +27,6 @@ import forge.assets.FSkin;
import forge.assets.FSkinFont;
import forge.assets.FSkinImage;
import forge.assets.FTextureRegionImage;
-import forge.card.CardDb;
import forge.card.CardEdition;
import forge.deck.io.DeckPreferences;
import forge.gamemodes.limited.BoosterDraft;
diff --git a/forge-gui-mobile/src/forge/screens/home/LoadGameMenu.java b/forge-gui-mobile/src/forge/screens/home/LoadGameMenu.java
index e248e02fdf6..40082ecf6a4 100644
--- a/forge-gui-mobile/src/forge/screens/home/LoadGameMenu.java
+++ b/forge-gui-mobile/src/forge/screens/home/LoadGameMenu.java
@@ -105,11 +105,8 @@ public class LoadGameMenu extends FPopupMenu {
protected void buildMenu() {
FScreen currentScreen = Forge.getCurrentScreen();
for (LoadGameScreen lgs : LoadGameScreen.values()) {
- //fixes the overlapping menu items when the user suddenly switch from load game screen index to another screen
- if (HomeScreen.instance.getActiveButtonIndex() == 1) {
- addItem(lgs.item);
- lgs.item.setSelected(currentScreen == lgs.screen);
- }
+ addItem(lgs.item);
+ lgs.item.setSelected(currentScreen == lgs.screen);
}
}
}
diff --git a/forge-gui/res/blockdata/blocks.txt b/forge-gui/res/blockdata/blocks.txt
index c70bab58191..0732ee6fb51 100644
--- a/forge-gui/res/blockdata/blocks.txt
+++ b/forge-gui/res/blockdata/blocks.txt
@@ -98,3 +98,4 @@ Time Spiral Remastered, 3/6/KHM, TSR
Strixhaven: School of Mages, 3/6/STX, STX
Modern Horizons 2, 3/6/MH2, MH2
Adventures in the Forgotten Realms, 3/6/AFR, AFR
+Innistrad: Midnight Hunt, 3/6/MID, MID
diff --git a/forge-gui/res/cardsfolder/a/akoum_boulderfoot.txt b/forge-gui/res/cardsfolder/a/akoum_boulderfoot.txt
index c9df933082a..0e9ea9ad3db 100644
--- a/forge-gui/res/cardsfolder/a/akoum_boulderfoot.txt
+++ b/forge-gui/res/cardsfolder/a/akoum_boulderfoot.txt
@@ -3,6 +3,6 @@ ManaCost:4 R R
Types:Creature Giant Warrior
PT:4/5
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDealDamage | TriggerDescription$ When CARDNAME enters the battlefield, it deals 1 damage to any target.
-SVar:TrigDealDamage:DB$DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1
+SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1
SVar:Picture:http://www.wizards.com/global/images/magic/general/akoum_boulderfoot.jpg
Oracle:When Akoum Boulderfoot enters the battlefield, it deals 1 damage to any target.
diff --git a/forge-gui/res/cardsfolder/a/alpha_authority.txt b/forge-gui/res/cardsfolder/a/alpha_authority.txt
index 13c0923edbe..dbf9d216d25 100644
--- a/forge-gui/res/cardsfolder/a/alpha_authority.txt
+++ b/forge-gui/res/cardsfolder/a/alpha_authority.txt
@@ -3,6 +3,6 @@ ManaCost:1 G
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | Cost$ 1 G | ValidTgts$ Creature | AILogic$ Pump
-S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Hexproof | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Enchanted creature has hexproof and can't be blocked by more than one creature.
-SVar:Picture:http://www.wizards.com/global/images/magic/general/alpha_authority.jpg
+S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Hexproof | Description$ Enchanted creature has hexproof and can't be blocked by more than one creature.
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.EnchantedBy | Max$ 1 | Secondary$ True | Description$ Enchanted creature can't be blocked by more than one creature.
Oracle:Enchant creature\nEnchanted creature has hexproof and can't be blocked by more than one creature.
diff --git a/forge-gui/res/cardsfolder/a/amphin_mutineer.txt b/forge-gui/res/cardsfolder/a/amphin_mutineer.txt
index fad351f6004..28dadf024e9 100755
--- a/forge-gui/res/cardsfolder/a/amphin_mutineer.txt
+++ b/forge-gui/res/cardsfolder/a/amphin_mutineer.txt
@@ -6,5 +6,5 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S
SVar:TrigExile:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature.nonSalamander | TgtPrompt$ Select up to one target non-Salamander creature | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBToken
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_4_3_salamander_warrior | TokenOwner$ TargetedController
K:Encore:4 U U
-DeckHas:Ablity$Token
+DeckHas:Ability$Token
Oracle:When Amphin Mutineer enters the battlefield, exile up to one target non-Salamander creature. That creature's controller creates a 4/3 blue Salamander Warrior creature token.\nEncore {4}{U}{U} ({4}{U}{U}, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery.)
diff --git a/forge-gui/res/cardsfolder/a/assassins_trophy.txt b/forge-gui/res/cardsfolder/a/assassins_trophy.txt
index c41401cbd95..a40b1fe90e9 100644
--- a/forge-gui/res/cardsfolder/a/assassins_trophy.txt
+++ b/forge-gui/res/cardsfolder/a/assassins_trophy.txt
@@ -2,6 +2,5 @@ Name:Assassin's Trophy
ManaCost:B G
Types:Instant
A:SP$ Destroy | Cost$ B G | ValidTgts$ Permanent.OppCtrl | AITgts$ Permanent.nonLand,Land.nonBasic | TgtPrompt$ Select target permanent an opponent controls | SubAbility$ DBChange | SpellDescription$ Destroy target permanent an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle.
-SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ TargetedController | ChangeType$ Land.Basic | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | SubAbility$ DBCleanup
-SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ TargetedController | ChangeType$ Land.Basic | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True
Oracle:Destroy target permanent an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle.
diff --git a/forge-gui/res/cardsfolder/b/battlefront_krushok.txt b/forge-gui/res/cardsfolder/b/battlefront_krushok.txt
index c403246106d..b03891e8605 100644
--- a/forge-gui/res/cardsfolder/b/battlefront_krushok.txt
+++ b/forge-gui/res/cardsfolder/b/battlefront_krushok.txt
@@ -2,10 +2,9 @@ Name:Battlefront Krushok
ManaCost:4 G
Types:Creature Beast
PT:3/4
-K:CantBeBlockedByAmount GT1
-S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control with a +1/+1 counter on it can't be blocked by more than one creature.
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl+counters_GE1_P1P1 | Max$ 1 | Description$ Each creature you control with a +1/+1 counter on it can't be blocked by more than one creature.
SVar:NonStackingEffect:True
SVar:PlayMain1:TRUE
DeckHints:Ability$Counters
-SVar:Picture:http://www.wizards.com/global/images/magic/general/battlefront_krushok.jpg
Oracle:Battlefront Krushok can't be blocked by more than one creature.\nEach creature you control with a +1/+1 counter on it can't be blocked by more than one creature.
diff --git a/forge-gui/res/cardsfolder/b/beacon_of_immortality.txt b/forge-gui/res/cardsfolder/b/beacon_of_immortality.txt
index 6311a3817fd..8294d127e6f 100644
--- a/forge-gui/res/cardsfolder/b/beacon_of_immortality.txt
+++ b/forge-gui/res/cardsfolder/b/beacon_of_immortality.txt
@@ -1,8 +1,7 @@
Name:Beacon of Immortality
ManaCost:5 W
Types:Instant
-A:SP$ GainLife | Cost$ 5 W | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ X | SubAbility$ DBShuffle | SpellDescription$ Double target player's life total. Shuffle CARDNAME into its owner's library.
-SVar:DBShuffle:DB$ChangeZone | Origin$ Stack | Destination$ Library | Shuffle$ True
-SVar:X:TargetedPlayer$LifeTotal
-SVar:Picture:http://www.wizards.com/global/images/magic/general/beacon_of_immortality.jpg
+A:SP$ SetLife | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ X | SubAbility$ DBShuffle | SpellDescription$ Double target player's life total. Shuffle CARDNAME into its owner's library.
+SVar:DBShuffle:DB$ ChangeZone | Origin$ Stack | Destination$ Library | Shuffle$ True
+SVar:X:TargetedPlayer$LifeTotal/Twice
Oracle:Double target player's life total. Shuffle Beacon of Immortality into its owner's library.
diff --git a/forge-gui/res/cardsfolder/b/biomancers_familiar.txt b/forge-gui/res/cardsfolder/b/biomancers_familiar.txt
index 7e69f3e6c2a..efdf64330d2 100644
--- a/forge-gui/res/cardsfolder/b/biomancers_familiar.txt
+++ b/forge-gui/res/cardsfolder/b/biomancers_familiar.txt
@@ -3,6 +3,9 @@ ManaCost:G U
Types:Creature Mutant
PT:2/2
S:Mode$ ReduceCost | ValidCard$ Creature.YouCtrl | Type$ Ability | Amount$ 2 | MinMana$ 1 | AffectedZone$ Battlefield | Description$ Activated abilities of creatures you control cost {2} less to activate. This effect can't reduce the mana in that cost to less than one mana.
-A:AB$ Pump | Cost$ T | ValidTgts$ Creature | KW$ HIDDEN CARDNAME adapts as though it had no +1/+1 counters | TgtPrompt$ Select target creature. | StackDescription$ SpellDescription | SpellDescription$ The next time target creature adapts this turn, it adapts as though it had no +1/+1 counters on it.
+A:AB$ Effect | Cost$ T | ValidTgts$ Creature | RememberObjects$ ThisTargetedCard | StaticAbilities$ StaticAllowAdapt | Triggers$ TriggerClearAdapt | TgtPrompt$ Select target creature. | StackDescription$ SpellDescription | SpellDescription$ The next time target creature adapts this turn, it adapts as though it had no +1/+1 counters on it.
+SVar:StaticAllowAdapt:Mode$ CanAdapt | ValidCard$ Card.IsRemembered | Description$ Remembered adapts as though it had no +1/+1 counters on it.
+SVar:TriggerClearAdapt:Mode$ Adapt | ValidCard$ Card.IsRemembered | Execute$ ExileSelf | Static$ True
+SVar:ExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
DeckHints:Keyword$Adapt
Oracle:Activated abilities of creatures you control cost {2} less to activate. This effect can't reduce the mana in that cost to less than one mana.\n{T}: The next time target creature adapts this turn, it adapts as though it had no +1/+1 counters on it.
diff --git a/forge-gui/res/cardsfolder/b/bristling_boar.txt b/forge-gui/res/cardsfolder/b/bristling_boar.txt
index e2cb49f247a..317513c4abb 100644
--- a/forge-gui/res/cardsfolder/b/bristling_boar.txt
+++ b/forge-gui/res/cardsfolder/b/bristling_boar.txt
@@ -1,6 +1,6 @@
Name:Bristling Boar
ManaCost:3 G
Types:Creature Boar
-K:CantBeBlockedByAmount GT1
+PT:4/3
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
Oracle:Bristling Boar can't be blocked by more than one creature.
-PT:4/3
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/c/camouflage.txt b/forge-gui/res/cardsfolder/c/camouflage.txt
index d46d860dfd7..9ae4487b942 100644
--- a/forge-gui/res/cardsfolder/c/camouflage.txt
+++ b/forge-gui/res/cardsfolder/c/camouflage.txt
@@ -4,4 +4,5 @@ Types:Instant
A:SP$ Effect | Cost$ G | ReplacementEffects$ RDeclareBlocker | ActivationPhases$ Declare Attackers | PlayerTurn$ True | AILogic$ Evasion | SpellDescription$ Cast this spell only during your declare attackers step. This turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.)
SVar:RDeclareBlocker:Event$ DeclareBlocker | ValidPlayer$ Opponent | ReplaceWith$ DBCamouflage | Description$ This turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.)
SVar:DBCamouflage:DB$ Camouflage | Defined$ ReplacedPlayer | Defender$ ReplacedDefendingPlayer | AILogic$ BestBlocker
+AI:RemoveDeck:All
Oracle:Cast this spell only during your declare attackers step.\nThis turn, instead of declaring blockers, each defending player chooses any number of creatures they control and divides them into a number of piles equal to the number of attacking creatures for whom that player is the defending player. Creatures those players control that can block additional creatures may likewise be put into additional piles. Assign each pile to a different one of those attacking creatures at random. Each creature in a pile that can block the creature that pile is assigned to does so. (Piles can be empty.)
diff --git a/forge-gui/res/cardsfolder/c/challenger_troll.txt b/forge-gui/res/cardsfolder/c/challenger_troll.txt
index 597b351895c..ccdadc2c0f2 100644
--- a/forge-gui/res/cardsfolder/c/challenger_troll.txt
+++ b/forge-gui/res/cardsfolder/c/challenger_troll.txt
@@ -2,7 +2,7 @@ Name:Challenger Troll
ManaCost:4 G
Types:Creature Troll
PT:6/5
-S:Mode$ Continuous | Affected$ Creature.YouCtrl+powerGE4 | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control with power 4 or greater can't be blocked by more than one creature.
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl+powerGE4 | Max$ 1 | Description$ Each creature you control with power 4 or greater can't be blocked by more than one creature.
SVar:PlayMain1:TRUE
AI:RemoveDeck:Random
-Oracle:Each creature you control with power 4 or greater can't be blocked by more than one creature.
+Oracle:Each creature you control with power 4 or greater can't be blocked by more than one creature.
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/c/charging_rhino.txt b/forge-gui/res/cardsfolder/c/charging_rhino.txt
index 2bdf7706e56..07e8ffaaeef 100644
--- a/forge-gui/res/cardsfolder/c/charging_rhino.txt
+++ b/forge-gui/res/cardsfolder/c/charging_rhino.txt
@@ -2,6 +2,5 @@ Name:Charging Rhino
ManaCost:3 G G
Types:Creature Rhino
PT:4/4
-K:CantBeBlockedByAmount GT1
-SVar:Picture:http://www.wizards.com/global/images/magic/general/charging_rhino.jpg
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
Oracle:Charging Rhino can't be blocked by more than one creature.
diff --git a/forge-gui/res/cardsfolder/c/coastline_marauders.txt b/forge-gui/res/cardsfolder/c/coastline_marauders.txt
index c8e843d5e15..18a755678b6 100644
--- a/forge-gui/res/cardsfolder/c/coastline_marauders.txt
+++ b/forge-gui/res/cardsfolder/c/coastline_marauders.txt
@@ -8,5 +8,5 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$
SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ X
SVar:X:Count$Valid Land.DefenderCtrl
SVar:HasAttackEffect:TRUE
-DeckHas:Ablity$Token
+DeckHas:Ability$Token
Oracle:Trample\nWhenever Coastline Marauders attacks, it gets +1/+0 until end of turn for each land defending player controls.\nEncore {4}{R}{R} ({4}{R}{R}, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery.)
diff --git a/forge-gui/res/cardsfolder/d/deadly_grub.txt b/forge-gui/res/cardsfolder/d/deadly_grub.txt
index 8efc35e3a67..be93d56917d 100644
--- a/forge-gui/res/cardsfolder/d/deadly_grub.txt
+++ b/forge-gui/res/cardsfolder/d/deadly_grub.txt
@@ -4,6 +4,6 @@ Types:Creature Insect
PT:3/1
K:Vanishing:3
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+counters_EQ0_TIME | Execute$ TrigToken | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, if it had no time counters on it, create a 6/1 green Insect creature token with shroud. (It can't be the target of spells or abilities.)
-SVar:TrigToken:DB$Token | TokenAmount$ 1 | TokenScript$ g_6_1_insect_shroud | TokenOwner$ TriggeredCardController | LegacyImage$ g 6 1 insect shroud plc
+SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_6_1_insect_shroud | TokenOwner$ TriggeredCardController | LegacyImage$ g 6 1 insect shroud plc
SVar:Picture:http://www.wizards.com/global/images/magic/general/deadly_grub.jpg
Oracle:Vanishing 3 (This creature enters the battlefield with three time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.)\nWhen Deadly Grub dies, if it had no time counters on it, create a 6/1 green Insect creature token with shroud. (It can't be the target of spells or abilities.)
diff --git a/forge-gui/res/cardsfolder/f/false_orders.txt b/forge-gui/res/cardsfolder/f/false_orders.txt
index 8f9b3a24b26..688293c0fa3 100644
--- a/forge-gui/res/cardsfolder/f/false_orders.txt
+++ b/forge-gui/res/cardsfolder/f/false_orders.txt
@@ -6,4 +6,5 @@ A:SP$ RemoveFromCombat | Cost$ R | ActivationPhases$ Declare Blockers | ValidTgt
SVar:ChooseAttacker:DB$ ChooseCard | Defined$ You | Choices$ Creature.attacking | RememberChosen$ True | MinAmount$ 0 | ChoiceTitle$ Choose an attacker to block | SubAbility$ Block
SVar:Block:DB$ Block | DefinedAttacker$ Remembered | DefinedBlocker$ ParentTarget | SpellDescription$ You may have it block an attacking creature of your choice.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+AI:RemoveDeck:All
Oracle:Cast this spell only during the declare blockers step.\nRemove target creature defending player controls from combat. Creatures it was blocking that had become blocked by only that creature this combat become unblocked. You may have it block an attacking creature of your choice.
diff --git a/forge-gui/res/cardsfolder/f/familiar_ground.txt b/forge-gui/res/cardsfolder/f/familiar_ground.txt
index 6e1d5b8e059..c5053c24d2c 100644
--- a/forge-gui/res/cardsfolder/f/familiar_ground.txt
+++ b/forge-gui/res/cardsfolder/f/familiar_ground.txt
@@ -1,8 +1,7 @@
Name:Familiar Ground
ManaCost:2 G
Types:Enchantment
-S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control can't be blocked by more than one creature.
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl | Max$ 1 | Description$ Each creature you control can't be blocked by more than one creature.
SVar:NonStackingEffect:True
SVar:PlayMain1:TRUE
-SVar:Picture:http://www.wizards.com/global/images/magic/general/familiar_ground.jpg
Oracle:Each creature you control can't be blocked by more than one creature.
diff --git a/forge-gui/res/cardsfolder/f/fireball.txt b/forge-gui/res/cardsfolder/f/fireball.txt
index 06eeb46a331..fda14c63b5c 100644
--- a/forge-gui/res/cardsfolder/f/fireball.txt
+++ b/forge-gui/res/cardsfolder/f/fireball.txt
@@ -1,8 +1,8 @@
Name:Fireball
ManaCost:X R
Types:Sorcery
-A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | TargetMin$ 0 | TargetMax$ MaxTargets | DivideEvenly$ RoundedDown | SpellDescription$ This spell costs {1} more to cast for each target beyond the first.
-S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ IncreaseCost | EffectZone$ All | Description$ CARDNAME deals X damage divided evenly, rounded down, among any number of targets.
+S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ IncreaseCost | AffectedAmount$ True | EffectZone$ All | Description$ This spell costs {1} more to cast for each target beyond the first.
+A:SP$ DealDamage | Cost$ X R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | TargetMin$ 0 | TargetMax$ MaxTargets | DivideEvenly$ RoundedDown | SpellDescription$ CARDNAME deals X damage divided evenly, rounded down, among any number of targets.
SVar:X:Count$xPaid
SVar:MaxTargets:SVar$Maxplayer/Plus.Maxcreatureorplaneswalker
SVar:Maxplayer:PlayerCountPlayers$Amount
diff --git a/forge-gui/res/cardsfolder/g/gates_of_istfell.txt b/forge-gui/res/cardsfolder/g/gates_of_istfell.txt
index 24fa83aafba..df1baecf1b1 100644
--- a/forge-gui/res/cardsfolder/g/gates_of_istfell.txt
+++ b/forge-gui/res/cardsfolder/g/gates_of_istfell.txt
@@ -3,7 +3,7 @@ ManaCost:no cost
Types:Land
K:CARDNAME enters the battlefield tapped.
A:AB$ Mana | Cost$ T | Produced$ W | SpellDescription$ Add {W}.
-A:AB$ GainLife | Cost$ 2 W U U T Sac<1/CARDNAME> | Defined$ You | LifeAmount$ 2 | SubAbility$ DBDraw | SpellDescription$ You gain 2 life and draw 2 cards.
+A:AB$ GainLife | Cost$ 2 W U U T Sac<1/CARDNAME> | Defined$ You | LifeAmount$ 2 | SubAbility$ DBDraw | SpellDescription$ You gain 2 life and draw two cards.
SVar:DBDraw:DB$ Draw | NumCards$ 2
DeckHas:Ability$GainLife & Ability$Sacrifice
Oracle:Gates of Istfell enters the battlefield tapped.\n{T}: Add {W}.\n{2}{W}{U}{U}, {T}, Sacrifice Gates of Istfell: You gain 2 life and draw two cards.
diff --git a/forge-gui/res/cardsfolder/g/gorilla_berserkers.txt b/forge-gui/res/cardsfolder/g/gorilla_berserkers.txt
index 94f5224de29..7cfcc7472e0 100644
--- a/forge-gui/res/cardsfolder/g/gorilla_berserkers.txt
+++ b/forge-gui/res/cardsfolder/g/gorilla_berserkers.txt
@@ -4,6 +4,5 @@ Types:Creature Ape Berserker
PT:2/3
K:Trample
K:Rampage:2
-K:CantBeBlockedByAmount LT3
-SVar:Picture:http://www.wizards.com/global/images/magic/general/gorilla_berserkers.jpg
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures.
Oracle:Trample; rampage 2 (Whenever this creature becomes blocked, it gets +2/+2 until end of turn for each creature blocking it beyond the first.)\nGorilla Berserkers can't be blocked except by three or more creatures.
diff --git a/forge-gui/res/cardsfolder/g/gravebreaker_lamia.txt b/forge-gui/res/cardsfolder/g/gravebreaker_lamia.txt
index 52e7a51ec77..a1061084fe9 100755
--- a/forge-gui/res/cardsfolder/g/gravebreaker_lamia.txt
+++ b/forge-gui/res/cardsfolder/g/gravebreaker_lamia.txt
@@ -6,4 +6,5 @@ K:Lifelink
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters the battlefield, search your library for a card, put it into your graveyard, then shuffle.
SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Graveyard | ChangeNum$ 1 | ChangeType$ Card | Mandatory$ True
S:Mode$ ReduceCost | ValidCard$ Card.wasCastFromGraveyard | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Spells you cast from your graveyard cost {1} less to cast.
+DeckHas:Ability$LifeGain & Ability$Graveyard
Oracle:Lifelink\nWhen Gravebreaker Lamia enters the battlefield, search your library for a card, put it into your graveyard, then shuffle.\nSpells you cast from your graveyard cost {1} less to cast.
diff --git a/forge-gui/res/cardsfolder/g/guile.txt b/forge-gui/res/cardsfolder/g/guile.txt
index e398bd5365a..1ccb363765f 100644
--- a/forge-gui/res/cardsfolder/g/guile.txt
+++ b/forge-gui/res/cardsfolder/g/guile.txt
@@ -2,11 +2,10 @@ Name:Guile
ManaCost:3 U U U
Types:Creature Elemental Incarnation
PT:6/6
-K:CantBeBlockedByAmount LT3
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures.
R:Event$ Counter | ActiveZones$ Battlefield | ValidType$ Spell | ValidCause$ Card.YouCtrl | ReplaceWith$ DBRemove | Description$ If a spell or ability you control would counter a spell, instead exile that spell and you may play that card without paying its mana cost.
SVar:DBRemove:DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Stack | Destination$ Exile | Fizzle$ True | SubAbility$ DBPlay
SVar:DBPlay:DB$ Play | Defined$ ReplacedCard | WithoutManaCost$ True | Optional$ True
T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigShuffle | TriggerDescription$ When CARDNAME is put into a graveyard from anywhere, shuffle it into its owner's library.
SVar:TrigShuffle:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | Shuffle$ True | Defined$ TriggeredCardLKICopy
-SVar:Picture:http://www.wizards.com/global/images/magic/general/guile.jpg
Oracle:Guile can't be blocked except by three or more creatures.\nIf a spell or ability you control would counter a spell, instead exile that spell and you may play that card without paying its mana cost.\nWhen Guile is put into a graveyard from anywhere, shuffle it into its owner's library.
diff --git a/forge-gui/res/cardsfolder/h/huang_zhong_shu_general.txt b/forge-gui/res/cardsfolder/h/huang_zhong_shu_general.txt
index 286ff2a35a4..f1d2c0bb0d2 100644
--- a/forge-gui/res/cardsfolder/h/huang_zhong_shu_general.txt
+++ b/forge-gui/res/cardsfolder/h/huang_zhong_shu_general.txt
@@ -2,6 +2,5 @@ Name:Huang Zhong, Shu General
ManaCost:2 W W
Types:Legendary Creature Human Soldier
PT:2/3
-K:CantBeBlockedByAmount GT1
-SVar:Picture:http://www.wizards.com/global/images/magic/general/huang_zhong_shu_general.jpg
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
Oracle:Huang Zhong, Shu General can't be blocked by more than one creature.
diff --git a/forge-gui/res/cardsfolder/h/hungering_hydra.txt b/forge-gui/res/cardsfolder/h/hungering_hydra.txt
index 1d08bc12649..31cfeea226a 100644
--- a/forge-gui/res/cardsfolder/h/hungering_hydra.txt
+++ b/forge-gui/res/cardsfolder/h/hungering_hydra.txt
@@ -2,7 +2,7 @@ Name:Hungering Hydra
ManaCost:X G
Types:Creature Hydra
PT:0/0
-K:CantBeBlockedByAmount GT1
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
K:etbCounter:P1P1:X
SVar:X:Count$xPaid
T:Mode$ DamageDoneOnce | ValidTarget$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME is dealt damage, put that many +1/+1 counters on CARDNAME.
diff --git a/forge-gui/res/cardsfolder/i/impulsive_pilferer.txt b/forge-gui/res/cardsfolder/i/impulsive_pilferer.txt
index 18fc9fe67b6..edcb05c15c1 100644
--- a/forge-gui/res/cardsfolder/i/impulsive_pilferer.txt
+++ b/forge-gui/res/cardsfolder/i/impulsive_pilferer.txt
@@ -5,7 +5,7 @@ PT:1/1
K:Encore:3 R
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigToken | TriggerDescription$ When CARDNAME dies, create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.")
SVar:TrigToken:DB$ Token | TokenScript$ c_a_treasure_sac | TokenAmount$ 1
-DeckHas:Ablity$Token
+DeckHas:Ability$Token
DeckHints:Ability$Sacrifice
SVar:SacMe:1
Oracle:When Impulsive Pilferer dies, create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.")\nEncore {3}{R} ({3}{R}, Exile this card from your graveyard: For each opponent, create a token copy that attacks that opponent this turn if able. They gain haste. Sacrifice them at the beginning of the next end step. Activate only as a sorcery.)
diff --git a/forge-gui/res/cardsfolder/i/ironhoof_ox.txt b/forge-gui/res/cardsfolder/i/ironhoof_ox.txt
index 1ad42910748..3b38365b7e4 100644
--- a/forge-gui/res/cardsfolder/i/ironhoof_ox.txt
+++ b/forge-gui/res/cardsfolder/i/ironhoof_ox.txt
@@ -2,6 +2,5 @@ Name:Ironhoof Ox
ManaCost:3 G G
Types:Creature Ox
PT:4/4
-K:CantBeBlockedByAmount GT1
-SVar:Picture:http://resources.wizards.com/magic/cards/p2/en-us/card6628.jpg
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
Oracle:Ironhoof Ox can't be blocked by more than one creature.
diff --git a/forge-gui/res/cardsfolder/k/kessig_prowler_sinuous_predator.txt b/forge-gui/res/cardsfolder/k/kessig_prowler_sinuous_predator.txt
index 5ca233feeeb..bcfb4ab31b3 100644
--- a/forge-gui/res/cardsfolder/k/kessig_prowler_sinuous_predator.txt
+++ b/forge-gui/res/cardsfolder/k/kessig_prowler_sinuous_predator.txt
@@ -1,36 +1,18 @@
Name:Kessig Prowler
-
ManaCost:G
-
Types:Creature Werewolf Horror
-
PT:2/1
-
A:AB$SetState | Cost$ 4 G | Defined$ Self | Mode$ Transform | SpellDescription$ Transform CARDNAME.
-
-SVar:Picture:http://www.wizards.com/global/images/magic/general/kessig_prowler.jpg
-
AlternateMode:DoubleFaced
-
Oracle:{4}{G}: Transform Kessig Prowler.
-
ALTERNATE
-
Name:Sinuous Predator
-
ManaCost:no cost
-
Types:Creature Eldrazi Werewolf
-
PT:4/4
-
-K:CantBeBlockedByAmount GT1
-
-SVar:Picture:http://www.wizards.com/global/images/magic/general/sinuous_predator.jpg
-
-Oracle:Sinuous Predator can't be blocked by more than one creature.
-
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
+Oracle:Sinuous Predator can't be blocked by more than one creature.
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/k/kozilek_the_great_distortion.txt b/forge-gui/res/cardsfolder/k/kozilek_the_great_distortion.txt
index 216119d18c6..014c7cabdeb 100644
--- a/forge-gui/res/cardsfolder/k/kozilek_the_great_distortion.txt
+++ b/forge-gui/res/cardsfolder/k/kozilek_the_great_distortion.txt
@@ -3,7 +3,7 @@ ManaCost:8 C C
Types:Legendary Creature Eldrazi
PT:12/12
T:Mode$ SpellCast | ValidCard$ Card.Self | Execute$ TrigDraw | CheckSVar$ Y | SVarCompare$ LT7 | TriggerDescription$ When you cast this spell, if you have fewer than seven cards in hand, draw cards equal to the difference.
-SVar:TrigDraw:DB$Draw | Defined$ You | NumCards$ Difference
+SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ Difference
SVar:Y:Count$InYourHand
SVar:Difference:Number$7/Minus.Y
K:Menace
diff --git a/forge-gui/res/cardsfolder/k/krosan_vorine.txt b/forge-gui/res/cardsfolder/k/krosan_vorine.txt
index 81c57beebba..28f568436c9 100644
--- a/forge-gui/res/cardsfolder/k/krosan_vorine.txt
+++ b/forge-gui/res/cardsfolder/k/krosan_vorine.txt
@@ -3,6 +3,5 @@ ManaCost:3 G
Types:Creature Cat Beast
PT:3/2
K:Provoke
-K:CantBeBlockedByAmount GT1
-SVar:Picture:http://www.wizards.com/global/images/magic/general/krosan_vorine.jpg
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
Oracle:Provoke (Whenever this creature attacks, you may have target creature defending player controls untap and block it if able.)\nKrosan Vorine can't be blocked by more than one creature.
diff --git a/forge-gui/res/cardsfolder/m/mycosynth_lattice.txt b/forge-gui/res/cardsfolder/m/mycosynth_lattice.txt
index e56f91f4a87..5330ef704f6 100644
--- a/forge-gui/res/cardsfolder/m/mycosynth_lattice.txt
+++ b/forge-gui/res/cardsfolder/m/mycosynth_lattice.txt
@@ -2,7 +2,7 @@ Name:Mycosynth Lattice
ManaCost:6
Types:Artifact
S:Mode$ Continuous | Affected$ Permanent | AddType$ Artifact | Description$ All permanents are artifact in addition to their other types.
-S:Mode$ Continuous| Affected$ Card | SetColor$ Colorless | AffectedZone$ Battlefield,Hand,Library,Graveyard,Exile,Stack,Command | Description$ All cards that aren't on the battlefield, spells, and permanents are colorless.
+S:Mode$ Continuous | Affected$ Card | SetColor$ Colorless | AffectedZone$ Battlefield,Hand,Library,Graveyard,Exile,Stack,Command | Description$ All cards that aren't on the battlefield, spells, and permanents are colorless.
S:Mode$ Continuous | Affected$ Player | ManaConversion$ AnyType->AnyColor | Description$ Players may spend mana as though it were mana of any color.
SVar:NonStackingEffect:True
AI:RemoveDeck:Random
diff --git a/forge-gui/res/cardsfolder/n/norwood_riders.txt b/forge-gui/res/cardsfolder/n/norwood_riders.txt
index 5550682df3d..c18af6e7b92 100644
--- a/forge-gui/res/cardsfolder/n/norwood_riders.txt
+++ b/forge-gui/res/cardsfolder/n/norwood_riders.txt
@@ -2,6 +2,5 @@ Name:Norwood Riders
ManaCost:3 G
Types:Creature Elf
PT:3/3
-K:CantBeBlockedByAmount GT1
-SVar:Picture:http://resources.wizards.com/magic/cards/p2/en-us/card6615.jpg
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
Oracle:Norwood Riders can't be blocked by more than one creature.
diff --git a/forge-gui/res/cardsfolder/o/orcish_artillery.txt b/forge-gui/res/cardsfolder/o/orcish_artillery.txt
index 9fd1c514a97..81e016cda17 100644
--- a/forge-gui/res/cardsfolder/o/orcish_artillery.txt
+++ b/forge-gui/res/cardsfolder/o/orcish_artillery.txt
@@ -6,6 +6,5 @@ A:AB$ DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt
SVar:DBDealDamage:DB$DealDamage | Defined$ You | NumDmg$ 3 | SubAbility$ DBDamageResolve
SVar:DBDamageResolve:DB$ DamageResolve
SVar:SelfDamageAmount:3
-AI:RemoveDeck:Random
SVar:Picture:http://resources.wizards.com/magic/cards/9ed/en-us/card83193.jpg
Oracle:{T}: Orcish Artillery deals 2 damage to any target and 3 damage to you.
diff --git a/forge-gui/res/cardsfolder/o/outland_colossus.txt b/forge-gui/res/cardsfolder/o/outland_colossus.txt
index 144504fe332..028f7fb1f68 100644
--- a/forge-gui/res/cardsfolder/o/outland_colossus.txt
+++ b/forge-gui/res/cardsfolder/o/outland_colossus.txt
@@ -3,7 +3,6 @@ ManaCost:3 G G
Types:Creature Giant
PT:6/6
K:Renown:6
-K:CantBeBlockedByAmount GT1
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
DeckHas:Ability$Counters
-SVar:Picture:http://www.wizards.com/global/images/magic/general/outland_colossus.jpg
Oracle:Renown 6 (When this creature deals combat damage to a player, if it isn't renowned, put six +1/+1 counters on it and it becomes renowned.)\nOutland Colossus can't be blocked by more than one creature.
diff --git a/forge-gui/res/cardsfolder/p/pathrazer_of_ulamog.txt b/forge-gui/res/cardsfolder/p/pathrazer_of_ulamog.txt
index 2ebff851e86..5544d18b79b 100644
--- a/forge-gui/res/cardsfolder/p/pathrazer_of_ulamog.txt
+++ b/forge-gui/res/cardsfolder/p/pathrazer_of_ulamog.txt
@@ -3,6 +3,5 @@ ManaCost:11
Types:Creature Eldrazi
PT:9/9
K:Annihilator:3
-K:CantBeBlockedByAmount LT3
-SVar:Picture:http://www.wizards.com/global/images/magic/general/pathrazer_of_ulamog.jpg
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures.
Oracle:Annihilator 3 (Whenever this creature attacks, defending player sacrifices three permanents.)\nPathrazer of Ulamog can't be blocked except by three or more creatures.
diff --git a/forge-gui/res/cardsfolder/p/phyrexian_colossus.txt b/forge-gui/res/cardsfolder/p/phyrexian_colossus.txt
index 01257985344..c9907d8bf86 100644
--- a/forge-gui/res/cardsfolder/p/phyrexian_colossus.txt
+++ b/forge-gui/res/cardsfolder/p/phyrexian_colossus.txt
@@ -4,7 +4,6 @@ Types:Artifact Creature Phyrexian Golem
PT:8/8
K:CARDNAME doesn't untap during your untap step.
A:AB$ Untap | Cost$ PayLife<8> | SpellDescription$ Untap CARDNAME.
-K:CantBeBlockedByAmount LT3
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures.
AI:RemoveDeck:All
-SVar:Picture:http://www.wizards.com/global/images/magic/general/phyrexian_colossus.jpg
Oracle:Phyrexian Colossus doesn't untap during your untap step.\nPay 8 life: Untap Phyrexian Colossus.\nPhyrexian Colossus can't be blocked except by three or more creatures.
diff --git a/forge-gui/res/cardsfolder/p/primitive_justice.txt b/forge-gui/res/cardsfolder/p/primitive_justice.txt
index cd61adc573d..5b21a626c38 100644
--- a/forge-gui/res/cardsfolder/p/primitive_justice.txt
+++ b/forge-gui/res/cardsfolder/p/primitive_justice.txt
@@ -2,7 +2,7 @@ Name:Primitive Justice
ManaCost:1 R
Types:Sorcery
A:SP$ Destroy | Cost$ X 1 R | XColor$ RG | Announce$ AdditionalCostPayTimes | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | TargetMin$ TargetNum | TargetMax$ TargetNum | SubAbility$ DBGainLife | SpellDescription$ Destroy target artifact. For each additional {1}{R} you paid, destroy another target artifact. For each additional {1}{G} you paid, destroy another target artifact, and you gain 1 life.
-S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ IncreaseCost | EffectZone$ All |Description$ As an additional cost to cast this spell, you may pay {1}{R} and/or {1}{G} any number of times.
+S:Mode$ RaiseCost | ValidCard$ Card.Self | Type$ Spell | Amount$ IncreaseCost | EffectZone$ All | Description$ As an additional cost to cast this spell, you may pay {1}{R} and/or {1}{G} any number of times.
SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ GreenManaPaid
SVar:AdditionalCostPayTimes:Number$0
SVar:TargetNum:SVar$AdditionalCostPayTimes/Plus.1
diff --git a/forge-gui/res/cardsfolder/p/profane_transfusion.txt b/forge-gui/res/cardsfolder/p/profane_transfusion.txt
index d48ca1b371e..f2e4fa895e0 100755
--- a/forge-gui/res/cardsfolder/p/profane_transfusion.txt
+++ b/forge-gui/res/cardsfolder/p/profane_transfusion.txt
@@ -5,6 +5,6 @@ A:SP$ ExchangeLife | Cost$ 6 B B B | TargetMin$ 2 | TargetMax$ 2 | ValidTgts$ Pl
SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_x_x_a_horror | TokenOwner$ You | TokenPower$ X | TokenToughness$ X | SubAbility$ DBCleanup | StackDescription$ {p:You} creates an X/X colorless Horror artifact creature token, where X is the difference between those players' life totals.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Count$RememberedNumber/Abs
-DeckHas:Ablity$Token
+DeckHas:Ability$Token
AI:RemoveDeck:All
Oracle:Two target players exchange life totals. You create an X/X colorless Horror artifact creature token, where X is the difference between those players' life totals.
diff --git a/forge-gui/res/cardsfolder/r/revival_revenge.txt b/forge-gui/res/cardsfolder/r/revival_revenge.txt
index 0c21541fdc5..00c1c485b06 100644
--- a/forge-gui/res/cardsfolder/r/revival_revenge.txt
+++ b/forge-gui/res/cardsfolder/r/revival_revenge.txt
@@ -1,8 +1,9 @@
Name:Revival
ManaCost:WB WB
-AlternateMode: Split
+AlternateMode:Split
Types:Sorcery
-A:SP$ ChangeZone | Cost$ WB WB | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature in your graveyard | ValidTgts$ Creature.YouOwn+cmcLE3 | SpellDescription$ Return target creature card with mana value 3 or less from your graveyard to the battlefield.
+A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target creature card in your graveyard with mana value 3 or less | ValidTgts$ Creature.YouOwn+cmcLE3 | SpellDescription$ Return target creature card with mana value 3 or less from your graveyard to the battlefield.
+DeckHas:Ability$Graveyard
Oracle:Return target creature card with mana value 3 or less from your graveyard to the battlefield.
ALTERNATE
@@ -10,8 +11,8 @@ ALTERNATE
Name:Revenge
ManaCost:4 W B
Types:Sorcery
-A:SP$ GainLife | Cost$ 4 W B | LifeAmount$ X | SubAbility$ DBLoseHalf | SpellDescription$ Double your life total. Target opponent loses half their life, rounded up.
+A:SP$ SetLife | LifeAmount$ X | SubAbility$ DBLoseHalf | SpellDescription$ Double your life total. Target opponent loses half their life, rounded up.
SVar:DBLoseHalf:DB$ LoseLife | ValidTgts$ Opponent | LifeAmount$ Y
-SVar:X:Count$YourLifeTotal
+SVar:X:Count$YourLifeTotal/Twice
SVar:Y:Count$TargetedLifeTotal/HalfUp
Oracle:Double your life total. Target opponent loses half their life, rounded up.
diff --git a/forge-gui/res/cardsfolder/r/riftmarked_knight.txt b/forge-gui/res/cardsfolder/r/riftmarked_knight.txt
index 9065e9b3cdd..98c1f7ac1fa 100644
--- a/forge-gui/res/cardsfolder/r/riftmarked_knight.txt
+++ b/forge-gui/res/cardsfolder/r/riftmarked_knight.txt
@@ -5,7 +5,7 @@ PT:2/2
K:Flanking
K:Protection from black
K:Suspend:3:1 W W
-T:Mode$ CounterRemoved | ValidCard$ Card.Self | TriggerZones$ Exile | CounterType$ TIME | Execute$ TrigToken | IsPresent$ Card.Self+counters_GE1_TIME | PresentZone$ Exile | PresentCompare$ EQ0 | TriggerDescription$ When the last time counter is removed from CARDNAME while it's exiled, create a 2/2 black Knight creature token with flanking, protection from white, and haste.
+T:Mode$ CounterRemoved | ValidCard$ Card.Self | TriggerZones$ Exile | CounterType$ TIME | Execute$ TrigToken | NewCounterAmount$ 0 | TriggerDescription$ When the last time counter is removed from CARDNAME while it's exiled, create a 2/2 black Knight creature token with flanking, protection from white, and haste.
SVar:TrigToken:DB$Token | TokenAmount$ 1 | TokenOwner$ You | TokenScript$ b_2_2_knight_flanking_pro_white_haste | LegacyImage$ b 2 2 knight flanking pro white haste plc
SVar:Picture:http://www.wizards.com/global/images/magic/general/riftmarked_knight.jpg
Oracle:Protection from black; flanking (Whenever a creature without flanking blocks this creature, the blocking creature gets -1/-1 until end of turn.)\nSuspend 3—{1}{W}{W}\nWhen the last time counter is removed from Riftmarked Knight while it's exiled, create a 2/2 black Knight creature token with flanking, protection from white, and haste.
diff --git a/forge-gui/res/cardsfolder/s/sawtusk_demolisher.txt b/forge-gui/res/cardsfolder/s/sawtusk_demolisher.txt
index 647711a2cfb..c9ba959430a 100644
--- a/forge-gui/res/cardsfolder/s/sawtusk_demolisher.txt
+++ b/forge-gui/res/cardsfolder/s/sawtusk_demolisher.txt
@@ -5,8 +5,7 @@ PT:6/6
K:Mutate:3 G
K:Trample
T:Mode$ Mutates | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigDestroy | TriggerDescription$ Whenever this creature mutates, destroy target noncreature permanent. Its controller creates a 3/3 green Beast creature token.
-SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonCreature | TgtPrompt$ Select target noncreature permanent | AITgts$ Card.cmcGE4 | RememberLKI$ True | SubAbility$ DBToken
-SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_3_3_beast | TokenOwner$ RememberedController | SubAbility$ DBCleanup
-SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonCreature | TgtPrompt$ Select target noncreature permanent | AITgts$ Card.cmcGE4 | SubAbility$ DBToken
+SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_3_3_beast | TokenOwner$ TargetedController
DeckHas:Ability$Token
Oracle:Mutate {3}{G} (If you cast this spell for its mutate cost, put it over or under target non-Human creature you own. They mutate into the creature on top plus all abilities from under it.)\nTrample\nWhenever this creature mutates, destroy target noncreature permanent. Its controller creates a 3/3 green Beast creature token.
diff --git a/forge-gui/res/cardsfolder/s/sonorous_howlbonder.txt b/forge-gui/res/cardsfolder/s/sonorous_howlbonder.txt
index 53ef5c6700c..bca6e3c94fa 100755
--- a/forge-gui/res/cardsfolder/s/sonorous_howlbonder.txt
+++ b/forge-gui/res/cardsfolder/s/sonorous_howlbonder.txt
@@ -3,6 +3,6 @@ ManaCost:1 B/R B/R
Types:Creature Human Warrior
PT:2/2
K:Menace
-S:Mode$ Continuous | Affected$ Creature.YouCtrl+withMenace | AddHiddenKeyword$ CantBeBlockedByAmount LT3 | Description$ Each creature you control with menace can't be blocked except by three or more creatures.
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl+withMenace | Min$ 3 | Description$ Each creature you control with menace can't be blocked except by three or more creatures.
SVar:PlayMain1:TRUE
Oracle:Menace\nEach creature you control with menace can't be blocked except by three or more creatures.
diff --git a/forge-gui/res/cardsfolder/s/sparktongue_dragon.txt b/forge-gui/res/cardsfolder/s/sparktongue_dragon.txt
index 286950cfd28..02f57fb312c 100644
--- a/forge-gui/res/cardsfolder/s/sparktongue_dragon.txt
+++ b/forge-gui/res/cardsfolder/s/sparktongue_dragon.txt
@@ -3,7 +3,7 @@ ManaCost:3 R R
Types:Creature Dragon
K:Flying
T:Mode$ ChangesZone | OptionalDecider$ You | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigPayCost | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {2}{R}. When you do, it deals 3 damage to any target.
-SVar:TrigPayCost:AB$ ImmediateTrigger | Cost$ 2 R | Execute$ TrigABDealDamage | TriggerDescription$ When you pay {2}{R}, CARDNAME deals 3 damage to any target
+SVar:TrigPayCost:AB$ ImmediateTrigger | Cost$ 2 R | Execute$ TrigABDealDamage | TriggerDescription$ When you pay {2}{R}, CARDNAME deals 3 damage to any target.
SVar:TrigABDealDamage:DB$ DealDamage | NumDmg$ 3 | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | SpellDescription$ CARDNAME deals 3 damage to any target.
Oracle:Flying\nWhen Sparktongue Dragon enters the battlefield, you may pay {2}{R}. When you do, it deals 3 damage to any target.
PT:3/3
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/s/stalking_tiger.txt b/forge-gui/res/cardsfolder/s/stalking_tiger.txt
index 58a93d527b5..9ca7ad00543 100644
--- a/forge-gui/res/cardsfolder/s/stalking_tiger.txt
+++ b/forge-gui/res/cardsfolder/s/stalking_tiger.txt
@@ -2,6 +2,5 @@ Name:Stalking Tiger
ManaCost:3 G
Types:Creature Cat
PT:3/3
-K:CantBeBlockedByAmount GT1
-SVar:Picture:http://www.wizards.com/global/images/magic/general/stalking_tiger.jpg
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
Oracle:Stalking Tiger can't be blocked by more than one creature.
diff --git a/forge-gui/res/cardsfolder/s/sunder_shaman.txt b/forge-gui/res/cardsfolder/s/sunder_shaman.txt
index 0c16a979a37..65ce6d16942 100644
--- a/forge-gui/res/cardsfolder/s/sunder_shaman.txt
+++ b/forge-gui/res/cardsfolder/s/sunder_shaman.txt
@@ -2,7 +2,7 @@ Name:Sunder Shaman
ManaCost:R R G G
Types:Creature Giant Shaman
PT:5/5
-K:CantBeBlockedByAmount GT1
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDestroy | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, destroy target artifact or enchantment that player controls.
SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Artifact.ControlledBy TriggeredDefendingPlayer,Enchantment.ControlledBy TriggeredDefendingPlayer | TgtPrompt$ Select target artifact or enchantment that player controls.
Oracle:Sunder Shaman can't be blocked by more than one creature.\nWhenever Sunder Shaman deals combat damage to a player, destroy target artifact or enchantment that player controls.
diff --git a/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt b/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt
index 27ba3d7c547..768ba8a3a8a 100644
--- a/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt
+++ b/forge-gui/res/cardsfolder/s/szadek_lord_of_secrets.txt
@@ -7,5 +7,5 @@ R:Event$ DamageDone | ActiveZones$ Battlefield | IsCombat$ True | ValidSource$ C
SVar:X:ReplaceCount$DamageAmount
SVar:CountersAndMill:DB$ PutCounter | Defined$ Self | CounterNum$ X | CounterType$ P1P1 | SubAbility$ Mill
SVar:Mill:DB$ Mill | Defined$ ReplacedTarget | NumCards$ X
-DeckHas:Ablity$Counters
+DeckHas:Ability$Counters
Oracle:Flying\nIf Szadek, Lord of Secrets would deal combat damage to a player, instead put that many +1/+1 counters on Szadek and that player mills that many cards.
diff --git a/forge-gui/res/cardsfolder/t/tahngarth_first_mate.txt b/forge-gui/res/cardsfolder/t/tahngarth_first_mate.txt
index 39487a986ac..f7b2a1c2aa9 100644
--- a/forge-gui/res/cardsfolder/t/tahngarth_first_mate.txt
+++ b/forge-gui/res/cardsfolder/t/tahngarth_first_mate.txt
@@ -2,7 +2,7 @@ Name:Tahngarth, First Mate
ManaCost:2 R G
Types:Legendary Creature Minotaur Warrior
PT:5/5
-K:CantBeBlockedByAmount GT1
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
T:Mode$ AttackersDeclared | AttackingPlayer$ Player.Opponent | Execute$ TrigGainControl | TriggerZones$ Battlefield | OptionalDecider$ You | IsPresent$ Card.Self+tapped | TriggerDescription$ Whenever an opponent attacks with one or more creatures, if CARDNAME is tapped, you may have that opponent gain control of CARDNAME until end of combat. If you do, choose a player or planeswalker that opponent is attacking. CARDNAME is attacking that player or planeswalker.
SVar:TrigGainControl:DB$ GainControl | Defined$ Self | NewController$ TriggeredAttackingPlayer | LoseControl$ EndOfCombat | Attacking$ Player.Defending | Chooser$ You | ChoosePlayerOrPlaneswalker$ True
AI:RemoveDeck:All
diff --git a/forge-gui/res/cardsfolder/t/the_akroan_war.txt b/forge-gui/res/cardsfolder/t/the_akroan_war.txt
index d6e6f4ad224..efffc5c30bf 100644
--- a/forge-gui/res/cardsfolder/t/the_akroan_war.txt
+++ b/forge-gui/res/cardsfolder/t/the_akroan_war.txt
@@ -3,7 +3,9 @@ ManaCost:3 R
Types:Enchantment Saga
K:Saga:3:DBGainControl,DBAllAttack,DBDamageTapped
SVar:DBGainControl:DB$ GainControl | ValidTgts$ Creature | TgtPrompt$ Select target creature | LoseControl$ LeavesPlay | SpellDescription$ Gain control of target creature for as long as CARDNAME remains on the battlefield.
-SVar:DBAllAttack:DB$ PumpAll | ValidCards$ Creature.OppCtrl | Duration$ UntilYourNextTurn | KW$ HIDDEN CARDNAME attacks each combat if able. | SpellDescription$ Until your next turn, creatures your opponents control attack each turn if able.
+
+# Refactor as Effect
+SVar:DBAllAttack:DB$ PumpAll | ValidCards$ Creature.OppCtrl | Duration$ UntilYourNextTurn | KW$ HIDDEN CARDNAME attacks each combat if able. | SpellDescription$ Until your next turn, creatures your opponents control attack each combat if able.
SVar:DBDamageTapped:DB$ EachDamage | ValidCards$ Creature.tapped | NumDmg$ X | DamageDesc$ damage equal to its power | DefinedCards$ Self | SpellDescription$ Each tapped creature deals damage to itself equal to its power.
SVar:X:Count$CardPower
Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Gain control of target creature for as long as The Akroan War remains on the battlefield.\nII — Until your next turn, creatures your opponents control attack each combat if able.\nIII — Each tapped creature deals damage to itself equal to its power.
diff --git a/forge-gui/res/cardsfolder/u/underworld_cerberus.txt b/forge-gui/res/cardsfolder/u/underworld_cerberus.txt
index 19110337cd3..4f2fac4949c 100644
--- a/forge-gui/res/cardsfolder/u/underworld_cerberus.txt
+++ b/forge-gui/res/cardsfolder/u/underworld_cerberus.txt
@@ -2,7 +2,7 @@ Name:Underworld Cerberus
ManaCost:3 B R
Types:Creature Dog
PT:6/6
-K:CantBeBlockedByAmount LT3
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.Self | Min$ 3 | Description$ CARDNAME can't be blocked except by three or more creatures.
S:Mode$ CantTarget | AffectedZone$ Graveyard | Description$ Cards in graveyards can't be the targets of spells or abilities.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigExile | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, exile it and each player returns all creature cards from their graveyard to their hand.
SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | Defined$ TriggeredNewCardLKICopy | SubAbility$ DBChangeZoneAll
diff --git a/forge-gui/res/cardsfolder/upcoming/augur_of_autumn.txt b/forge-gui/res/cardsfolder/upcoming/augur_of_autumn.txt
new file mode 100644
index 00000000000..d4bf43eb042
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/augur_of_autumn.txt
@@ -0,0 +1,9 @@
+Name:Augur of Autumn
+ManaCost:1 G G
+Types:Creature Human Druid
+PT:2/3
+S:Mode$ Continuous | Affected$ Card.TopLibrary+YouCtrl | AffectedZone$ Library | MayLookAt$ You | Description$ You may look at the top card of your library any time.
+S:Mode$ Continuous | Affected$ Land.TopLibrary+YouCtrl | AffectedZone$ Library | MayPlay$ True | Description$ You may play lands from the top of your library.
+S:Mode$ Continuous | CheckSVar$ X | SVarCompare$ GE3 | Affected$ Creature.TopLibrary+YouCtrl | AffectedZone$ Library | MayPlay$ True | Description$ Coven — As long as you control three or more creatures with different powers, you may cast creature spells from the top of your library.
+SVar:X:Count$DifferentPower_Creature.YouCtrl
+Oracle:You may look at the top card of your library any time.\nYou may play lands from the top of your library.\nCoven — As long as you control three or more creatures with different powers, you may cast creature spells from the top of your library.
diff --git a/forge-gui/res/cardsfolder/upcoming/baithook_angler_hook_haunt_drifter.txt b/forge-gui/res/cardsfolder/upcoming/baithook_angler_hook_haunt_drifter.txt
index ee761c99313..a850c9831cb 100644
--- a/forge-gui/res/cardsfolder/upcoming/baithook_angler_hook_haunt_drifter.txt
+++ b/forge-gui/res/cardsfolder/upcoming/baithook_angler_hook_haunt_drifter.txt
@@ -4,6 +4,7 @@ Types:Creature Human Peasant
PT:2/1
K:Disturb:1 U
AlternateMode:DoubleFaced
+DeckHas:Ability$Graveyard
Oracle:Disturb {1}{U} (You may cast this card from your graveyard transformed for its disturb cost.)
ALTERNATE
diff --git a/forge-gui/res/cardsfolder/upcoming/beloved_beggar_generous_soul.txt b/forge-gui/res/cardsfolder/upcoming/beloved_beggar_generous_soul.txt
index 8119a4e8cab..b3eacb9cd1f 100644
--- a/forge-gui/res/cardsfolder/upcoming/beloved_beggar_generous_soul.txt
+++ b/forge-gui/res/cardsfolder/upcoming/beloved_beggar_generous_soul.txt
@@ -4,6 +4,7 @@ Types:Creature Human Peasant
PT:0/4
K:Disturb:4 W W
AlternateMode:DoubleFaced
+DeckHas:Ability$Graveyard
Oracle:Disturb {4}{W}{W} (You may cast this card from your graveyard transformed for its disturb cost.)
ALTERNATE
diff --git a/forge-gui/res/cardsfolder/upcoming/bounding_wolf.txt b/forge-gui/res/cardsfolder/upcoming/bounding_wolf.txt
new file mode 100644
index 00000000000..f4004c4dc8c
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/bounding_wolf.txt
@@ -0,0 +1,7 @@
+Name:Bounding Wolf
+ManaCost:2 G
+Types:Creature wolf
+PT:3/2
+K:Flash
+K:Reach
+Oracle:Flash\nReach
diff --git a/forge-gui/res/cardsfolder/upcoming/candlegrove_witch.txt b/forge-gui/res/cardsfolder/upcoming/candlegrove_witch.txt
new file mode 100644
index 00000000000..d234b739e91
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/candlegrove_witch.txt
@@ -0,0 +1,8 @@
+Name:Candlegrove Witch
+ManaCost:1 W
+Types:Creature Human Warlock
+PT:2/2
+T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigPump | TriggerDescription$ Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, CARDNAME gains flying until end of turn.
+SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ Flying
+SVar:X:Count$DifferentPower_Creature.YouCtrl
+Oracle:Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, Candlegrove Witch gains flying until end of turn.
diff --git a/forge-gui/res/cardsfolder/upcoming/clarion_cathars.txt b/forge-gui/res/cardsfolder/upcoming/clarion_cathars.txt
new file mode 100644
index 00000000000..ce380447bfe
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/clarion_cathars.txt
@@ -0,0 +1,8 @@
+Name:Clarion Cathars
+ManaCost:3 W
+Types:Creature Human Knight
+PT:3/3
+T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 white Human creature token.
+SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human | TokenOwner$ You
+DeckHas:Ability$Token
+Oracle:When Clarion Cathars enters the battlefield, create a 1/1 white Human creature token.
diff --git a/forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt b/forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt
new file mode 100644
index 00000000000..d32dcea287c
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/covert_cutpurse_covetous_geist.txt
@@ -0,0 +1,23 @@
+Name:Covert Cutpurse
+ManaCost:2 B
+Types:Creature Human Rogue
+PT:2/1
+T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerDescription$ When CARDNAME enters the battlefield, destroy target creature you don't control that was dealt damage this turn.
+SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Creature.YouDontCtrl+wasDealtDamageThisTurn | TgtPrompt$ Select target creature you don't control that was dealt damage this turn
+K:Disturb:4 B
+AlternateMode:DoubleFaced
+DeckHas:Ability$Graveyard
+Oracle:When Covert Cutpurse enters the battlefield, destroy target creature you don't control that was dealt damage this turn.\nDisturb {4}{B} (You may cast this card from your graveyard transformed for its disturb cost.)
+
+ALTERNATE
+
+Name:Covetous Geist
+ManaCost:no cost
+Types:Creature Spirit Rogue
+Colors:black
+PT:2/2
+K:Flying
+K:Deathtouch
+R:Event$ Moved | ValidCard$ Card.Self | Destination$ Graveyard | ReplaceWith$ Exile | Description$ If CARDNAME would be put into a graveyard from anywhere, exile it instead.
+SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
+Oracle:Flying, deathtouch\nIf Covetous Geist would be put into a graveyard from anywhere, exile it instead.
diff --git a/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt b/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt
new file mode 100644
index 00000000000..3b564673845
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/covetous_castaway_ghostly_castigator.txt
@@ -0,0 +1,24 @@
+Name:Covetous Castaway
+ManaCost:1 U
+Types:Creature Human
+PT:1/3
+K:Disturb:3 U U
+T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME dies, mill three cards.
+SVar:TrigMill:DB$ Mill | NumCards$ 3 | Defined$ You
+AlternateMode:DoubleFaced
+DeckHas:Ability$Mill & Ability$Graveyard
+Oracle:When Covetous Castaway dies, mill three cards.\nDisturb {3}{U}{U} (You may cast this card from your graveyard transformed for its disturb cost.)
+
+ALTERNATE
+
+Name:Ghostly Castigator
+ManaCost:no cost
+Types:Creature Spirit
+Colors:blue
+PT:3/4
+K:Flying
+T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, shuffle up to three target cards from your graveyard into your library.
+SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | ValidTgts$ Card.YouOwn | TgtPrompt$ Select up to three target cards from your graveyard | TargetMin$ 0 | TargetMax$ 3 | Shuffle$ True
+R:Event$ Moved | ValidCard$ Card.Self | Destination$ Graveyard | ReplaceWith$ Exile | Description$ If CARDNAME would be put into a graveyard from anywhere, exile it instead.
+SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard
+Oracle:Flying\nWhen Ghostly Castigator enters the battlefield, shuffle up to three target cards from your graveyard into your library.\nIf Ghostly Castigator would be put into a graveyard from anywhere, exile it instead.
diff --git a/forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt b/forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt
new file mode 100644
index 00000000000..f67d2abc1a0
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/croaking_counterpart.txt
@@ -0,0 +1,7 @@
+Name:Croaking Counterpart
+ManaCost:1 G U
+Types:Sorcery
+A:SP$ CopyPermanent | ValidTgts$ Creature.nonFrog | TgtPrompt$ Select target non-Frog creature | SetPower$ 1 | SetToughness$ 1 | SetColor$ Green | SetCreatureTypes$ Frog | StackDescription$ Create a token that's a copy of {c:Targeted}, except it's a 1/1 green Frog. | SpellDescription$ Create a token that's a copy of target non-Frog creature, except it's a 1/1 green Frog.
+K:Flashback:3 G U
+DeckHas:Ability$Token & Ability$Graveyard
+Oracle:Create a token that's a copy of target non-Frog creature, except it's a 1/1 green Frog.\nFlashback {3}{G}{U} (You may cast this card from your graveyard for its flashback cost. Then exile it.)
diff --git a/forge-gui/res/cardsfolder/upcoming/dawnhart_wardens.txt b/forge-gui/res/cardsfolder/upcoming/dawnhart_wardens.txt
new file mode 100644
index 00000000000..40141d1c656
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/dawnhart_wardens.txt
@@ -0,0 +1,9 @@
+Name:Dawnhart Wardens
+ManaCost:1 G W
+Types:Creature Human Warlock
+PT:3/3
+K:Vigilance
+T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigPumpAll | TriggerDescription$ Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, creatures you control get +1/+0 until end of turn.
+SVar:TrigPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ +1
+SVar:X:Count$DifferentPower_Creature.YouCtrl
+Oracle:Vigilance\nCoven — At the beginning of combat on your turn, if you control three or more creatures with different powers, creatures you control get +1/+0 until end of turn.
diff --git a/forge-gui/res/cardsfolder/upcoming/defend_the_celestus.txt b/forge-gui/res/cardsfolder/upcoming/defend_the_celestus.txt
new file mode 100644
index 00000000000..ad022735c2b
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/defend_the_celestus.txt
@@ -0,0 +1,6 @@
+Name:Defend the Celestus
+ManaCost:2 G G
+Types:Instant
+A:SP$ PutCounter | Cost$ 2 G G | ValidTgts$ Creature | TgtPrompt$ Select target creature to distribute counters to | CounterType$ P1P1 | CounterNum$ 3 | TargetMin$ 1 | TargetMax$ 3 | DividedAsYouChoose$ 3 | SpellDescription$ Distribute three +1/+1 counters among one, two, or three target creatures.
+DeckHas:Ability$Counters
+Oracle:Distribute three +1/+1 counters among one, two or three target creatures you control.
diff --git a/forge-gui/res/cardsfolder/upcoming/devoted_grafkeeper_departed_soulkeeper.txt b/forge-gui/res/cardsfolder/upcoming/devoted_grafkeeper_departed_soulkeeper.txt
index 5617e0f3a51..c0f6d71278b 100644
--- a/forge-gui/res/cardsfolder/upcoming/devoted_grafkeeper_departed_soulkeeper.txt
+++ b/forge-gui/res/cardsfolder/upcoming/devoted_grafkeeper_departed_soulkeeper.txt
@@ -6,8 +6,7 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S
SVar:TrigMill:DB$ Mill | NumCards$ 2 | Defined$ You
T:Mode$ SpellCast | ValidCard$ Card.wasCastFromGraveyard | ValidActivatingPlayer$ You | Execute$ TrigTap | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a spell from your graveyard, tap target creature you don't control.
SVar:TrigTap:DB$ Tap | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control
-DeckHas:Ability$Mill
-DeckHints:Ability$Graveyard
+DeckHas:Ability$Mill & Ability$Graveyard
K:Disturb:1 W U
AlternateMode:DoubleFaced
Oracle:When Devoted Grafkeeper enters the battlefield, mill two cards.\nWhenever you cast a spell from your graveyard, tap target creature you don't control.\nDisturb {1}{W}{U} (You may cast this card from your graveyard transformed for its disturb cost.)
diff --git a/forge-gui/res/cardsfolder/upcoming/diregraf_rebirth.txt b/forge-gui/res/cardsfolder/upcoming/diregraf_rebirth.txt
new file mode 100644
index 00000000000..fc7815ef7e3
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/diregraf_rebirth.txt
@@ -0,0 +1,9 @@
+Name:Diregraf Rebirth
+ManaCost:3 B G
+Types:Sorcery
+S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each creature that died this turn.
+A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouOwn | SpellDescription$ Return target creature card from your graveyard to the battlefield.
+SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Creature
+K:Flashback:5 B G
+DeckHas:Ability$Graveyard
+Oracle:This spell costs {1} less to cast for each creature that died this turn.\nReturn target creature card from your graveyard to the battlefield.\nFlashback {5}{B}{G} (You may cast this card from your graveyard for its flashback cost. Then exile it.)
diff --git a/forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt b/forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt
new file mode 100644
index 00000000000..9848f14e4bc
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/enduring_angel_angelic_enforcer.txt
@@ -0,0 +1,31 @@
+Name:Enduring Angel
+ManaCost:2 W W W
+Types:Creature Angel
+PT:3/3
+K:Flying
+K:Double Strike
+S:Mode$ Continuous | Affected$ You | AddKeyword$ Hexproof | Description$ You have hexproof.
+R:Event$ LifeReduced | ValidPlayer$ You | Result$ LE0 | ReplaceWith$ Transform | Description$ If your life total would be reduced to 0 or less, instead transform CARDNAME and your life total becomes 3. Then if CARDNAME didn't transform this way, you lose the game.
+SVar:Transform:DB$ SetState | Defined$ Self | Mode$ Transform | RememberChanged$ True | SubAbility$ DBSetLife
+SVar:DBSetLife:DB$ SetLife | Defined$ You | LifeAmount$ 3 | SubAbility$ DBLoseGame
+SVar:DBLoseGame:DB$ LosesGame | Defined$ You | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ NE1 | SubAbility$ DBCleanup
+SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+AlternateMode:DoubleFaced
+Oracle:Flying, double strike\nYou have hexproof.\nIf your life total would be reduced to 0 or less, instead transform Enduring Angel and your life total becomes 3. Then if Enduring Angel didn't transform this way, you lose the game.
+
+ALTERNATE
+
+Name:Angelic Enforcer
+ManaCost:no cost
+Colors:white
+Types:Creature Angel
+PT:*/*
+K:Flying
+S:Mode$ Continuous | Affected$ You | AddKeyword$ Hexproof | Description$ You have hexproof.
+S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to your life total.
+T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDoubleLife | TriggerDescription$ Whenever CARDNAME attacks, double your life total.
+SVar:TrigDoubleLife:DB$ SetLife | LifeAmount$ Y
+SVar:X:Count$YourLifeTotal
+SVar:Y:Count$YourLifeTotal/Twice
+SVar:HasAttackEffect:TRUE
+Oracle:Flying\nYou have hexproof.\nAngelic Enforcer's power and toughness are each equal to your life total.\nWhenever Angelic Enforcer attacks, double your life total.
diff --git a/forge-gui/res/cardsfolder/upcoming/faithful_mending.txt b/forge-gui/res/cardsfolder/upcoming/faithful_mending.txt
new file mode 100644
index 00000000000..a9f5681dcf1
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/faithful_mending.txt
@@ -0,0 +1,9 @@
+Name:Faithful Mending
+ManaCost:W U
+Types:Instant
+A:SP$ GainLife | Defined$ You | LifeAmount$ 2 | SubAbility$ DBDraw | StackDescription$ {p:You} gains 2 life, | SpellDescription$ You gain 2 life, draw two cards, then discard two cards.
+SVar:DBDraw:DB$ Draw | NumCards$ 2 | SubAbility$ DBDiscard | StackDescription$ draws two cards,
+SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 2 | Mode$ TgtChoose | StackDescription$ then discards two cards.
+K:Flashback:1 W U
+DeckHas:Ability$LifeGain & Ability$Discard & Ability$Graveyard
+Oracle:You gain 2 life, draw two cards, then discard two cards.\nFlashback {1}{W}{U} (You may cast this card from your graveyard for its flashback cost. Then exile it.)
diff --git a/forge-gui/res/cardsfolder/upcoming/fleshtaker.txt b/forge-gui/res/cardsfolder/upcoming/fleshtaker.txt
new file mode 100644
index 00000000000..b086f563773
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/fleshtaker.txt
@@ -0,0 +1,10 @@
+Name:Fleshtaker
+ManaCost:W B
+Types:Creature Human Assassin
+PT:2/2
+T:Mode$ Sacrificed | ValidCard$ Creature.Other | Execute$ TrigGainLife | TriggerZones$ Battlefield | ValidPlayer$ You | TriggerDescription$ Whenever you sacrifice another creature, you gain 1 life and scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.)
+SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 | SubAbility$ DBScry
+SVar:DBScry:DB$ Scry | ScryNum$ 1
+A:AB$ Pump | Cost$ 1 Sac<1/Creature.Other/another creature> | Defined$ Self | NumAtt$ +2 | NumDef$ +2 | SpellDescription$ CARDNAME gets +2/+2 until end of turn.
+DeckHas:Ability$Sacrifice & Ability$LifeGain
+Oracle:Whenever you sacrifice another creature, you gain 1 life and scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.)\n{1}, Sacrifice another creature: Fleshtaker gets +2/+2 until end of turn.
diff --git a/forge-gui/res/cardsfolder/upcoming/gavony_silversmith.txt b/forge-gui/res/cardsfolder/upcoming/gavony_silversmith.txt
new file mode 100644
index 00000000000..a289ce6a16f
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/gavony_silversmith.txt
@@ -0,0 +1,8 @@
+Name:Gavony Silversmith
+ManaCost:3 W
+Types:Creature Human Soldier
+PT:3/3
+T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPut | TriggerDescription$ When CARDNAME enters the battlefield, put a +1/+1 counter on each of up to two target creatures.
+SVar:TrigPut:DB$ PutCounter | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature | TgtPrompt$ Select up to two target creatures | CounterType$ P1P1 | CounterNum$ 1
+DeckHas:Ability$Counters
+Oracle:When Gavony Silversmith enters the battlefield, put a +1/+1 counter on each of up to two target creatures.
diff --git a/forge-gui/res/cardsfolder/upcoming/gavony_trapper.txt b/forge-gui/res/cardsfolder/upcoming/gavony_trapper.txt
new file mode 100644
index 00000000000..d378259cc55
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/gavony_trapper.txt
@@ -0,0 +1,6 @@
+Name:Gavony Trapper
+ManaCost:W
+Types:Creature Human Soldier
+PT:0/2
+A:AB$ Tap | Cost$ 2 T | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Tap target creature.
+Oracle:{2}{T}: Tap target creature.
diff --git a/forge-gui/res/cardsfolder/upcoming/ghoulcallers_harvest.txt b/forge-gui/res/cardsfolder/upcoming/ghoulcallers_harvest.txt
new file mode 100644
index 00000000000..3867fe85ac1
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/ghoulcallers_harvest.txt
@@ -0,0 +1,8 @@
+Name:Ghoulcaller's Harvest
+ManaCost:B G
+Types:Sorcery
+A:SP$ Token | TokenAmount$ X | TokenScript$ b_2_2_zombie_decayed | SpellDescription$ Create X 2/2 black Zombie creature tokens with decayed, where X is half the number of creature cards in your graveyard, rounded up. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.)
+SVar:X:Count$ValidGraveyard Creature.YouOwn/HalfUp
+K:Flashback:3 B G
+DeckHas:Ability$Token & Ability$Graveyard
+Oracle:Create X 2/2 black Zombie creature tokens with decayed, where X is half the number of creature cards in your graveyard, rounded up. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.)\nFlashback {3}{B}{G} (You may cast this card from your graveyard for its flashback cost. Then exile it.)
diff --git a/forge-gui/res/cardsfolder/upcoming/hedgewitchs_mask.txt b/forge-gui/res/cardsfolder/upcoming/hedgewitchs_mask.txt
new file mode 100644
index 00000000000..a3206a5283d
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/hedgewitchs_mask.txt
@@ -0,0 +1,7 @@
+Name:Hedgewitch's Mask
+ManaCost:W
+Types:Artifact Equipment
+K:Equip:2
+S:Mode$ CantBlockBy | ValidAttacker$ Creature.EquippedBy | ValidBlocker$ Creature.powerGE4 | Description$ Equipped creature can't be blocked by creatures with power 4 or greater.
+S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1.
+Oracle:Equipped creature gets +1/+1.\nEquipped creature can't be blocked by creatures with power 4 or greater.\nEquip {2}
diff --git a/forge-gui/res/cardsfolder/upcoming/homestead_courage.txt b/forge-gui/res/cardsfolder/upcoming/homestead_courage.txt
new file mode 100644
index 00000000000..afa9be8dff0
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/homestead_courage.txt
@@ -0,0 +1,8 @@
+Name:Homestead Courage
+ManaCost:W
+Types:Sorcery
+K:Flashback:W
+A:SP$ PutCounter | Cost$ W | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on target creature you control.
+SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Vigilance
+DeckHas:Ability$Counters
+Oracle:Put a +1/+1 counter on target creature you control. That creature gains vigilance until end of turn.\nFlashback {W} (You may cast this card from your graveyard for its flashback cost. Then exile it.)
diff --git a/forge-gui/res/cardsfolder/upcoming/novice_occultist.txt b/forge-gui/res/cardsfolder/upcoming/novice_occultist.txt
new file mode 100644
index 00000000000..d44477cb97e
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/novice_occultist.txt
@@ -0,0 +1,8 @@
+Name:Novice Occultist
+ManaCost:1 B
+Types:Creature Human Wizard
+PT:1/2
+T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, you draw a card and you lose 1 life.
+SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ DBLoseLife
+SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1
+Oracle:When Novice Occultist dies, you draw a card and you lose 1 life.
diff --git a/forge-gui/res/cardsfolder/upcoming/patrician_geist.txt b/forge-gui/res/cardsfolder/upcoming/patrician_geist.txt
new file mode 100644
index 00000000000..520263ea26e
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/patrician_geist.txt
@@ -0,0 +1,9 @@
+Name:Patrician Geist
+ManaCost:2 U
+Types:Creature Spirit Knight
+PT:2/2
+K:Flying
+S:Mode$ Continuous | Affected$ Spirit.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Spirits you control get +1/+1.
+S:Mode$ ReduceCost | ValidCard$ Card.wasCastFromGraveyard | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Spells you cast from your graveyard cost {1} less to cast.
+DeckHints:Ability$Graveyard & Type$Spirit
+Oracle:Flying\nOther Spirits you control get +1/+1.\nSpells you cast from your graveyard cost {1} less to cast.
diff --git a/forge-gui/res/cardsfolder/upcoming/purifying_dragon.txt b/forge-gui/res/cardsfolder/upcoming/purifying_dragon.txt
new file mode 100644
index 00000000000..f4577efa8ee
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/purifying_dragon.txt
@@ -0,0 +1,10 @@
+Name:Purifying Dragon
+ManaCost:3 R R
+Types:Creature Dragon
+PT:4/3
+K:Flying
+T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ Whenever CARDNAME attacks, it deals 1 damage to target creature an opponent controls. If that creature is a Zombie, CARDNAME deals 2 damage to that creature instead.
+SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature | TargetsWithDefinedController$ TriggeredDefendingPlayer | TgtPrompt$ Select target creature defending player controls | NumDmg$ X
+SVar:X:Targeted$Valid Creature.Zombie/Plus.1
+SVar:HasAttackEffect:TRUE
+Oracle:Flying\nWhenever Purifying Dragon attacks, it deals 1 damage to target creature defending player controls. If that creature is a Zombie, Purifying Dragon deals 2 damage to that creature instead.
diff --git a/forge-gui/res/cardsfolder/upcoming/raze_the_effigy.txt b/forge-gui/res/cardsfolder/upcoming/raze_the_effigy.txt
new file mode 100644
index 00000000000..07999841d75
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/raze_the_effigy.txt
@@ -0,0 +1,7 @@
+Name:Raze the Effigy
+ManaCost:R
+Types:Instant
+A:SP$ Charm | Cost$ R | Choices$ DBDestroy,DBPump
+SVar:DBDestroy:DB$ Destroy | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | SpellDescription$ Destroy target artifact
+SVar:DBPump:DB$ Pump | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature | NumAtt$ +2 | NumDef$ +2 | SpellDescription$ Target attacking creature gets +2/+2 until end of turn.
+Oracle:Choose one —\n•Destroy target artifact\n•Target attacking creature gets +2/+2 until end of turn.
diff --git a/forge-gui/res/cardsfolder/upcoming/ritual_guardian.txt b/forge-gui/res/cardsfolder/upcoming/ritual_guardian.txt
new file mode 100644
index 00000000000..d508efb678c
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/ritual_guardian.txt
@@ -0,0 +1,9 @@
+Name:Ritual Guardian
+ManaCost:2 W
+Types:Creature Human Soldier
+PT:3/2
+T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE3 | Execute$ TrigPump | TriggerDescription$ Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, CARDNAME gains lifelink until end of turn.
+SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ Lifelink
+SVar:X:Count$DifferentPower_Creature.YouCtrl
+DeckHas:Ability$LifeGain
+Oracle:Coven — At the beginning of combat on your turn, if you control three or more creatures with different powers, Ritual Guardian gains lifelink until end of turn.
diff --git a/forge-gui/res/cardsfolder/upcoming/shadowbeast_sighting.txt b/forge-gui/res/cardsfolder/upcoming/shadowbeast_sighting.txt
new file mode 100644
index 00000000000..ce2315aeeac
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/shadowbeast_sighting.txt
@@ -0,0 +1,7 @@
+Name:Shadowbeast Sighting
+ManaCost:3 G
+Types:Sorcery
+A:SP$ Token | Cost$ 3 G | TokenScript$ g_4_4_beast | TokenOwner$ You | SpellDescription$ Create a 4/4 green Beast creature token.
+K:Flashback:6 G
+DeckHas:Ability$Token & Ability$Graveyard
+Oracle:Create a 4/4 green Beast creature token.\nFlashback {6}{G}
diff --git a/forge-gui/res/cardsfolder/upcoming/soul_guide_gryff.txt b/forge-gui/res/cardsfolder/upcoming/soul_guide_gryff.txt
new file mode 100644
index 00000000000..c1bafa30c18
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/soul_guide_gryff.txt
@@ -0,0 +1,8 @@
+Name:Soul-Guide Gryff
+ManaCost:4 W
+Types:Creature Hippogriff Spirit
+PT:3/4
+K:Flying
+T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, exile up to one target card from a graveyard.
+SVar:TrigChangeZone:DB$ChangeZone | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Card | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target card in a graveyard to exile
+Oracle:Flying\nWhen Soul-Guide Gryff enters the battlefield, exile up to one target card from a graveyard.
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/upcoming/turn_the_earth.txt b/forge-gui/res/cardsfolder/upcoming/turn_the_earth.txt
new file mode 100644
index 00000000000..ddff62b40b1
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/turn_the_earth.txt
@@ -0,0 +1,8 @@
+Name:Turn the Earth
+ManaCost:G
+Types:Instant
+A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Library | Shuffle$ True | ValidTgts$ Card | TgtPrompt$ Choose up to three target cards in graveyards | TargetMin$ 0 | TargetMax$ 3 | SubAbility$ DBGainLife | StackDescription$ The owners of {c:Targeted} shuffle them into their libraries. | SpellDescription$ Choose up to three target cards in graveyards. The owners of those cards shuffle them into their libraries. You gain 2 life.
+SVar:DBGainLife:DB$ GainLife | LifeAmount$ 2 | Defined$ You
+K:Flashback:1 G
+DeckHas:Ability$Graveyard & Ability$LifeGain
+Oracle:Choose up to three target cards in graveyards. The owners of those cards shuffle them into their libraries. You gain 2 life.\nFlashback {1}{G} (You may cast this card from your graveyard for its flashback cost. Then exile it.)
diff --git a/forge-gui/res/cardsfolder/upcoming/unnatural_growth.txt b/forge-gui/res/cardsfolder/upcoming/unnatural_growth.txt
new file mode 100644
index 00000000000..12af8ba8c15
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/unnatural_growth.txt
@@ -0,0 +1,10 @@
+Name:Unnatural Growth
+ManaCost:1 G G G G
+Types:Enchantment
+T:Mode$ Phase | Phase$ BeginCombat | TriggerZones$ Battlefield | Execute$ TrigDouble | TriggerDescription$ At the beginning of each combat, double the power and toughness of each creature you control until end of turn.
+SVar:TrigDouble:DB$ RepeatEach | RepeatCards$ Creature.YouCtrl | RepeatSubAbility$ DBDouble
+SVar:DBDouble:DB$ Pump | Defined$ Remembered | NumAtt$ X | NumDef$ Y
+SVar:X:Remembered$CardPower
+SVar:Y:Remembered$CardToughness
+SVar:PlayMain1:TRUE
+Oracle:At the beginning of each combat, double the power and toughness of each creature you control until end of turn.
diff --git a/forge-gui/res/cardsfolder/upcoming/vampire_socialite.txt b/forge-gui/res/cardsfolder/upcoming/vampire_socialite.txt
new file mode 100644
index 00000000000..f12fd11eae0
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/vampire_socialite.txt
@@ -0,0 +1,13 @@
+Name:Vampire Socialite
+ManaCost:B R
+Types:Creature Vampire Noble
+PT:2/2
+K:Menace
+T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | CheckSVar$ X | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, if an opponent lost life this turn, put a +1/+1 counter on each other Vampire you control.
+SVar:TrigPutCounter:DB$ PutCounterAll | ValidCards$ Vampire.YouCtrl+Other | CounterType$ P1P1 | CounterNum$ 1
+K:ETBReplacement:Other:AddExtraCounter:Mandatory:Battlefield:Vampire.YouCtrl+Other
+SVar:AddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ P1P1 | CounterNum$ 1 | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | SpellDescription$ As long as an opponent lost life this turn, each other Vampire you control enters the battlefield with an additional +1/+1 counter on it.
+SVar:X:Count$LifeOppsLostThisTurn
+DeckNeeds:Type$Vampire
+DeckHas:Ability$Counters
+Oracle:Menace (This creature can’t be blocked except by two or more creatures.)\nWhen Vampire Socialite enters the battlefield, if an opponent lost life this turn, put a +1/+1 counter on each other Vampire you control.\nAs long as an opponent lost life this turn, each other Vampire you control enters the battlefield with an additional +1/+1 counter on it.
diff --git a/forge-gui/res/cardsfolder/upcoming/voldaren_ambusher.txt b/forge-gui/res/cardsfolder/upcoming/voldaren_ambusher.txt
new file mode 100644
index 00000000000..57258136eb6
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/voldaren_ambusher.txt
@@ -0,0 +1,10 @@
+Name:Voldaren Ambusher
+ManaCost:2 R
+Types:Creature Vampire Archer
+PT:2/2
+T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | CheckSVar$ Y | Execute$ TrigDealDamage | TriggerDescription$ When CARDNAME enters the battlefield, if an opponent lost life this turn, it deals X damage to up to one target creature or planeswalker, where X is the number of Vampires you control.
+SVar:TrigDealDamage:DB$ DealDamage | NumDmg$ X | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker
+SVar:X:Count$Valid Vampire.YouCtrl
+SVar:Y:Count$LifeOppsLostThisTurn
+DeckNeeds:Type$Vampire
+Oracle:When Voldaren Ambusher enters the battlefield, if an opponent lost life this turn, it deals X damage to up to one target creature or planeswalker, where X is the number of Vampires you control.
diff --git a/forge-gui/res/cardsfolder/v/veiling_oddity.txt b/forge-gui/res/cardsfolder/v/veiling_oddity.txt
index b1cec93eebc..2cabd38946c 100644
--- a/forge-gui/res/cardsfolder/v/veiling_oddity.txt
+++ b/forge-gui/res/cardsfolder/v/veiling_oddity.txt
@@ -3,7 +3,7 @@ ManaCost:3 U
Types:Creature Illusion
PT:2/3
K:Suspend:4:1 U
-T:Mode$ CounterRemoved | ValidCard$ Card.Self | TriggerZones$ Exile | CounterType$ TIME | Execute$ TrigEffect | IsPresent$ Card.Self+counters_GE1_TIME | PresentZone$ Exile | PresentCompare$ EQ0 | TriggerDescription$ When the last time counter is removed from CARDNAME while it's exiled, creatures can't be blocked this turn.
+T:Mode$ CounterRemoved | ValidCard$ Card.Self | TriggerZones$ Exile | CounterType$ TIME | Execute$ TrigEffect | NewCounterAmount$ 0 | TriggerDescription$ When the last time counter is removed from CARDNAME while it's exiled, creatures can't be blocked this turn.
SVar:TrigEffect:DB$ Effect | Name$ Veiling Oddity Effect | StaticAbilities$ KWPump
SVar:KWPump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature | AddHiddenKeyword$ Unblockable | Description$ creatures can't be blocked this turn.
SVar:Picture:http://www.wizards.com/global/images/magic/general/veiling_oddity.jpg
diff --git a/forge-gui/res/cardsfolder/v/vigorspore_wurm.txt b/forge-gui/res/cardsfolder/v/vigorspore_wurm.txt
index 8602f5e3cb5..9c14d715067 100644
--- a/forge-gui/res/cardsfolder/v/vigorspore_wurm.txt
+++ b/forge-gui/res/cardsfolder/v/vigorspore_wurm.txt
@@ -2,7 +2,7 @@ Name:Vigorspore Wurm
ManaCost:5 G
Types:Creature Wurm
PT:6/4
-K:CantBeBlockedByAmount GT1
+S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Max$ 1 | Description$ CARDNAME can't be blocked by more than one creature.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ Undergrowth - When CARDNAME enters the battlefield, target creature gains vigilance and gets +X/+X until end of turn, where X is the number of creature cards in your graveyard.
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ X | NumDef$ X | KW$ Vigilance
SVar:X:Count$TypeInYourYard.Creature
diff --git a/forge-gui/res/cardsfolder/v/vorrac_battlehorns.txt b/forge-gui/res/cardsfolder/v/vorrac_battlehorns.txt
index 935e7e4df82..96a6198eee3 100644
--- a/forge-gui/res/cardsfolder/v/vorrac_battlehorns.txt
+++ b/forge-gui/res/cardsfolder/v/vorrac_battlehorns.txt
@@ -2,6 +2,6 @@ Name:Vorrac Battlehorns
ManaCost:2
Types:Artifact Equipment
K:Equip:1
-S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddKeyword$ Trample | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Equipped creature has trample and can't be blocked by more than one creature.
-SVar:Picture:http://www.wizards.com/global/images/magic/general/vorrac_battlehorns.jpg
+S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddKeyword$ Trample | Description$ Equipped creature has trample and can't be blocked by more than one creature.
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.EquippedBy | Max$ 1 | Secondary$ True | Description$ Equipped creature can't be blocked by more than one creature.
Oracle:Equipped creature has trample and can't be blocked by more than one creature.\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery. This card enters the battlefield unattached and stays on the battlefield if the creature leaves.)
diff --git a/forge-gui/res/cardsfolder/w/wolfriders_saddle.txt b/forge-gui/res/cardsfolder/w/wolfriders_saddle.txt
index 540c9735b9f..34c6b072eeb 100644
--- a/forge-gui/res/cardsfolder/w/wolfriders_saddle.txt
+++ b/forge-gui/res/cardsfolder/w/wolfriders_saddle.txt
@@ -6,6 +6,7 @@ SVar:TrigToken:DB$ Token | LegacyImage$ g 2 2 wolf m20 | TokenAmount$ 1 | TokenS
SVar:DBAttach:DB$ Attach | Defined$ Remembered | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
DeckHas:Ability$Token
-S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Equipped creature gets +1/+1 and can't be blocked by more than one creature.
+S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1 and can't be blocked by more than one creature.
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.EquippedBy | Max$ 1 | Secondary$ True | Description$ Equipped creature can't be blocked by more than one creature.
K:Equip:3
Oracle:When Wolfrider's Saddle enters the battlefield, create a 2/2 green Wolf creature token, then attach Wolfrider's Saddle to it.\nEquipped creature gets +1/+1 and can't be blocked by more than one creature.\nEquip {3} ({3}: Attach to target creature you control. Equip only as a sorcery.)
diff --git a/forge-gui/res/cardsfolder/y/yuan_shao_the_indecisive.txt b/forge-gui/res/cardsfolder/y/yuan_shao_the_indecisive.txt
index c319ae44531..78f74b0dfa7 100644
--- a/forge-gui/res/cardsfolder/y/yuan_shao_the_indecisive.txt
+++ b/forge-gui/res/cardsfolder/y/yuan_shao_the_indecisive.txt
@@ -3,6 +3,5 @@ ManaCost:4 R
Types:Legendary Creature Human Soldier
PT:2/3
K:Horsemanship
-S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddHiddenKeyword$ CantBeBlockedByAmount GT1 | Description$ Each creature you control can't be blocked by more than one creature.
-SVar:Picture:http://www.wizards.com/global/images/magic/general/yuan_shao_the_indecisive.jpg
+S:Mode$ MinMaxBlocker | ValidCard$ Creature.YouCtrl | Max$ 1 | Description$ Each creature you control can't be blocked by more than one creature.
Oracle:Horsemanship (This creature can't be blocked except by creatures with horsemanship.)\nEach creature you control can't be blocked by more than one creature.
diff --git a/forge-gui/res/draft/rankings.txt b/forge-gui/res/draft/rankings.txt
index c419e06d371..a0c218b46b5 100644
--- a/forge-gui/res/draft/rankings.txt
+++ b/forge-gui/res/draft/rankings.txt
@@ -33653,4 +33653,272 @@
#257|Dust to Dust|U|ME4
#258|Gate to Phyrexia|U|ME4
#259|Tablet of Epityr|C|ME4
-#260|Leeches|R|ME4
\ No newline at end of file
+#260|Leeches|R|ME4
+//Rank|Name|Rarity|Set
+#1|Liesa, Forgotten Archangel|R|MID
+#2|Sigarda, Champion of Light|M|MID
+#3|Wrenn and Seven|M|MID
+#4|Arlinn, the Pack's Hope|M|MID
+#5|Tainted Adversary|M|MID
+#6|Tovolar's Huntmaster|R|MID
+#7|Consuming Blob|M|MID
+#8|Intrepid Adversary|M|MID
+#9|Florian, Voldaren Scion|R|MID
+#10|Spectral Adversary|M|MID
+#11|Tovolar, Dire Overlord|R|MID
+#12|Enduring Angel|M|MID
+#13|Primal Adversary|M|MID
+#14|Lord of the Forsaken|M|MID
+#15|Teferi, Who Slows the Sunset|M|MID
+#16|Moonveil Regent|M|MID
+#17|Gisa, Glorious Resurrector|R|MID
+#18|Jerren, Corrupted Bishop|M|MID
+#19|Dennick, Pious Apprentice|R|MID
+#20|Slogurk, the Overslime|R|MID
+#21|Suspicious Stowaway|R|MID
+#22|Brutal Cathar|R|MID
+#23|Katilda, Dawnhart Prime|R|MID
+#24|Augur of Autumn|R|MID
+#25|Bloodline Culling|R|MID
+#26|Sungold Sentinel|R|MID
+#27|Jadar, Ghoulcaller of Nephalia|R|MID
+#28|Ludevic, Necrogenius|R|MID
+#29|Reckless Stormseeker|R|MID
+#30|Bloodthirsty Adversary|M|MID
+#31|Sunstreak Phoenix|M|MID
+#32|Mask of Griselbrand|R|MID
+#33|Lier, Disciple of the Drowned|M|MID
+#34|Briarbridge Tracker|R|MID
+#35|Poppet Stitcher|M|MID
+#36|The Meathook Massacre|M|MID
+#37|Vadrik, Astral Archmage|R|MID
+#38|Grafted Identity|R|MID
+#39|Triskaidekaphile|R|MID
+#40|Smoldering Egg|R|MID
+#41|Borrowed Time|U|MID
+#42|Saryth, the Viper's Fang|R|MID
+#43|Infernal Grasp|U|MID
+#44|Adeline, Resplendent Cathar|R|MID
+#45|Light Up the Night|R|MID
+#46|Hostile Hostel|M|MID
+#47|Graveyard Trespasser|R|MID
+#48|Cathartic Pyre|U|MID
+#49|Fateful Absence|R|MID
+#50|Willow Geist|R|MID
+#51|Clear Shot|U|MID
+#52|Burn Down the House|R|MID
+#53|Rem Karolus, Stalwart Slayer|R|MID
+#54|Wake to Slaughter|R|MID
+#55|Old Stickfingers|R|MID
+#56|Hound Tamer|U|MID
+#57|Rootcoil Creeper|U|MID
+#58|Moonrager's Slash|C|MID
+#59|Sunrise Cavalier|U|MID
+#60|Vanquish the Horde|R|MID
+#61|Gavony Dawnguard|U|MID
+#62|Sludge Monster|R|MID
+#63|Foul Play|U|MID
+#64|Overwhelmed Archivist|U|MID
+#65|Kessig Naturalist|U|MID
+#66|Devoted Grafkeeper|U|MID
+#67|Heirloom Mirror|U|MID
+#68|Morbid Opportunist|U|MID
+#69|Seize the Storm|U|MID
+#70|Dawnhart Mentor|U|MID
+#71|Vampire Socialite|U|MID
+#72|Unnatural Growth|R|MID
+#73|Diregraf Rebirth|U|MID
+#74|Defenestrate|C|MID
+#75|Join the Dance|U|MID
+#76|Play with Fire|U|MID
+#77|Patrician Geist|R|MID
+#78|Cathar's Call|U|MID
+#79|Memory Deluge|R|MID
+#80|Eaten Alive|C|MID
+#81|Rite of Oblivion|U|MID
+#82|Unnatural Moonrise|U|MID
+#83|Duel for Dominance|C|MID
+#84|Hungry for More|U|MID
+#85|Falkenrath Pit Fighter|R|MID
+#86|Fleshtaker|U|MID
+#87|Ghoulcaller's Harvest|R|MID
+#88|Dawnhart Wardens|U|MID
+#89|Geistflame Reservoir|R|MID
+#90|Bereaved Survivor|U|MID
+#91|Burly Breaker|U|MID
+#92|Covert Cutpurse|U|MID
+#93|Curse of Leeches|R|MID
+#94|Phantom Carriage|U|MID
+#95|Winterthorn Blessing|U|MID
+#96|Sacred Fire|U|MID
+#97|Bladestitched Skaab|U|MID
+#98|Ghoulish Procession|U|MID
+#99|Voldaren Ambusher|U|MID
+#100|Galvanic Iteration|R|MID
+#101|Ambitious Farmhand|U|MID
+#102|Spellrune Painter|U|MID
+#103|Chaplain of Alms|U|MID
+#104|Flame Channeler|U|MID
+#105|Burn the Accursed|C|MID
+#106|Shadowbeast Sighting|C|MID
+#107|Dreadhound|U|MID
+#108|Angelfire Ignition|R|MID
+#109|Hallowed Respite|R|MID
+#110|Obsessive Astronomer|U|MID
+#111|Organ Hoarder|C|MID
+#112|Slaughter Specialist|R|MID
+#113|Dryad's Revival|U|MID
+#114|Arcane Infusion|U|MID
+#115|Immolation|C|MID
+#116|Defend the Celestus|U|MID
+#117|Candletrap|C|MID
+#118|Ominous Roost|U|MID
+#119|Thermo-Alchemist|U|MID
+#120|Contortionist Troupe|U|MID
+#121|Outland Liberator|U|MID
+#122|Deathbonnet Sprout|U|MID
+#123|Malevolent Hermit|R|MID
+#124|Mysterious Tome|U|MID
+#125|Can't Stay Away|R|MID
+#126|Rise of the Ants|U|MID
+#127|Nebelgast Intruder|U|MID
+#128|Storm Skreelix|U|MID
+#129|Stromkirk Bloodthief|U|MID
+#130|Skaab Wrangler|U|MID
+#131|Odric's Outrider|U|MID
+#132|Brood Weaver|U|MID
+#133|Loyal Gryff|U|MID
+#134|The Celestus|R|MID
+#135|Faithful Mending|U|MID
+#136|Locked in the Cemetery|C|MID
+#137|Champion of the Perished|R|MID
+#138|Festival Crasher|C|MID
+#139|Baneblade Scoundrel|U|MID
+#140|Fangblade Brigand|U|MID
+#141|Grizzly Ghoul|U|MID
+#142|Sigarda's Splendor|R|MID
+#143|Duelcraft Trainer|U|MID
+#144|Search Party Captain|C|MID
+#145|Unblinking Observer|C|MID
+#146|Olivia's Midnight Ambush|C|MID
+#147|Ardent Elementalist|C|MID
+#148|Firmament Sage|U|MID
+#149|Bloodtithe Collector|U|MID
+#150|Siphon Insight|R|MID
+#151|Curse of Surveillance|R|MID
+#152|Falkenrath Perforator|C|MID
+#153|Lunar Frenzy|U|MID
+#154|Croaking Counterpart|R|MID
+#155|Corpse Cobble|U|MID
+#156|Galedrifter|C|MID
+#157|Vengeful Strangler|U|MID
+#158|Covetous Castaway|U|MID
+#159|Cathar Commando|C|MID
+#160|Clarion Cathars|C|MID
+#161|Gavony Silversmith|C|MID
+#162|Homestead Courage|C|MID
+#163|Geistwave|C|MID
+#164|Vampire Interloper|C|MID
+#165|Timberland Guide|C|MID
+#166|Evolving Wilds|C|MID
+#167|Fading Hope|U|MID
+#168|Storm the Festival|R|MID
+#169|Sunset Revelry|U|MID
+#170|Purifying Dragon|U|MID
+#171|Candlegrove Witch|C|MID
+#172|Dawnhart Rejuvenator|C|MID
+#173|Rite of Harmony|R|MID
+#174|Delver of Secrets|U|MID
+#175|Mourning Patrol|C|MID
+#176|Shady Traveler|C|MID
+#177|Harvesttide Infiltrator|C|MID
+#178|Tireless Hauler|C|MID
+#179|Mystic Skull|U|MID
+#180|Bird Admirer|C|MID
+#181|Beloved Beggar|U|MID
+#182|Village Watch|U|MID
+#183|Ritual of Hope|U|MID
+#184|Gavony Trapper|C|MID
+#185|Falcon Abomination|C|MID
+#186|Revenge of the Drowned|C|MID
+#187|Bat Whisperer|C|MID
+#188|Morkrut Behemoth|C|MID
+#189|Lambholt Harrier|C|MID
+#190|Harvesttide Sentry|C|MID
+#191|Silver Bolt|C|MID
+#192|Vivisection|U|MID
+#193|Startle|C|MID
+#194|Hobbling Zombie|C|MID
+#195|Dissipate|U|MID
+#196|Celestus Sanctifier|C|MID
+#197|Sungold Barrage|C|MID
+#198|Haunted Ridge|R|MID
+#199|Shipwreck Marsh|R|MID
+#200|Rockfall Vale|R|MID
+#201|Overgrown Farmland|R|MID
+#202|Deserted Beach|R|MID
+#203|Arrogant Outlaw|C|MID
+#204|Brimstone Vandal|C|MID
+#205|Famished Foragers|C|MID
+#206|Siege Zombie|C|MID
+#207|Unruly Mob|C|MID
+#208|Pestilent Wolf|C|MID
+#209|Snarling Wolf|C|MID
+#210|Lunarch Veteran|C|MID
+#211|Tavern Ruffian|C|MID
+#212|Baithook Angler|C|MID
+#213|Soul-Guide Gryff|C|MID
+#214|Component Collector|C|MID
+#215|Drownyard Amalgam|C|MID
+#216|Flip the Switch|C|MID
+#217|Shipwreck Sifters|C|MID
+#218|Blood Pact|C|MID
+#219|Crawl from the Cellar|C|MID
+#220|Novice Occultist|C|MID
+#221|Electric Revelation|C|MID
+#222|Mounted Dreadknight|C|MID
+#223|Raze the Effigy|C|MID
+#224|Bounding Wolf|C|MID
+#225|Bramble Armor|C|MID
+#226|Eccentric Farmer|C|MID
+#227|Path to the Festival|C|MID
+#228|Tapping at the Window|C|MID
+#229|Crossroads Candleguide|C|MID
+#230|Jack-o'-Lantern|C|MID
+#231|Diregraf Horde|C|MID
+#232|Stuffed Bear|C|MID
+#233|Turn the Earth|U|MID
+#234|Ritual Guardian|C|MID
+#235|Candlelit Cavalry|C|MID
+#236|Secrets of the Key|C|MID
+#237|Stormrider Spirit|C|MID
+#238|Howl of the Hunt|C|MID
+#239|Ecstatic Awakener|C|MID
+#240|Blessed Defiance|C|MID
+#241|No Way Out|C|MID
+#242|Abandon the Post|C|MID
+#243|Stolen Vitality|C|MID
+#244|Voldaren Stinger|C|MID
+#245|Return to Nature|C|MID
+#246|Bladebrand|C|MID
+#247|Moonsilver Key|U|MID
+#248|Larder Zombie|C|MID
+#249|Hedgewitch's Mask|C|MID
+#250|Might of the Old Ways|C|MID
+#251|Consider|C|MID
+#252|Dire-Strain Rampage|R|MID
+#253|Necrosynthesis|U|MID
+#254|Flare of Faith|C|MID
+#255|Devious Cover-Up|C|MID
+#256|Rotten Reunion|C|MID
+#257|Neonate's Rush|C|MID
+#258|Pack's Betrayal|C|MID
+#259|Sigardian Savior|M|MID
+#260|Thraben Exorcism|C|MID
+#261|Otherworldly Gaze|C|MID
+#262|Duress|C|MID
+#263|Plummet|C|MID
+#264|Curse of Shaken Faith|R|MID
+#265|Field of Ruin|U|MID
+#266|Curse of Silence|R|MID
+#267|Pithing Needle|R|MID
diff --git a/forge-gui/res/editions/Commander Collection Black.txt b/forge-gui/res/editions/Commander Collection Black.txt
index eee0f93ed27..f8a4eca1251 100644
--- a/forge-gui/res/editions/Commander Collection Black.txt
+++ b/forge-gui/res/editions/Commander Collection Black.txt
@@ -7,13 +7,14 @@ ScryfallCode=CC2
[cards]
1 M Liliana, Heretical Healer @Bastien L. Deharme
-2 M Ghoulcaller Gisa @Aaron J. Riley
-3 R Ophiomancer @Caroline Gariba
-4 R Phyrexian Arena @Vincent Proce
-5 R Reanimate @Nils Hamm
-6 R Toxic Deluge @Yeong-Hao Han
-7 R Sol Ring @Yeong-Hao Han
-8 R Command Tower @Julian Kok Joon Wen
+2 M Liliana, Defiant Necromancer @Bastien L. Deharme
+3 M Ghoulcaller Gisa @Aaron J. Riley
+4 R Ophiomancer @Caroline Gariba
+5 R Phyrexian Arena @Vincent Proce
+6 R Reanimate @Nils Hamm
+7 R Toxic Deluge @Yeong-Hao Han
+8 R Sol Ring @Yeong-Hao Han
+9 R Command Tower @Julian Kok Joon Wen
[tokens]
b_1_1_snake_deathtouch
diff --git a/forge-gui/res/editions/Innistrad Midnight Hunt Commander.txt b/forge-gui/res/editions/Innistrad Midnight Hunt Commander.txt
index a278d392969..1110011d4e2 100644
--- a/forge-gui/res/editions/Innistrad Midnight Hunt Commander.txt
+++ b/forge-gui/res/editions/Innistrad Midnight Hunt Commander.txt
@@ -15,6 +15,7 @@ ScryfallCode=MIC
35 R Curse of Obsession @Irvin Rodriguez
36 R Visions of Ruin @Andrew Mar
37 R Visions of Dominance @Andrew Mar
+38 M Lynde, Cheerful Tormenter @Anna Steinbauer
39 M Leinore, Autumn Sovereign @Fariba Khamseh
40 R Wilhelt, the Rotcleaver @Chris Rallis
69 M Avacyn's Memorial @Kasia 'Kafis' Zielińska
@@ -24,3 +25,4 @@ ScryfallCode=MIC
73 R Curse of Obsession @Irvin Rodriguez
74 R Visions of Ruin @Andrew Mar
75 R Visions of Dominance @Andrew Mar
+76 M Lynde, Cheerful Tormenter @Anna Steinbauer
diff --git a/forge-gui/res/editions/Innistrad Midnight Hunt.txt b/forge-gui/res/editions/Innistrad Midnight Hunt.txt
index a83f16e5c81..9d8ce43e720 100644
--- a/forge-gui/res/editions/Innistrad Midnight Hunt.txt
+++ b/forge-gui/res/editions/Innistrad Midnight Hunt.txt
@@ -10,100 +10,270 @@ Booster=10 Common, 3 Uncommon, 1 RareMythic, 1 BasicLand
Prerelease=6 Boosters, 1 RareMythic+
[cards]
+1 R Adeline, Resplendent Cathar @Bryan Sola
+2 U Ambitious Farmhand @Bryan Sola
3 U Beloved Beggar @Francisco Miyara
+4 U Bereaved Survivor @Zara Alfonso
+5 C Blessed Defiance @Kim Sokol
+6 U Borrowed Time @Andreas Zafiratos
7 R Brutal Cathar @Karl Kopinski
8 C Candlegrove Witch @Anna Christenson
9 C Candletrap @Manuel Castañón
+10 C Cathar Commando @Evyn Fong
+11 U Cathar's Call @Matt Stewart
12 C Celestus Sanctifier @Irina Nordsol
13 U Chaplain of Alms @Anastasia Ovchinnikova
+14 C Clarion Cathars @Leanna Crossan
+15 R Curse of Silence @Irina Nordsol
+16 U Duelcraft Trainer @John Stanko
17 M Enduring Angel @Irina Nordsol
+18 R Fateful Absence @Eric Deschamps
+19 C Flare of Faith @Justyna Gil
20 U Gavony Dawnguard @Micah Epstein
+21 C Gavony Silversmith @Volkan Baǵa
+22 C Gavony Trapper @Caio Monteiro
23 C Hedgewitch's Mask @Ovidio Cartagena
+24 C Homestead Courage @Colin Boyer
+25 M Intrepid Adversary @Viktor Titov
26 U Loyal Gryff @Ilse Gort
+27 C Lunarch Veteran @Igor Kieryluk
+28 C Mourning Patrol @Greg Opalinski
+29 U Odric's Outrider @Cristi Balanescu
30 C Ritual Guardian @Denman Rooke
+31 U Ritual of Hope @Craig J Spearing
+32 C Search Party Captain @Mike Bierek
+33 R Sigarda's Splendor @Howard Lyon
+34 M Sigardian Savior @David Rapoza
+35 C Soul-Guide Gryff @Cristi Balanescu
36 C Sungold Barrage @Leanna Crossan
+37 R Sungold Sentinel @Marta Nael
+38 U Sunset Revelry @Antonio José Manzanedo
+39 C Thraben Exorcism @Matt Stewart
40 C Unruly Mob @Ryan Pancoast
+41 R Vanquish the Horde @Grzegorz Rutkowski
42 C Baithook Angler @Uriah Voth
+43 C Component Collector @Mark Behm
44 C Consider @Zezhou Chen
+45 U Covetous Castaway @Dan Scott
+46 R Curse of Surveillance @Igor Kieryluk
+47 U Delver of Secrets @Matt Stewart
+48 C Devious Cover-Up @Colin Boyer
49 U Dissipate @David Palumbo
+50 C Drownyard Amalgam @Alex Brock
+51 U Fading Hope @Rovina Cai
+52 C Falcon Abomination @Brent Hollowell
+53 U Firmament Sage @Anastasia Ovchinnikova
+54 C Flip the Switch @Campbell White
+55 C Galedrifter @Daniel Ljunggren
+56 C Geistwave @Olena Richards
57 R Grafted Identity @Manuel Castañón
-64 U Neblegast Intruder @Volkan Baǵa
+58 C Larder Zombie @E. M. Gist
+59 M Lier, Disciple of the Drowned @Ekaterina Burmak
+60 C Locked in the Cemetery @Tran Nguyen
+61 R Malevolent Hermit @Daarken
+62 R Memory Deluge @Lake Hurwitz
+63 U Mysterious Tome @David Auden Nash
+64 U Nebelgast Intruder @Volkan Baǵa
65 U Ominous Roost @Josu Hernaiz
+66 C Organ Hoarder @Nicholas Gregory
+67 C Otherworldly Gaze @Chris Cold
+68 U Overwhelmed Archivist @Cristi Balanescu
+69 R Patrician Geist @Marta Nael
+70 U Phantom Carriage @Igor Kieryluk
71 M Poppet Stitcher @Simon Dominic
+72 C Revenge of the Drowned @Chris Cold
73 C Secrets of the Key @Alix Branwyn
+74 C Shipwreck Sifters @Svetlin Velinov
+75 U Skaab Wrangler @Billy Christian
76 R Sludge Monster @Svetlin Velinov
77 M Spectral Adversary @Uriah Voth
78 C Startle @Craig J Spearing
79 C Stormrider Spirit @Lake Hurwitz
+80 R Suspicious Stowaway @Jake Murray
81 R Triskaidekaphile @Slawomir Maniak
+82 C Unblinking Observer @Filip Burburan
+83 U Vivisection @Aaron Miller
84 C Arrogant Outlaw @Aurore Folny
85 U Baneblade Scoundrel @Marta Nael
+86 C Bat Whisperer @Jodie Muir
+87 C Bladebrand @Khurrum
+88 C Blood Pact @Sam White
+89 R Bloodline Culling @Liiga Smilshkalne
+90 U Bloodtithe Collector @Maria Zolotukhina
91 R Champion of the Perished @Kekai Kotaki
+92 U Covert Cutpurse @John Stanko
+93 C Crawl from the Cellar @Igor Kieryluk
94 R Curse of Leeches @Uriah Voth
95 C Defenestrate @Darek Zabrocki
+96 C Diregraf Horde @Alex Negrea
+97 U Dreadhound @Joe Slucher
+98 C Duress @Paul Scott Canavan
+99 C Eaten Alive @Nicholas Gregory
+100 C Ecstatic Awakener @Tuan Duong Chu
101 U Foul Play @Campbell White
+102 U Ghoulish Procession @Vincent Proce
103 R Gisa, Glorious Resurrector @Yongjae Choi
104 R Graveyard Trespasser @Chris Rallis
105 U Heirloom Mirror @Josh Hass
106 C Hobbling Zombie @Josh Hass
107 U Infernal Grasp @Naomi Baker
108 R Jadar, Ghoulcaller of Nephalia @Yongjae Choi
+109 M Jerren, Corrupted Bishop @Yongjae Choi
+110 M Lord of the Forsaken @Kekai Kotaki
+111 R Mask of Griselbrand @Jason Felix
+112 M The Meathook Massacre @Chris Seaman
+113 U Morbid Opportunist @Tyler Walpole
+114 C Morkrut Behemoth @Milivoj Ćeran
+115 U Necrosynthesis @Isis
+116 C No Way Out @Sebastian Giacobino
+117 C Novice Occultist @Zara Alfonso
+118 C Olivia's Midnight Ambush @Chris Rallis
+119 C Rotten Reunion @Aaron Miller
+120 C Shady Traveler @Lie Setiawan
+121 C Siege Zombie @Johann Bodin
+122 R Slaughter Specialist @Kari Christensen
+123 U Stromkirk Bloodthief @Caroline Gariba
124 M Tainted Adversary @Tuan Duong Chu
+125 C Vampire Interloper @James Ryman
+126 U Vengeful Strangler @Sean Sevestre
+127 C Abandon the Post @Zoltan Boros
+128 C Ardent Elementalist @Miguel Mercado
+129 M Bloodthirsty Adversary @Heonhwa Choe
130 C Brimstone Vandal @Andreas Zafiratos
+131 R Burn Down the House @Campbell White
+132 C Burn the Accursed @David Auden Nash
+133 U Cathartic Pyre @Ryan Yee
134 R Curse of Shaken Faith @Campbell White
+135 C Electric Revelation @Liiga Smilshkalne
+136 C Falkenrath Perforator @Marcela Medeiros
+137 R Falkenrath Pit Fighter @Anna Fehr
138 C Famished Foragers @Lorenzo Mastroianni
+139 U Fangblade Brigand @Vincent Proce
140 C Festival Crasher @Milivoj Ćeran
141 U Flame Channeler @Véronique Meignaud
142 R Geistflame Reservoir @Anna Christenson
+143 C Harvesttide Infiltrator @Mathias Kollros
144 C Immolation @Olena Richards
+145 C Lambholt Harrier @Jason Kang
146 R Light Up the Night @Wei Wei
147 U Lunar Frenzy @Alix Branwyn
+148 C Moonrager's Slash @Lars Grant-West
149 M Moonveil Regent @Joshua Raphael
+150 C Mounted Dreadknight @Josu Hernaiz
+151 C Neonate's Rush @Justine Cruz
+152 U Obsessive Astronomer @Josu Hernaiz
+153 C Pack's Betrayal @Sam Rowan
154 U Play with Fire @Svetlin Velinov
+155 U Purifying Dragon @Zezhou Chen
+156 C Raze the Effigy @Cristi Balanescu
+157 R Reckless Stormseeker @Manuel Castañón
+158 U Seize the Storm @Deruchenko Alexander
+159 R Smoldering Egg @Simon Dominic
+160 U Spellrune Painter @Lucas Graciano
+161 C Stolen Vitality @Mike Bierek
+162 M Sunstreak Phoenix @Brian Valeza
163 C Tavern Ruffian @Zoltan Boros
164 U Thermo-Alchemist @Raymond Swanland
165 U Village Watch @Nestor Ossandon Leal
166 U Voldaren Ambusher @Evyn Fong
+167 C Voldaren Stinger @Martina Fackova
168 R Augur of Autumn @Billy Christian
+169 C Bird Admirer @Slawomir Maniak
+170 C Bounding Wolf @Andrea Radeck
+171 C Bramble Armor @Alessandra Pisano
172 R Briarbridge Tracker @Mathias Kollros
+173 U Brood Weaver @Slawomir Maniak
174 U Burly Breaker @Justine Cruz
175 C Candlelit Cavalry @Viko Menezes
176 U Clear Shot @Craig J Spearing
+177 M Consuming Blob @Simon Dominic
178 U Contortionist Troupe @Jesper Ejsing
+179 U Dawnhart Mentor @Fariba Khamseh
180 C Dawnhart Rejuvenator @Darren Tan
+181 U Deathbonnet Sprout @Filip Burburan
182 U Defend the Celestus @Andrey Kuzinskiy
+183 U Dryad's Revival @Mila Pesic
+184 C Duel for Dominance @Ryan Pancoast
+185 C Eccentric Farmer @Tyler Walpole
+186 C Harvesttide Sentry @Livia Prima
+187 U Hound Tamer @Randy Vargas
188 C Howl of the Hunt @Josu Hernaiz
189 C Might of the Old Ways @Zezhou Chen
+190 U Outland Liberator @Randy Vargas
+191 C Path to the Festival @Darek Zabrocki
192 C Pestilent Wolf @Oriana Menendez
+193 C Plummet @Nicholas Gregory
+194 M Primal Adversary @Ilse Gort
+195 C Return to Nature @Kim Sokol
+196 U Rise of the Ants @Nicholas Gregory
197 R Saryth, the Viper's Fang @Igor Kieryluk
+198 C Shadowbeast Sighting @Piotr Foksowicz
199 C Snarling Wolf @Ilse Gort
+200 R Storm the Festival @Yigit Koroglu
+201 C Tapping at the Window @Nils Hamm
+202 C Timberland Guide @Zoltan Boros
+203 C Tireless Hauler @Forrest Imel
+204 R Tovolar's Huntmaster @Cristi Balanescu
205 U Turn the Earth @Alayna Danner
206 R Unnatural Growth @Svetlin Velinov
+207 R Willow Geist @Ryan Yee
208 M Wrenn and Seven @Heonhwa Choe
+209 R Angelfire Ignition @Yeong-Hao Han
+210 U Arcane Infusion @Jason Felix
211 M Arlinn, the Pack's Hope @Anna Steinbauer
212 U Bladestitched Skaab @Dave Kendall
213 R Can't Stay Away @Milivoj Ćeran
214 U Corpse Cobble @Ravenna Tran
215 R Croaking Counterpart @Yeong-Hao Han
+216 U Dawnhart Wardens @Joshua Raphael
+217 R Dennick, Pious Apprentice @Chris Rallis
218 U Devoted Grafkeeper @Raoul Vitale
+219 R Dire-Strain Rampage @Darek Zabrocki
220 U Diregraf Rebirth @Irina Nordsol
221 U Faithful Mending @Caroline Gariba
222 U Fleshtaker @Kev Walker
+223 R Florian, Voldaren Scion @Justine Cruz
224 R Galvanic Iteration @Johann Bodin
225 R Ghoulcaller's Harvest @Anna Steinbauer
-226 U Hungry for More @Bastien L. Deharme
+226 U Grizzly Ghoul @Vincent Proce
+227 R Hallowed Respite @Jason A. Engle
+228 U Hungry for More @Bastien L. Deharme
229 U Join the Dance @Raoul Vitale
+230 R Katilda, Dawnhart Prime @Bryan Sola
231 U Kessig Naturalist @Johan Grenier
232 R Liesa, Forgotten Archangel @Dmitry Burmak
+233 R Ludevic, Necrogenius @Ryan Pancoast
234 R Old Stickfingers @Jehan Choo
+235 R Rem Karolus, Stalwart Slayer @Francisco Miyara
236 R Rite of Harmony @Rovina Cai
+237 U Rite of Oblivion @Martina Pilcerova
+238 U Rootcoil Creeper @Iris Compiet
+239 U Sacred Fire @Svetlin Velinov
240 M Sigarda, Champion of Light @Howard Lyon
+241 R Siphon Insight @Livia Prima
+242 R Slogurk, the Overslime @Nicholas Gregory
+243 U Storm Skreelix @Darek Zabrocki
+244 U Sunrise Cavalier @John Di Giovanni
+245 M Teferi, Who Slows the Sunset @Heonhwa Choe
246 R Tovolar, Dire Overlord @Chris Rahn
+247 U Unnatural Moonrise @Ryan Pancoast
+248 R Vadrik, Astral Archmage @Kieran Yanner
249 U Vampire Socialite @Suzanne Helmigh
+250 R Wake to Slaughter @Chris Cold
+251 U Winterthorn Blessing @Yeong-Hao Han
252 R The Celestus @Jonas De Ro
+253 C Crossroads Candleguide @Dan Scott
+254 C Jack-o'-Lantern @Josu Hernaiz
+255 U Moonsilver Key @Joseph Meehan
+256 U Mystic Skull @Joe Slucher
257 R Pithing Needle @Ovidio Cartagena
+258 C Silver Bolt @Anna Fehr
+259 C Stuffed Bear @Joe Slucher
260 R Deserted Beach @Jonas De Ro
+261 C Evolving Wilds @Alayna Danner
+262 U Field of Ruin @Chris Ostrowski
263 R Haunted Ridge @Jonas De Ro
+264 M Hostile Hostel @Daniel Ljunggren
265 R Overgrown Farmland @Jonas De Ro
266 R Rockfall Vale @Muhammad Firdaus
267 R Shipwreck Marsh @Jonas De Ro
@@ -117,55 +287,124 @@ Prerelease=6 Boosters, 1 RareMythic+
275 L Mountain @Dan Mumford
276 L Forest @Alayna Danner
277 L Forest @Dan Mumford
+
+[borderless]
278 M Wrenn and Seven @Bram Sels
279 M Arlinn, the Pack's Hope @Eric Deschamps
+280 M Teferi, Who Slows the Sunset @Francisco Miyara
281 R Deserted Beach @Piotr Dura
282 R Haunted Ridge @Piotr Dura
283 R Overgrown Farmland @Donato Giancola
284 R Rockfall Vale @Piotr Dura
285 R Shipwreck Marsh @Steven Belledin
+
+[showcase]
286 R Brutal Cathar @Steve Ellis
287 C Candlegrove Witch @Brigitte Roka
+288 R Suspicious Stowaway @Joshua Alvarado
289 U Baneblade Scoundrel @Andrea De Dominicis
290 R Graveyard Trespasser @Tyler Crook
+291 C Shady Traveler @Joshua Alvarado
+292 U Fangblade Brigand @Sami Makkonen
+293 C Harvesttide Infiltrator @Sami Makkonen
+294 R Reckless Stormseeker @Michael Walsh
+295 U Spellrune Painter @Michael Walsh
296 C Tavern Ruffian @Michael Walsh
297 U Village Watch @Andrea De Dominicis
+298 C Bird Admirer @Joshua Alvarado
299 U Burly Breaker @Sami Makkonen
+300 U Dawnhart Mentor @Antonio Bravo
301 C Dawnhart Rejuvenator @Cabrol
+302 U Hound Tamer @Cabrol
+303 U Outland Liberator @Brigitte Roka
304 R Saryth, the Viper's Fang @Tyler Crook
+305 C Tireless Hauler @Steve Ellis
+306 R Tovolar's Huntmaster @Andrea De Dominicis
307 M Arlinn, the Pack's Hope @Emma Rios
+308 U Dawnhart Wardens @Antonio Bravo
+309 R Katilda, Dawnhart Prime @Tyler Crook
310 U Kessig Naturalist @Rafael Albuquerque
311 R Tovolar, Dire Overlord @Michael Walsh
+312 R Adeline, Resplendent Cathar @rishxxv
+313 M Lier, Disciple of the Drowned @Evan Cagle
314 R Gisa, Glorious Resurrector @Cabrol
315 R Jadar, Ghoulcaller of Nephalia @DZO
+316 M Jerren, Corrupted Bishop @Andrew Mar
+317 R Dennick, Pious Apprentice @Paul Jackson
+318 R Florian, Voldaren Scion @DZO
319 R Liesa, Forgotten Archangel @Evan Cagle
+320 R Ludevic, Necrogenius @Milivoj Ćeran
321 R Old Stickfingers @DZO
+322 R Rem Karolus, Stalwart Slayer @Andy Brase
323 M Sigarda, Champion of Light @N.C. Winters
+324 R Slogurk, the Overslime @Robbie Trevino
+325 R Vadrik, Astral Archmage @Evan Cagle
+
+[extended art]
326 R Curse of Silence @Irina Nordsol
327 M Enduring Angel @Irina Nordsol
+328 R Fateful Absence @Eric Deschamps
+329 M Intrepid Adversary @Viktor Titov
+330 R Sigarda's Splendor @Howard Lyon
+331 M Sigardian Savior @David Rapoza
+332 R Sungold Sentinel @Marta Nael
+333 R Vanquish the Horde @Grzegorz Rutkowski
+334 R Curse of Surveillance @Igor Kieryluk
335 R Grafted Identity @Manuel Castañón
+336 R Malevolent Hermit @Daarken
+337 R Memory Deluge @Lake Hurwitz
+338 R Patrician Geist @Marta Nael
339 M Poppet Stitcher @Simon Dominic
+340 R Sludge Monster @Svetlin Velinov
341 M Spectral Adversary @Uriah Voth
+342 R Triskaidekaphile @Slawomir Maniak
+343 R Bloodline Culling @Liiga Smilshkalne
344 R Champion of the Perished @Kekai Kotaki
345 R Curse of Leeches @Uriah Voth
+346 M Lord of the Forsaken @Kekai Kotaki
+347 R Mask of Griselbrand @Jason Felix
+348 M The Meathook Massacre @Chris Seaman
+349 R Slaughter Specialist @Kari Christensen
350 M Tainted Adversary @Tuan Duong Chu
+351 M Bloodthirsty Adversary @Heonhwa Choe
+352 R Burn Down the House @Campbell White
+353 R Curse of Shaken Faith @Campbell White
+354 R Falkenrath Pit Fighter @Anna Fehr
355 R Geistflame Reservoir @Anna Christenson
356 R Light Up the Night @Wei Wei
+357 M Moonveil Regent @Joshua Raphael
+358 R Smoldering Egg @Simon Dominic
+359 M Sunstreak Phoenix @Brian Valeza
+360 R Augur of Autumn @Billy Christian
361 R Briarbridge Tracker @Mathias Kollros
+362 M Consuming Blob @Simon Dominic
+363 M Primal Adversary @Ilse Gort
+364 R Storm the Festival @Yigit Koroglu
365 R Unnatural Growth @Svetlin Velinov
+366 R Willow Geist @Ryan Yee
+367 R Angelfire Ignition @Yeong-Hao Han
368 R Can't Stay Away @Milivoj Ćeran
369 R Croaking Counterpart @Yeong-Hao Han
+370 R Dire-Strain Rampage @Darek Zabrocki
371 R Galvanic Iteration @Johann Bodin
372 R Ghoulcaller's Harvest @Anna Steinbauer
+373 R Hallowed Respite @Jason A. Engle
374 R Rite of Harmony @Rovina Cai
+375 R Siphon Insight @Livia Prima
+376 R Wake to Slaughter @Chris Cold
377 R The Celestus @Jonas De Ro
378 R Pithing Needle @Ovidio Cartagena
+379 M Hostile Hostel @Daniel Ljunggren
380 L Plains @Andreas Rocha
381 L Island @Andreas Rocha
382 L Swamp @Kasia 'Kafis' Zielińska
383 L Mountain @Muhammad Firdaus
384 L Forest @Andreas Rocha
+
+[buy a box]
385 R Champion of the Perished @Daarken
+
+[promo]
386 R Triskaidekaphile @Mathias Kollros
387 U Gavony Dawnguard @Micah Epstein
388 C Consider @Zezhou Chen
diff --git a/forge-gui/res/formats/Casual/Commander.txt b/forge-gui/res/formats/Casual/Commander.txt
index 35011925e6b..f98af3000b7 100644
--- a/forge-gui/res/formats/Casual/Commander.txt
+++ b/forge-gui/res/formats/Casual/Commander.txt
@@ -3,4 +3,4 @@ Name:Commander
Type:Casual
Subtype:Commander
Order:137
-Banned:Adriana's Valor; Advantageous Proclamation; Ashnod's Coupon; Assemble the Rank and Vile; Backup Plan; Brago's Favor; Double Cross; Double Deal; Double Dip; Double Play; Double Stroke; Double Take; Echoing Boon; Emissary's Ploy; Enter the Dungeon; Flash; Hired Heist; Hold the Perimeter; Hullbreacher; Hymn of the Wilds; Immediate Action; Incendiary Dissent; Iterative Analysis; Lutri, the Spellchaser; Magical Hacker; Mox Lotus; Muzzio's Preparations; Natural Unity; Once More with Feeling; Power Play; R&D's Secret Lair; Richard Garfield, Ph.D.; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Sovereign's Realm; Staying Power; Summoner's Bond; Time Machine; Unexpected Potential; Weight Advantage; Worldknit; Amulet of Quoz; Bronze Tablet; Contract from Below; Darkpact; Demonic Attorney; Jeweled Bird; Rebirth; Tempest Efreet; Timmerian Fiends; Ancestral Recall; Balance; Biorhythm; Black Lotus; Braids, Cabal Minion; Chaos Orb; Coalition Victory; Channel; Emrakul, the Aeons Torn; Erayo, Soratami Ascendant; Falling Star; Fastbond; Gifts Ungiven; Griselbrand; Iona, Shield of Emeria; Karakas; Leovold, Emissary of Trest; Library of Alexandria; Limited Resources; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Panoptic Mirror; Paradox Engine; Primeval Titan; Prophet of Kruphix; Recurring Nightmare; Rofellos, Llanowar Emissary; Shahrazad; Sundering Titan; Sway of the Stars; Sylvan Primordial; Time Vault; Time Walk; Tinker; Tolarian Academy; Trade Secrets; Upheaval; Worldfire; Yawgmoth's Bargain; Cleanse; Crusade; Imprison; Invoke Prejudice; Jihad; Pradesh Gypsies; Stone-Throwing Devils
+Banned:Adriana's Valor; Advantageous Proclamation; Ashnod's Coupon; Assemble the Rank and Vile; Backup Plan; Brago's Favor; Double Cross; Double Deal; Double Dip; Double Play; Double Stroke; Double Take; Echoing Boon; Emissary's Ploy; Enter the Dungeon; Flash; Hired Heist; Hold the Perimeter; Hullbreacher; Hymn of the Wilds; Immediate Action; Incendiary Dissent; Iterative Analysis; Lutri, the Spellchaser; Magical Hacker; Mox Lotus; Muzzio's Preparations; Natural Unity; Once More with Feeling; Power Play; R&D's Secret Lair; Richard Garfield, Ph.D.; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Sovereign's Realm; Staying Power; Summoner's Bond; Time Machine; Unexpected Potential; Weight Advantage; Worldknit; Amulet of Quoz; Bronze Tablet; Contract from Below; Darkpact; Demonic Attorney; Jeweled Bird; Rebirth; Tempest Efreet; Timmerian Fiends; Ancestral Recall; Balance; Biorhythm; Black Lotus; Braids, Cabal Minion; Chaos Orb; Coalition Victory; Channel; Emrakul, the Aeons Torn; Erayo, Soratami Ascendant; Falling Star; Fastbond; Gifts Ungiven; Griselbrand; Iona, Shield of Emeria; Karakas; Leovold, Emissary of Trest; Library of Alexandria; Limited Resources; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Panoptic Mirror; Paradox Engine; Primeval Titan; Prophet of Kruphix; Recurring Nightmare; Rofellos, Llanowar Emissary; Shahrazad; Sundering Titan; Sway of the Stars; Sylvan Primordial; Time Vault; Time Walk; Tinker; Tolarian Academy; Trade Secrets; Upheaval; Yawgmoth's Bargain; Cleanse; Crusade; Imprison; Invoke Prejudice; Jihad; Pradesh Gypsies; Stone-Throwing Devils; Golos, Tireless Pilgrim
diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties
index 287fb642e24..a012c7a5098 100644
--- a/forge-gui/res/languages/en-US.properties
+++ b/forge-gui/res/languages/en-US.properties
@@ -1453,7 +1453,7 @@ lblLogAsPoisonCounters=(as poison counters)
lblCombat=combat
lblNonCombat=non-combat
lblLogSourceDealsNDamageOfTypeToDest={0} deals {1} {2} damage to {3}{4}.
-lblLogPlayerReceivesNPosionCounterFrom={0} receives {1} posion counter from {2}
+lblLogPlayerReceivesNPosionCounterFrom={0} receives {1} poison counter from {2}
lblLogPlayerAssignedAttackerToAttackTarget={0} assigned {1} to attack {2}.
lblLogPlayerDidntBlockAttacker={0} didn''t block {1}.
lblLogPlayerAssignedBlockerToBlockAttacker={0} assigned {1} to block {2}.
@@ -2320,12 +2320,12 @@ lblPutZoneCardsToLibrary=Put cards from {0} to Library
lblPutNTypeCounterOnTarget=Put {0} {1} counter on {2}
lblReturnCardToHandConfirm=Return {0} to hand?
lblNTypeCardsToHand=Return {0} {1} card(s) to Hand
-lblSelectNCardOfSameColorToReveal=Select {0} Card of same color to reveal.
+lblSelectNCardOfSameColorToReveal=Select {0} card(s) of same color to reveal.
lblSelectNMoreTypeCardsTpReveal=Select {0} more {1} card(s) to reveal.
lblSelectTargetCounter=Select {0} to remove a counter
lblRemoveCounterFromCard=remove counter from card
lblRemoveAllCountersConfirm=Remove all counters?
-lblRemoveNTargetCounterFromCardPayCostConfirm=Pay Cost: Remove {0} {1} counter from {2}?
+lblRemoveNTargetCounterFromCardPayCostConfirm=Remove {0} {1} counter(s) from {2}?
lblRemoveCountersFromAInZoneCard=Remove counter(s) from a card in {0}
lblSacrificeCardConfirm=Sacrifice {0}?
lblSelectATargetToSacrifice=Select {0} to sacrifice ({1} left)
diff --git a/forge-gui/res/lists/NonStackingKWList.txt b/forge-gui/res/lists/NonStackingKWList.txt
index 0b6eb77cd78..1e9efe0a5fe 100644
--- a/forge-gui/res/lists/NonStackingKWList.txt
+++ b/forge-gui/res/lists/NonStackingKWList.txt
@@ -1,11 +1,7 @@
All creatures able to block CARDNAME do so.
Banding
-CantBeBlockedByAmount LT2
-CantBeBlockedByAmount LT3
CARDNAME's activated abilities can't be activated.
-CARDNAME attacks each turn if able.
CARDNAME attacks each combat if able.
-CARDNAME blocks each turn if able.
CARDNAME blocks each combat if able.
CARDNAME can attack as though it didn't have defender.
CARDNAME can block any number of creatures.
diff --git a/forge-gui/res/lists/net-decks-archive-block.txt b/forge-gui/res/lists/net-decks-archive-block.txt
index e04e0aab3d7..776dd2ba03d 100644
--- a/forge-gui/res/lists/net-decks-archive-block.txt
+++ b/forge-gui/res/lists/net-decks-archive-block.txt
@@ -974,3 +974,5 @@
2021-06-27 Sealed Mh2 Block Champs (8 decks) | https://downloads.cardforge.org/decks/archive/block/2021-06-27-sealed-mh2-block-champs.zip
2021-07-25 Sealed Mh2 Block Super Qualifier (8 decks) | https://downloads.cardforge.org/decks/archive/block/2021-07-25-sealed-mh2-block-super-qualifier.zip
2021-08-15 Sealed Mh2 Block Super Qualifier (8 decks) | https://downloads.cardforge.org/decks/archive/block/2021-08-15-sealed-mh2-block-super-qualifier.zip
+2021-08-29 Sealed Mh2 Block Mocs (8 decks) | https://downloads.cardforge.org/decks/archive/block/2021-08-29-sealed-mh2-block-mocs.zip
+2021-08-30 Sealed Mh2 Block Mocs (8 decks) | https://downloads.cardforge.org/decks/archive/block/2021-08-30-sealed-mh2-block-mocs.zip
diff --git a/forge-gui/res/lists/net-decks-archive-legacy.txt b/forge-gui/res/lists/net-decks-archive-legacy.txt
index a06c93ff292..4d7092db8c7 100644
--- a/forge-gui/res/lists/net-decks-archive-legacy.txt
+++ b/forge-gui/res/lists/net-decks-archive-legacy.txt
@@ -2088,3 +2088,24 @@
2021-08-15 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-15-legacy-challenge.zip
2021-08-16 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-16-legacy-challenge.zip
2021-08-17 Legacy Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-17-legacy-preliminary.zip
+2021-08-20 Legacy Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-20-legacy-preliminary.zip
+2021-08-21 Legacy League (44 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-21-legacy-league.zip
+2021-08-21 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-21-legacy-preliminary.zip
+2021-08-22 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-22-legacy-challenge.zip
+2021-08-23 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-23-legacy-challenge.zip
+2021-08-24 Legacy Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-24-legacy-preliminary.zip
+2021-08-25 Legacy Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-25-legacy-preliminary.zip
+2021-08-28 Legacy League (53 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-28-legacy-league.zip
+2021-08-28 Legacy Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-28-legacy-preliminary.zip
+2021-08-29 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-29-legacy-challenge.zip
+2021-08-30 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-08-30-legacy-challenge.zip
+2021-09-01 Legacy Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-01-legacy-preliminary.zip
+2021-09-02 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-02-legacy-preliminary.zip
+2021-09-03 Legacy Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-03-legacy-preliminary.zip
+2021-09-04 Legacy League (52 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-04-legacy-league.zip
+2021-09-04 Legacy Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-04-legacy-preliminary.zip
+2021-09-05 Legacy Super Qualifier (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-05-legacy-super-qualifier.zip
+2021-09-06 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-06-legacy-challenge.zip
+2021-09-07 Legacy Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-07-legacy-preliminary.zip
+2021-09-08 Legacy Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-08-legacy-preliminary.zip
+2021-09-09 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-09-09-legacy-preliminary.zip
diff --git a/forge-gui/res/lists/net-decks-archive-modern.txt b/forge-gui/res/lists/net-decks-archive-modern.txt
index 1362e311dc7..18adfba457b 100644
--- a/forge-gui/res/lists/net-decks-archive-modern.txt
+++ b/forge-gui/res/lists/net-decks-archive-modern.txt
@@ -2719,3 +2719,34 @@
2021-08-16 Modern Super Qualifier (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-16-modern-super-qualifier.zip
2021-08-17 Modern League (76 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-17-modern-league.zip
2021-08-17 Modern Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-17-modern-preliminary.zip
+2021-08-18 Modern Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-18-modern-preliminary.zip
+2021-08-19 Modern Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-19-modern-preliminary.zip
+2021-08-20 Modern League (64 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-20-modern-league.zip
+2021-08-20 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-20-modern-preliminary.zip
+2021-08-21 Modern Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-21-modern-preliminary.zip
+2021-08-22 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-22-modern-challenge.zip
+2021-08-23 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-23-modern-challenge.zip
+2021-08-24 Modern League (76 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-24-modern-league.zip
+2021-08-24 Modern Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-24-modern-preliminary.zip
+2021-08-25 Modern Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-25-modern-preliminary.zip
+2021-08-26 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-26-modern-preliminary.zip
+2021-08-27 Modern League (61 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-27-modern-league.zip
+2021-08-27 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-27-modern-preliminary.zip
+2021-08-28 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-28-modern-preliminary.zip
+2021-08-29 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-29-modern-challenge.zip
+2021-08-30 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-30-modern-challenge.zip
+2021-08-31 Modern League (80 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-31-modern-league.zip
+2021-08-31 Modern Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-08-31-modern-preliminary.zip
+2021-09-01 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-01-modern-preliminary.zip
+2021-09-02 Modern Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-02-modern-preliminary.zip
+2021-09-03 Modern League (53 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-03-modern-league.zip
+2021-09-03 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-03-modern-preliminary.zip
+2021-09-04 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-04-modern-preliminary.zip
+2021-09-05 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-05-modern-challenge.zip
+2021-09-06 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-06-modern-challenge.zip
+2021-09-07 Modern League (68 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-07-modern-league.zip
+2021-09-07 Modern Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-07-modern-preliminary.zip
+2021-09-08 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-08-modern-preliminary.zip
+2021-09-09 Modern Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-09-modern-preliminary.zip
+2021-09-10 Modern League (65 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-10-modern-league.zip
+2021-09-10 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-09-10-modern-preliminary.zip
diff --git a/forge-gui/res/lists/net-decks-archive-pauper.txt b/forge-gui/res/lists/net-decks-archive-pauper.txt
index b09bbf25e64..022f4388125 100644
--- a/forge-gui/res/lists/net-decks-archive-pauper.txt
+++ b/forge-gui/res/lists/net-decks-archive-pauper.txt
@@ -1784,4 +1784,16 @@
2021-08-11 Pauper League (22 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-11-pauper-league.zip
2021-08-15 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-15-pauper-challenge.zip
2021-08-16 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-16-pauper-challenge.zip
+2021-08-18 Pauper League (22 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-18-pauper-league.zip
+2021-08-20 Pauper Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-20-pauper-preliminary.zip
+2021-08-22 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-22-pauper-challenge.zip
+2021-08-23 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-23-pauper-challenge.zip
+2021-08-25 Pauper League (20 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-25-pauper-league.zip
+2021-08-29 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-29-pauper-challenge.zip
+2021-08-30 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-08-30-pauper-challenge.zip
+2021-09-01 Pauper League (21 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-09-01-pauper-league.zip
+2021-09-03 Pauper Preliminary (3 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-09-03-pauper-preliminary.zip
+2021-09-05 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-09-05-pauper-challenge.zip
+2021-09-06 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-09-06-pauper-challenge.zip
+2021-09-08 Pauper League (17 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-09-08-pauper-league.zip
Pauper League (20 decks) | https://downloads.cardforge.org/decks/archive/pauper/pauper-league.zip
diff --git a/forge-gui/res/lists/net-decks-archive-pioneer.txt b/forge-gui/res/lists/net-decks-archive-pioneer.txt
index 7a2935bb946..616a4cda8d1 100644
--- a/forge-gui/res/lists/net-decks-archive-pioneer.txt
+++ b/forge-gui/res/lists/net-decks-archive-pioneer.txt
@@ -701,3 +701,28 @@
2021-08-16 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-16-pioneer-challenge.zip
2021-08-16 Pioneer League (19 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-16-pioneer-league.zip
2021-08-17 Pioneer Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-17-pioneer-preliminary.zip
+2021-08-18 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-18-pioneer-preliminary.zip
+2021-08-19 Pioneer League (20 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-19-pioneer-league.zip
+2021-08-19 Pioneer Preliminary (9 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-19-pioneer-preliminary.zip
+2021-08-20 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-20-pioneer-preliminary.zip
+2021-08-21 Pioneer Preliminary (12 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-21-pioneer-preliminary.zip
+2021-08-22 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-22-pioneer-challenge.zip
+2021-08-22 Pioneer Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-22-pioneer-preliminary.zip
+2021-08-22 Pioneer Premier (16 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-22-pioneer-premier.zip
+2021-08-23 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-23-pioneer-challenge.zip
+2021-08-23 Pioneer League (21 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-23-pioneer-league.zip
+2021-08-24 Pioneer Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-24-pioneer-preliminary.zip
+2021-08-25 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-25-pioneer-preliminary.zip
+2021-08-26 Pioneer League (23 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-26-pioneer-league.zip
+2021-08-27 Pioneer Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-27-pioneer-preliminary.zip
+2021-08-28 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-28-pioneer-preliminary.zip
+2021-08-29 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-29-pioneer-challenge.zip
+2021-08-30 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-30-pioneer-challenge.zip
+2021-08-30 Pioneer League (21 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-08-30-pioneer-league.zip
+2021-09-02 Pioneer League (13 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-02-pioneer-league.zip
+2021-09-03 Pioneer Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-03-pioneer-preliminary.zip
+2021-09-05 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-05-pioneer-challenge.zip
+2021-09-06 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-06-pioneer-challenge.zip
+2021-09-06 Pioneer League (20 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-06-pioneer-league.zip
+2021-09-08 Pioneer Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-08-pioneer-preliminary.zip
+2021-09-09 Pioneer League (17 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-09-09-pioneer-league.zip
diff --git a/forge-gui/res/lists/net-decks-archive-standard.txt b/forge-gui/res/lists/net-decks-archive-standard.txt
index 2827f9a44b6..d77aef384c2 100644
--- a/forge-gui/res/lists/net-decks-archive-standard.txt
+++ b/forge-gui/res/lists/net-decks-archive-standard.txt
@@ -2477,3 +2477,16 @@
2021-08-15 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-15-standard-challenge.zip
2021-08-16 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-16-standard-challenge.zip
2021-08-16 Standard League (11 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-16-standard-league.zip
+2021-08-19 Standard League (10 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-19-standard-league.zip
+2021-08-22 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-22-standard-challenge.zip
+2021-08-23 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-23-standard-challenge.zip
+2021-08-23 Standard League (12 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-23-standard-league.zip
+2021-08-26 Standard League (7 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-26-standard-league.zip
+2021-08-29 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-29-standard-challenge.zip
+2021-08-30 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-30-standard-challenge.zip
+2021-08-30 Standard League (11 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-08-30-standard-league.zip
+2021-09-02 Standard League (10 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-09-02-standard-league.zip
+2021-09-05 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-09-05-standard-challenge.zip
+2021-09-06 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-09-06-standard-challenge.zip
+2021-09-06 Standard League (8 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-09-06-standard-league.zip
+2021-09-09 Standard League (11 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-09-09-standard-league.zip
diff --git a/forge-gui/res/lists/net-decks-archive-vintage.txt b/forge-gui/res/lists/net-decks-archive-vintage.txt
index acc49ef7ae7..9e8232ae0e9 100644
--- a/forge-gui/res/lists/net-decks-archive-vintage.txt
+++ b/forge-gui/res/lists/net-decks-archive-vintage.txt
@@ -1500,3 +1500,20 @@
2021-08-15 Vintage League (11 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-15-vintage-league.zip
2021-08-16 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-16-vintage-challenge.zip
2021-08-17 Vintage Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-17-vintage-preliminary.zip
+2021-08-19 Vintage Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-19-vintage-preliminary.zip
+2021-08-21 Vintage Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-21-vintage-preliminary.zip
+2021-08-22 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-22-vintage-challenge.zip
+2021-08-22 Vintage League (12 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-22-vintage-league.zip
+2021-08-23 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-23-vintage-challenge.zip
+2021-08-26 Vintage Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-26-vintage-preliminary.zip
+2021-08-28 Vintage Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-28-vintage-preliminary.zip
+2021-08-29 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-29-vintage-challenge.zip
+2021-08-29 Vintage League (13 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-29-vintage-league.zip
+2021-08-30 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-08-30-vintage-challenge.zip
+2021-09-02 Vintage Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-02-vintage-preliminary.zip
+2021-09-04 Vintage Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-04-vintage-preliminary.zip
+2021-09-05 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-05-vintage-challenge.zip
+2021-09-05 Vintage League (8 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-05-vintage-league.zip
+2021-09-06 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-06-vintage-challenge.zip
+2021-09-06 Vintage Premier (16 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-06-vintage-premier.zip
+2021-09-09 Vintage Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-09-09-vintage-preliminary.zip
diff --git a/forge-gui/res/puzzle/PS_AFR01.pzl b/forge-gui/res/puzzle/PS_AFR1.pzl
similarity index 100%
rename from forge-gui/res/puzzle/PS_AFR01.pzl
rename to forge-gui/res/puzzle/PS_AFR1.pzl
diff --git a/forge-gui/res/puzzle/PS_AFR2.pzl b/forge-gui/res/puzzle/PS_AFR2.pzl
new file mode 100644
index 00000000000..f9c6f3d2347
--- /dev/null
+++ b/forge-gui/res/puzzle/PS_AFR2.pzl
@@ -0,0 +1,20 @@
+[metadata]
+Name:Possibility Storm - Adventures in the Forgotten Realms #02
+URL:https://i0.wp.com/www.possibilitystorm.com/wp-content/uploads/2021/08/180.-AFR2-scaled.jpg
+Goal:Win
+Turns:1
+Difficulty:Rare
+Description:Win this turn. Your opponent's Mind Flayer is controlling your Treeshaker Chimera 'mutate stack'. Both players start with 12 cards remaining in their libraries. Assume whatever they are is not relevant to the solution.
+[state]
+humanlife=20
+ailife=18
+turn=1
+activeplayer=human
+activephase=MAIN1
+humanhand=Lore Drakkis;Struggle for Skemfar;Witherbloom Command
+humanlibrary=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt
+humanbattlefield=Mordenkainen|Counters:LOYALTY=3;Pestilent Cauldron;Koma's Faithful;Vineglimmer Snarl;Vineglimmer Snarl;Vineglimmer Snarl;Temple of Deceit;Temple of Deceit;Temple of Deceit;Island;Treeshaker Chimera|MergedCards:Dreamtail Heron,Dreamtail Heron,Dreamtail Heron|Counters:P1P1=1|Id:1
+ailibrary=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt
+aibattlefield=Mind Flayer|ExecuteScript:TrigChange->1;Cogwork Archivist|Tapped
+humanprecast=Mordenkainen:2
+removesummoningsickness=true
diff --git a/forge-gui/res/puzzle/PS_AFR3.pzl b/forge-gui/res/puzzle/PS_AFR3.pzl
new file mode 100644
index 00000000000..3ede0e82057
--- /dev/null
+++ b/forge-gui/res/puzzle/PS_AFR3.pzl
@@ -0,0 +1,19 @@
+[metadata]
+Name:Possibility Storm - Adventures in the Forgotten Realms #03
+URL:https://i0.wp.com/www.possibilitystorm.com/wp-content/uploads/2021/08/181.-AFR3-scaled.jpg
+Goal:Win
+Turns:1
+Difficulty:Mythic
+Description:Start in your first main phase (with Elspeth's Nightmare chapter II ability resolving) and win this turn. You topdecked Pharika's Libation this draw step. Your opponent resolved Revel in Silence during your upkeep. Assume your opponent is tapped out.
+[state]
+humanlife=20
+ailife=3
+turn=1
+activeplayer=human
+activephase=DRAW
+activephaseadvance=MAIN1
+humanhand=Pharika's Libation
+humanbattlefield=Spare Dagger;Elspeth's Nightmare|Counters:LORE=1;Hive of the Eye Tyrant;Hive of the Eye Tyrant;Hive of the Eye Tyrant;Swamp;Swamp;Swamp;Swamp;Swamp
+aihand=Indomitable Will;Indomitable Will;Indomitable Will
+aibattlefield=Nine Lives|Counters:INCARNATION=6;Speaker of the Heavens
+aiprecast=Revel in Silence
diff --git a/forge-gui/res/puzzle/PS_AFR4.pzl b/forge-gui/res/puzzle/PS_AFR4.pzl
new file mode 100644
index 00000000000..75e97fbb608
--- /dev/null
+++ b/forge-gui/res/puzzle/PS_AFR4.pzl
@@ -0,0 +1,16 @@
+[metadata]
+Name:Possibility Storm - Adventures in the Forgotten Realms #04
+URL:https://i2.wp.com/www.possibilitystorm.com/wp-content/uploads/2021/08/182.-AFR4-scaled.jpg
+Goal:Win
+Turns:1
+Difficulty:Uncommon
+Description:Win this turn. Your opponent has no cards in hand and no mana available.
+[state]
+humanlife=20
+ailife=7
+turn=1
+activeplayer=human
+activephase=MAIN1
+humanhand=Goblin Javelineer;Hulking Bugbear;You See a Pair of Goblins
+humanbattlefield=Hobgoblin Bandit Lord;Battle Cry Goblin;Mountain;Mountain;Mountain;Mountain;Mountain
+aibattlefield=Vadrok, Apex of Thunder;Yorion, Sky Nomad
diff --git a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java
index 76e07081e85..a83b31277da 100644
--- a/forge-gui/src/main/java/forge/deck/DeckgenUtil.java
+++ b/forge-gui/src/main/java/forge/deck/DeckgenUtil.java
@@ -226,8 +226,6 @@ public class DeckgenUtil {
return deck;
}
-
-
public static Deck buildLDACArchetypeDeck(GameFormat format, boolean isForAI){
List keys = new ArrayList<>(CardArchetypeLDAGenerator.ldaArchetypes.get(format.getName()));
Archetype randomKey = keys.get( MyRandom.getRandom().nextInt(keys.size()) );
diff --git a/forge-gui/src/main/java/forge/download/AutoUpdater.java b/forge-gui/src/main/java/forge/download/AutoUpdater.java
index f39547d1bce..bf656022b58 100644
--- a/forge-gui/src/main/java/forge/download/AutoUpdater.java
+++ b/forge-gui/src/main/java/forge/download/AutoUpdater.java
@@ -167,7 +167,7 @@ public class AutoUpdater {
System.out.println(index);
Pattern p = Pattern.compile(">forge-(.*SNAPSHOT)");
Matcher m = p.matcher(index);
- while(m.find()){
+ while (m.find()) {
version = m.group(1);
}
}
@@ -178,7 +178,7 @@ public class AutoUpdater {
Pattern p = Pattern.compile("(.*)");
Matcher m = p.matcher(xml);
- while(m.find()){
+ while (m.find()) {
version = m.group(1);
}
}
@@ -229,7 +229,7 @@ public class AutoUpdater {
};
SwingUtilities.invokeLater(callback);
- //
+
return false;
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java b/forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java
index cfc3600f746..24746ba1ed1 100644
--- a/forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java
+++ b/forge-gui/src/main/java/forge/gamemodes/limited/ArchetypeDeckBuilder.java
@@ -57,6 +57,4 @@ public class ArchetypeDeckBuilder extends CardThemedDeckBuilder{
return super.getBasicLand(basicLand);
}
-
-
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraftAI.java b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraftAI.java
index d5d61676818..eda3ad645f1 100644
--- a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraftAI.java
+++ b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraftAI.java
@@ -108,4 +108,3 @@ public class BoosterDraftAI {
}
} // BoosterDraftAI()
-
diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java
index e41acf06ab8..30ca03724d7 100644
--- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java
+++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java
@@ -68,8 +68,6 @@ public class LimitedPlayer {
chooseFrom.remove(bestPick);
-
-
CardPool pool = deck.getOrCreate(section);
pool.add(bestPick);
draftedThisRound++;
@@ -147,4 +145,3 @@ public class LimitedPlayer {
}
*/
}
-
diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedWinLoseController.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedWinLoseController.java
index 69a5ee01eef..e223b6cae1a 100644
--- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedWinLoseController.java
+++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedWinLoseController.java
@@ -56,12 +56,10 @@ public abstract class LimitedWinLoseController {
view.getBtnContinue().setEnabled(true);
showTournamentInfo(localizer.getMessage("btnWonRound") + gauntlet.getCurrentRound() + "/"
+ gauntlet.getRounds());
- }
- else {
+ } else {
showTournamentInfo(localizer.getMessage("btnWonTournament"));
}
- }
- else {
+ } else {
view.getBtnContinue().setVisible(false);
showTournamentInfo(localizer.getMessage("btnLoseRound") + gauntlet.getCurrentRound() + "/"
+ gauntlet.getRounds());
diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/ReadDraftRankings.java b/forge-gui/src/main/java/forge/gamemodes/limited/ReadDraftRankings.java
index 5707db096b2..656036c856f 100644
--- a/forge-gui/src/main/java/forge/gamemodes/limited/ReadDraftRankings.java
+++ b/forge-gui/src/main/java/forge/gamemodes/limited/ReadDraftRankings.java
@@ -89,7 +89,7 @@ public class ReadDraftRankings {
* the card's edition
* @return ranking
*/
- public Double getRanking(String cardName, String edition) {
+ public Double getRanking(String cardName, String edition) {
Double rank = null;
if (draftRankings.containsKey(edition)) {
diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java b/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java
index da9c95d5154..e290d57e02b 100644
--- a/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java
+++ b/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java
@@ -544,7 +544,6 @@ public final class BoosterUtils {
* @return List
*/
public static List generateCardRewardList(final String s) {
-
if (StringUtils.isBlank(s)) {
return null;
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestChallengeGenerator.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestChallengeGenerator.java
index 810247d5f3f..7c59d18a569 100644
--- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestChallengeGenerator.java
+++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestChallengeGenerator.java
@@ -25,7 +25,7 @@ public class QuestChallengeGenerator {
QuestChallengeGenerator(GameFormat baseFormat){
this.baseFormat=baseFormat;
- if(baseFormat.getName().equals((FModel.getFormats().getModern().getName()))){
+ if (baseFormat.getName().equals((FModel.getFormats().getModern().getName()))) {
formatMedium=FModel.getFormats().get("Legacy");
formatHard=FModel.getFormats().get("Vintage");
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java
index 2721a3f4ebf..686b37d7839 100644
--- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java
+++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventCommanderDuelManager.java
@@ -40,7 +40,7 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte
/**
* Assembles the list of all possible Commander duels via CommanderDeckGenerator. Should be done within constructor.
*/
- private void assembleDuels(){
+ private void assembleDuels() {
//isCardGen = true seemed to make slightly more difficult decks based purely on experience with a very small sample size.
//Gotta work on this more, its making pretty average decks after further testing.
expertCommanderDecks = CommanderDeckGenerator.getCommanderDecks(DeckFormat.Commander, true, true);
@@ -86,11 +86,11 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte
* title replaced as Random.
* @return ArrayList of QuestEventDuels containing 4 duels.
*/
- public List generateDuels(){
+ public List generateDuels() {
final List duelOpponents = new ArrayList<>();
//While there are less than 4 duels chosen
- while(duelOpponents.size() < 4){
+ while (duelOpponents.size() < 4) {
//Get a random duel from the possible duels list
QuestEventCommanderDuel duel = (QuestEventCommanderDuel)commanderDuels.get(((int) (commanderDuels.size() * MyRandom.getRandom().nextDouble())));
@@ -105,8 +105,6 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte
//Modify deck for difficulty
modifyDuelForDifficulty(duel);
-
-
}
//Modify the stats of the final duel to hide the opponent, creating a "random" duel.
@@ -135,7 +133,7 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte
* @param dp The easy generation commander deck
* @return The same commander's expert generation DeckProxy
*/
- private Deck getExpertGenDeck(DeckProxy dp){
+ private Deck getExpertGenDeck(DeckProxy dp) {
for(QuestEventDuel qed : commanderDuels){
QuestEventCommanderDuel cmdQED = (QuestEventCommanderDuel)qed;
if(cmdQED.getDeckProxy().getName().equals(dp.getName())){
@@ -150,7 +148,7 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte
* of the same commander's deck. Medium replaces 30%, Hard replaces 60%, Expert replaces 100%.
* @param duel The QuestEventCommanderDuel to modify
*/
- private void modifyDuelForDifficulty(QuestEventCommanderDuel duel){
+ private void modifyDuelForDifficulty(QuestEventCommanderDuel duel) {
final QuestPreferences questPreferences = FModel.getQuestPreferences();
final int index = FModel.getQuest().getAchievements().getDifficulty();
final int numberOfWins = FModel.getQuest().getAchievements().getWin();
@@ -184,15 +182,14 @@ public class QuestEventCommanderDuelManager implements QuestEventDuelManagerInte
List expertList = expertMain.toFlatList();
//Replace cards in the easy deck with cards from the expert deck up to the difficulty replacement percent
- for(int i = 0; i < difficultyReplacementPercent; i++){
- if(!easyMain.contains(expertList.get(i))) { //ensure that the card being copied over isn't already in the deck
+ for (int i = 0; i < difficultyReplacementPercent; i++) {
+ if (!easyMain.contains(expertList.get(i))) { //ensure that the card being copied over isn't already in the deck
easyMain.remove(easyList.get(i));
easyMain.add(expertList.get(i));
- }
- else{
+ } else {
expertList.remove(expertList.get(i));
i--;
- if(expertList.size() == 0) break; //break if there are no more cards to copy over
+ if (expertList.size() == 0) break; //break if there are no more cards to copy over
}
}
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java
index bbe04e82ddc..576670a806b 100644
--- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java
+++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java
@@ -265,7 +265,6 @@ public class QuestEventDraft implements IQuestEvent {
}
}
} else {
-
String aiIndex = "";
for (int i = aiNames.length - 1; i >= 0; i--) {
@@ -298,7 +297,6 @@ public class QuestEventDraft implements IQuestEvent {
int boosterPrices = 0;
for (final String boosterSet : boosterConfiguration.split("/")) {
-
int value;
final String boosterName = FModel.getMagicDb().getEditions().get(boosterSet).getName() + " Booster Pack";
@@ -309,7 +307,6 @@ public class QuestEventDraft implements IQuestEvent {
}
boosterPrices += value;
-
}
prizePool -= boosterPrices * 8;
diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestSpellShop.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestSpellShop.java
index 16d06e7b146..d672ebfae5b 100644
--- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestSpellShop.java
+++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestSpellShop.java
@@ -56,8 +56,7 @@ public class QuestSpellShop {
nsArt = card.getName() + " (" + artIndex + ")|" + pc.getEdition();
foil = ((PaperCard) card).isFoil();
- }
- else {
+ } else {
ns = card.getName();
nsArt = ns;
}
@@ -265,8 +264,7 @@ public class QuestSpellShop {
GuiBase.getInterface().showCardList(booster.getName(), "You have found the following cards inside:", remainingCards);
}
- }
- else {
+ } else {
GuiBase.getInterface().showCardList(booster.getName(), "You have found the following cards inside:", newCards);
}
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java
index 04d12c8f13d..619123a89a3 100644
--- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java
+++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java
@@ -364,8 +364,7 @@ public class QuestTournamentController {
if (draft.playerHasMatchesLeft()) {
view.getBtnLeaveTournament().setText(localizer.getMessage("btnLeaveTournament"));
- }
- else {
+ } else {
view.getBtnLeaveTournament().setText(localizer.getMessage("lblCollectPrizes"));
}
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVigor.java b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVigor.java
index 665a35f4fe8..708d8049472 100644
--- a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVigor.java
+++ b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVigor.java
@@ -44,5 +44,4 @@ public class QuestItemCharmOfVigor extends QuestItemBasic {
return super.isAvailableForPurchase(qA, qCtrl);
}
-
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVim.java b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVim.java
index 760411d8e1b..966a14ddf50 100644
--- a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVim.java
+++ b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemCharmOfVim.java
@@ -31,5 +31,4 @@ public class QuestItemCharmOfVim extends QuestItemBasic {
return super.isAvailableForPurchase(qA, qCtrl);
}
-
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemZeppelin.java b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemZeppelin.java
index 1402ab7b14f..5f410a48c4b 100644
--- a/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemZeppelin.java
+++ b/forge-gui/src/main/java/forge/gamemodes/quest/bazaar/QuestItemZeppelin.java
@@ -44,5 +44,4 @@ public class QuestItemZeppelin extends QuestItemBasic {
return super.isAvailableForPurchase(qA, qCtrl) && qA.hasItem(QuestItemType.MAP);
}
-
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/io/QuestDataIO.java b/forge-gui/src/main/java/forge/gamemodes/quest/io/QuestDataIO.java
index bdf872ae96a..3b0f28c4aa5 100644
--- a/forge-gui/src/main/java/forge/gamemodes/quest/io/QuestDataIO.java
+++ b/forge-gui/src/main/java/forge/gamemodes/quest/io/QuestDataIO.java
@@ -267,7 +267,6 @@ public class QuestDataIO {
// Migrate DraftTournaments to use new Tournament class
}
-
final QuestAssets qS = newData.getAssets();
final QuestAchievements qA = newData.getAchievements();
@@ -647,7 +646,6 @@ public class QuestDataIO {
QuestEventDraftContainer output = new QuestEventDraftContainer();
while (reader.hasMoreChildren()) {
-
reader.moveDown();
// TODO Add Tournament
String draftName = null;
@@ -735,7 +733,6 @@ public class QuestDataIO {
output.add(draft);
reader.moveUp();
-
}
return output;
diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java
index a1630810707..4747832c4b4 100644
--- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java
+++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/AbstractTournament.java
@@ -68,7 +68,6 @@ public abstract class AbstractTournament implements Serializable {
public void setInitialized(boolean initialized) { this.initialized = initialized; }
-
public boolean isPlayerRemaining(TournamentPlayer player) {
return remainingPlayers.contains(player);
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPairing.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPairing.java
index 338eb829cbb..78fcbdf4fd9 100644
--- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPairing.java
+++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPairing.java
@@ -21,21 +21,21 @@ public class TournamentPairing {
winner = null;
}
- public int getRound() { return round; }
+ public int getRound() { return round; }
- public void setRound(int round) { this.round = round; }
+ public void setRound(int round) { this.round = round; }
- public boolean isBye() { return bye; }
+ public boolean isBye() { return bye; }
- public void setBye(boolean bye) { this.bye = bye; }
+ public void setBye(boolean bye) { this.bye = bye; }
- public List getPairedPlayers() { return pairedPlayers; }
+ public List getPairedPlayers() { return pairedPlayers; }
- public List getOutcomes() { return outcomes; }
+ public List getOutcomes() { return outcomes; }
- public TournamentPlayer getWinner() { return winner; }
+ public TournamentPlayer getWinner() { return winner; }
- public void setWinner(TournamentPlayer winner) { this.winner = winner; }
+ public void setWinner(TournamentPlayer winner) { this.winner = winner; }
public void setWinnerByIndex(int index) {
for(TournamentPlayer pl : pairedPlayers) {
diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPlayer.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPlayer.java
index 4dca1f2b529..e825e304dde 100644
--- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPlayer.java
+++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentPlayer.java
@@ -106,9 +106,9 @@ public class TournamentPlayer {
this.active = active;
}
- public int getIndex() { return index; }
+ public int getIndex() { return index; }
- public void setIndex(int index) { this.index = index; }
+ public void setIndex(int index) { this.index = index; }
public List getPreviousOpponents() { return previousOpponents; }
diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentRoundRobin.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentRoundRobin.java
index 61f4b254ff8..3932d45f8e7 100644
--- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentRoundRobin.java
+++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentRoundRobin.java
@@ -50,7 +50,7 @@ public class TournamentRoundRobin extends AbstractTournament {
activeRound++;
- for(int i = 0; i < numPlayers/2; i++) {
+ for (int i = 0; i < numPlayers/2; i++) {
boolean bye = false;
if (roundPairings.get(i).getPlayer().getName().equals("BYE")) {
bye = true;
diff --git a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java
index 066481c1555..6afe40902ec 100644
--- a/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java
+++ b/forge-gui/src/main/java/forge/gamemodes/tournament/system/TournamentSwiss.java
@@ -45,7 +45,7 @@ public class TournamentSwiss extends AbstractTournament {
if (allPlayers.size() % 2 == 1) {
int i = allPlayers.size() - 1;
- while(byePlayer == null) {
+ while (byePlayer == null) {
TournamentPlayer pl = allPlayers.get(i);
if (pl.getByes() == 0) {
byePlayer = pl;
@@ -72,8 +72,8 @@ public class TournamentSwiss extends AbstractTournament {
}
int score = groupPlayers.get(frontOfGroup).getScore();
- while(pairing) {
- for(backOfGroup = frontOfGroup+1; backOfGroup < groupPlayers.size(); backOfGroup++) {
+ while (pairing) {
+ for (backOfGroup = frontOfGroup+1; backOfGroup < groupPlayers.size(); backOfGroup++) {
if (allPlayers.get(backOfGroup).getScore() < score) {
break;
}
@@ -115,10 +115,10 @@ public class TournamentSwiss extends AbstractTournament {
int oppClashes = 0;
List unpairedPlayers = Lists.newArrayList();
- for(TournamentPlayer tp : players) {
+ for (TournamentPlayer tp : players) {
HashSet opponents = new HashSet<>();
List prevOpps = tp.getPreviousOpponents();
- for(TournamentPlayer opp : players) {
+ for (TournamentPlayer opp : players) {
if (!prevOpps.contains(opp.getIndex()) && !opp.equals(tp)) {
opponents.add(opp);
} else if (!opp.equals(tp)) {
@@ -141,20 +141,20 @@ public class TournamentSwiss extends AbstractTournament {
}
});
- while(players.size() > 1) {
+ while (players.size() > 1) {
TournamentPlayer initialPlayer = players.get(0);
players.remove(0);
ArrayList pair = new ArrayList<>();
HashSet opposing = availableOpponents.get(initialPlayer);
- for(TournamentPlayer opp : players) {
+ for (TournamentPlayer opp : players) {
if (opposing.contains(opp)) {
pair.add(opp);
break;
}
}
- for(TournamentPlayer opp : pair) {
+ for (TournamentPlayer opp : pair) {
players.remove(opp);
}
@@ -192,8 +192,6 @@ public class TournamentSwiss extends AbstractTournament {
return unpairable;
}
-
-
@Override
public boolean reportMatchCompletion(TournamentPairing pairing) {
// Returns whether there are more matches left in this round
@@ -215,7 +213,7 @@ public class TournamentSwiss extends AbstractTournament {
}
for (TournamentPlayer tp : pairing.getPairedPlayers()) {
- for(Integer i : oppIndexes) {
+ for (Integer i : oppIndexes) {
if (i != null && !i.equals(tp.getIndex())) {
tp.addOpponentIndex(i);
}
@@ -229,7 +227,6 @@ public class TournamentSwiss extends AbstractTournament {
return true;
}
-
@Override
public void endTournament() {
this.activePairings.clear();
diff --git a/forge-gui/src/main/java/forge/gui/control/FControlGamePlayback.java b/forge-gui/src/main/java/forge/gui/control/FControlGamePlayback.java
index 6ce805877d6..ba348be4225 100644
--- a/forge-gui/src/main/java/forge/gui/control/FControlGamePlayback.java
+++ b/forge-gui/src/main/java/forge/gui/control/FControlGamePlayback.java
@@ -87,7 +87,7 @@ public class FControlGamePlayback extends IGameEventVisitor.Base {
try {
final boolean isUiToStop = !humanController.getGui().isUiSetToSkipPhase(ev.playerTurn.getView(), ev.phase);
- switch(ev.phase) {
+ switch (ev.phase) {
case COMBAT_END:
case COMBAT_DECLARE_ATTACKERS:
case COMBAT_DECLARE_BLOCKERS:
diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java
index 313d27841b5..81eded8cce2 100644
--- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java
+++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java
@@ -123,7 +123,7 @@ public final class ForgeConstants {
public static final String SPRITE_PLANAR_CONQUEST_FILE = "sprite_planar_conquest.png";
public static final String SPRITE_SETLOGO_FILE = "sprite_setlogo.png";
public static final String SPRITE_WATERMARK_FILE = "sprite_watermark.png";
- public static final String SPRITE_CARDBG_FILE = "sprite_cardbg.png";
+ public static String SPRITE_CARDBG_FILE = "sprite_cardbg.png";
public static final String FONT_FILE = "font1.ttf";
public static final String SPLASH_BG_FILE = "bg_splash.png";
public static final String MATCH_BG_FILE = "bg_match.jpg";
diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java
index 068adbb1e94..88dd939bcfb 100644
--- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java
+++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java
@@ -720,8 +720,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
final CardView view = CardView.get(card);
return player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblReturnCardToHandConfirm", CardTranslation.getTranslatedName(view.getName())), ability) ? PaymentDecision.card(card) : null;
}
- }
- else {
+ } else {
final CardCollectionView validCards = CardLists.getValidCards(ability.getActivatingPlayer().getCardsIn(ZoneType.Battlefield),
cost.getType().split(";"), player, source, ability);
@@ -849,7 +848,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
this.validChoices = validCards;
counterType = cType;
- setMessage(Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", "%d", counterType == null ? "any" : counterType.getName(), costPart.getDescriptiveType()));
+ setMessage(Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", "%d", counterType == null ? "any" : counterType.getName().toLowerCase(), costPart.getDescriptiveType()));
}
@Override
@@ -967,7 +966,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
if (maxCounters < cntRemoved) {
return null;
}
- if (!player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", String.valueOf(amount), cost.counter.getName(), CardTranslation.getTranslatedName(source.getName())), ability)) {
+ if (!player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", amount, cost.counter.getName().toLowerCase(), CardTranslation.getTranslatedName(source.getName())), ability)) {
return null;
}
}
diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java
index 0888afa10ac..fc4e8c28fb9 100644
--- a/forge-gui/src/main/java/forge/player/HumanPlay.java
+++ b/forge-gui/src/main/java/forge/player/HumanPlay.java
@@ -753,14 +753,6 @@ public class HumanPlay {
}
}
- Integer replicate = ability.getSVarInt("Replicate");
- if (replicate != null) {
- ManaCost rCost = source.getManaCost();
- for (int i = 0; i < replicate; i++) {
- toPay.addManaCost(rCost);
- }
- }
-
CardCollection cardsToDelve = new CardCollection();
if (isActivatedSa) {
CostAdjustment.adjust(toPay, ability, cardsToDelve, false);
diff --git a/forge-gui/src/main/java/forge/sound/SoundSystem.java b/forge-gui/src/main/java/forge/sound/SoundSystem.java
index 77bd17b7100..d1dcab487c0 100644
--- a/forge-gui/src/main/java/forge/sound/SoundSystem.java
+++ b/forge-gui/src/main/java/forge/sound/SoundSystem.java
@@ -124,8 +124,7 @@ public class SoundSystem {
public void play(final SoundEffectType type, final boolean isSynchronized) {
if (isUsingAltSystem()) {
GuiBase.getInterface().startAltSoundSystem(ForgeConstants.SOUND_DIR + type.getResourceFileName(), isSynchronized);
- }
- else {
+ } else {
final IAudioClip snd = fetchResource(type);
if (!isSynchronized || snd.isDone()) {
snd.play(FModel.getPreferences().getPrefInt(FPref.UI_VOL_SOUNDS)/100f);
diff --git a/forge-gui/src/main/java/forge/util/ImageFetcher.java b/forge-gui/src/main/java/forge/util/ImageFetcher.java
index 4c86431a4b7..b017f1d7ded 100644
--- a/forge-gui/src/main/java/forge/util/ImageFetcher.java
+++ b/forge-gui/src/main/java/forge/util/ImageFetcher.java
@@ -126,6 +126,11 @@ public abstract class ImageFetcher {
if (destFile.exists()) {
// TODO: Figure out why this codepath gets reached.
// Ideally, fetchImage() wouldn't be called if we already have the image.
+ if (prefix.equals(ImageKeys.CARD_PREFIX)) {
+ PaperCard paperCard = ImageUtil.getPaperCardFromImageKey(imageKey);
+ if (paperCard != null)
+ paperCard.hasImage(true);
+ }
return;
}