diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index fa600d9bbce..9d6623523b0 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -153,10 +153,15 @@ public class AiAttackController { } } - /** Choose opponent for AI to attack here. Expand as necessary. */ + /** + * Choose opponent for AI to attack here. Expand as necessary. + * No strategy to secure a second place instead, since Forge has no variant for that + */ public static Player choosePreferredDefenderPlayer(Player ai) { Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range + // TODO connect with evaluateBoardPosition and only fall back to random when no player is the biggest threat by a fair margin + if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players // TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size())); @@ -720,7 +725,7 @@ public class AiAttackController { continue; } boolean mustAttack = false; - // TODO for nextTurn check if it was temporary + // TODO this might result into attacking the wrong player if (attacker.isGoaded()) { mustAttack = true; } else if (attacker.getSVar("MustAttack").equals("True")) { @@ -737,7 +742,7 @@ public class AiAttackController { mustAttack = true; } } - if (mustAttack || (attacker.getController().getMustAttackEntity() != null && nextTurn) || (attacker.getController().getMustAttackEntityThisTurn() != null && !nextTurn)) { + if (mustAttack ||attacker.getController().getMustAttackEntityThisTurn() != null) { combat.addAttacker(attacker, defender); attackersLeft.remove(attacker); numForcedAttackers++; diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java index 5f37ddf5716..83dac98417b 100644 --- a/forge-ai/src/main/java/forge/ai/AiBlockController.java +++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java @@ -426,7 +426,6 @@ public class AiBlockController { } attackersLeft = new ArrayList<>(currentAttackers); - currentAttackers = new ArrayList<>(attackersLeft); boolean considerTripleBlock = true; @@ -437,6 +436,11 @@ public class AiBlockController { continue; } + // AI can't handle good blocks with more than three creatures yet + if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) { + continue; + } + int evalAttackerValue = ComputerUtilCard.evaluateCreature(attacker); blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); @@ -446,11 +450,6 @@ public class AiBlockController { int currentValue; // The value of the creatures in the blockgang boolean foundDoubleBlock = false; // if true, a good double block is found - // AI can't handle good blocks with more than three creatures yet - if (CombatUtil.getMinNumBlockersForAttacker(attacker, ai) > (considerTripleBlock ? 3 : 2)) { - continue; - } - // Try to add blockers that could be destroyed, but are worth less than the attacker // Don't use blockers without First Strike or Double Strike if attacker has it usableBlockers = CardLists.filter(blockers, new Predicate() { @@ -460,8 +459,7 @@ public class AiBlockController { && !ComputerUtilCombat.dealsFirstStrikeDamage(c, false, combat)) { return false; } - final boolean randomTrade = wouldLikeToRandomlyTrade(attacker, c, combat); - return lifeInDanger || randomTrade || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker); + return lifeInDanger || wouldLikeToRandomlyTrade(attacker, c, combat) || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker); } }); if (usableBlockers.size() < 2) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index c144ca7c8ed..d4c79223fc0 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1343,7 +1343,7 @@ public class ComputerUtil { } final CardCollection typeList = - CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), source.getController(), source, sa); + CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type, source.getController(), source, sa); for (Card c : typeList) { if (c.getSVar("SacMe").equals("6")) { return true; @@ -1620,7 +1620,7 @@ public class ComputerUtil { objects = AbilityUtils.getDefinedObjects(source, topStack.getParam("Defined"), topStack); } else if (topStack.hasParam("ValidCards")) { CardCollectionView battleField = aiPlayer.getCardsIn(ZoneType.Battlefield); - objects = CardLists.getValidCards(battleField, topStack.getParam("ValidCards").split(","), source.getController(), source, topStack); + objects = CardLists.getValidCards(battleField, topStack.getParam("ValidCards"), source.getController(), source, topStack); } else { return threatened; } @@ -2822,8 +2822,6 @@ public class ComputerUtil { pRating /= 5; } - System.out.println("Board position evaluation for " + p + ": " + pRating); - if (pRating > bestBoardRating) { bestBoardRating = pRating; bestBoardPosition = p; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java index b590b3e23a3..8766eff1f05 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java @@ -36,7 +36,7 @@ public class ComputerUtilAbility { public boolean apply(final Card c) { if (!c.getSVar("NeedsToPlay").isEmpty()) { final String needsToPlay = c.getSVar("NeedsToPlay"); - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay.split(","), c.getController(), c, null); + CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), needsToPlay, c.getController(), c, null); if (list.isEmpty()) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 6043bfd27f0..eae57125e27 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1951,7 +1951,7 @@ public class ComputerUtilCard { CardCollectionView list = game.getCardsIn(ZoneType.Battlefield); - list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, sa); + list = CardLists.getValidCards(list, needsToPlay, card.getController(), card, sa); if (list.isEmpty()) { return AiPlayDecision.MissingNeededCards; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index acb2554c4de..5131f6968b6 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -830,7 +830,7 @@ public class ComputerUtilCombat { } // defender == null means unblocked - if ((defender == null) && mode == TriggerType.AttackerUnblocked) { + if (defender == null && mode == TriggerType.AttackerUnblocked) { willTrigger = true; if (!trigger.matchesValidParam("ValidCard", attacker)) { return false; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 756926fe490..847f2ee12f5 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -144,7 +144,7 @@ public class ComputerUtilCost { return true; } } - final CardCollection typeList = CardLists.getValidCards(hand, type.split(","), source.getController(), source, sa); + final CardCollection typeList = CardLists.getValidCards(hand, type, source.getController(), source, sa); if (typeList.size() > ai.getMaxHandSize()) { continue; } diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java index 49876c64fb4..0272d980cf6 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java @@ -69,14 +69,14 @@ public class AnimateAi extends SpellAbilityAi { final String valid = topStack.getParamOrDefault("SacValid", "Card.Self"); String num = topStack.getParamOrDefault("Amount", "1"); final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack); - CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), + CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, ai.getWeakestOpponent(), topStack.getHostCard(), topStack); list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true)); ComputerUtilCard.sortByEvaluateCreature(list); if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai, sa.isTrigger())) { Card animatedCopy = becomeAnimated(source, sa); list.add(animatedCopy); - list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(), + list = CardLists.getValidCards(list, valid, ai.getWeakestOpponent(), topStack.getHostCard(), topStack); list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true)); if (ComputerUtilCard.evaluateCreature(animatedCopy) < ComputerUtilCard.evaluateCreature(list.get(0)) diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java index 3690b981f2c..ad73c11051c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java @@ -184,7 +184,7 @@ public class ChooseCardAi extends SpellAbilityAi { choice = ComputerUtilCard.getBestCreatureAI(options); } else if (logic.equals("Clone")) { final String filter = "Permanent.YouDontCtrl,Permanent.nonLegendary"; - CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); + CardCollection newOptions = CardLists.getValidCards(options, filter, ctrl, host, sa); if (!newOptions.isEmpty()) { options = newOptions; } @@ -194,7 +194,7 @@ public class ChooseCardAi extends SpellAbilityAi { choice = Aggregates.random(options); } else if (logic.equals("Untap")) { final String filter = "Permanent.YouCtrl,Permanent.tapped"; - CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); + CardCollection newOptions = CardLists.getValidCards(options, filter, ctrl, host, sa); if (!newOptions.isEmpty()) { options = newOptions; } diff --git a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java index d9a7070f29c..4a719fa2ea5 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java @@ -198,7 +198,7 @@ public class CloneAi extends SpellAbilityAi { filter = filter.replace(".nonLegendary+", ".").replace(".nonLegendary", ""); } - CardCollection newOptions = CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); + CardCollection newOptions = CardLists.getValidCards(options, filter, ctrl, host, sa); if (!newOptions.isEmpty()) { options = newOptions; } diff --git a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java index f74fa8115ed..990a5596af0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java @@ -248,7 +248,7 @@ public class CopyPermanentAi extends SpellAbilityAi { final boolean canCopyLegendary = sa.hasParam("NonLegendary"); final String filter = canCopyLegendary ? "Permanent" : "Permanent.YouDontCtrl,Permanent.nonLegendary"; // TODO add filter to not select Legendary from Other Player when ai already have a Legendary with that name - return CardLists.getValidCards(options, filter.split(","), ctrl, host, sa); + return CardLists.getValidCards(options, filter, ctrl, host, sa); } @Override diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersAi.java index 9dcc8a43597..7be51bdecf7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersAi.java @@ -101,6 +101,7 @@ public abstract class CountersAi extends SpellAbilityAi { Card choice = null; if (type.equals("P1P1")) { + // TODO look for modified choice = ComputerUtilCard.getBestCreatureAI(list); if (choice == null) { diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java index 56e2bfcd06b..1396fc3a5d3 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java @@ -251,7 +251,7 @@ public class DamageAllAi extends SpellAbilityAi { // TODO: X may be something different than X paid CardCollection list = - CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC.split(","), source.getController(), source, sa); + CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validC, source.getController(), source, sa); final Predicate filterKillable = new Predicate() { @Override diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java index c21be6ff6f7..a4cd917937a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java @@ -92,8 +92,8 @@ public class DestroyAllAi extends SpellAbilityAi { // TODO should probably sort results when targeted to use on biggest threat instead of first match for (Player opponent: ai.getOpponents()) { - CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa); - CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa); + CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa); + CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa); opplist = CardLists.filter(opplist, predicate); ailist = CardLists.filter(ailist, predicate); diff --git a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java index 8d09f66e886..3bbed335861 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java @@ -68,7 +68,7 @@ public class DigUntilAi extends SpellAbilityAi { } else { if (sa.hasParam("Valid")) { final String valid = sa.getParam("Valid"); - if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid.split(","), source.getController(), source, sa).isEmpty()) { + if (CardLists.getValidCards(ai.getCardsIn(ZoneType.Library), valid, source.getController(), source, sa).isEmpty()) { return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/RegenerateAllAi.java b/forge-ai/src/main/java/forge/ai/ability/RegenerateAllAi.java index f4add7b107e..909fe257786 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RegenerateAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RegenerateAllAi.java @@ -29,7 +29,7 @@ public class RegenerateAllAi extends SpellAbilityAi { final String valid = sa.getParamOrDefault("ValidCards", ""); CardCollectionView list = game.getCardsIn(ZoneType.Battlefield); - list = CardLists.getValidCards(list, valid.split(","), hostCard.getController(), hostCard, sa); + list = CardLists.getValidCards(list, valid, hostCard.getController(), hostCard, sa); list = CardLists.filter(list, CardPredicates.isController(ai)); if (list.size() == 0) { diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java index 3cf0cb67bcc..87754161cd9 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java @@ -79,7 +79,7 @@ public class SacrificeAi extends SpellAbilityAi { List list = null; try { - list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa); + list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); } catch (NullPointerException e) { return false; } finally { @@ -141,7 +141,7 @@ public class SacrificeAi extends SpellAbilityAi { List humanList = null; try { - humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa); + humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); } catch (NullPointerException e) { return false; } finally { @@ -155,7 +155,7 @@ public class SacrificeAi extends SpellAbilityAi { } else if (defined.equals("You")) { List computerList = null; try { - computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa); + computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); } catch (NullPointerException e) { return false; } finally { diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java index 4605604cabc..b1ce0ea688a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java @@ -241,14 +241,14 @@ public class TokenAi extends SpellAbilityAi { final String valid = topStack.getParamOrDefault("SacValid", "Card.Self"); String num = sa.getParamOrDefault("Amount", "1"); final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack); - CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), + CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, ai.getWeakestOpponent(), topStack.getHostCard(), sa); list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true)); // only care about saving single creature for now if (!list.isEmpty() && nTokens > 0 && list.size() == nToSac) { ComputerUtilCard.sortByEvaluateCreature(list); list.add(token); - list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(), sa); + list = CardLists.getValidCards(list, valid, ai.getWeakestOpponent(), topStack.getHostCard(), sa); list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true)); return ComputerUtilCard.evaluateCreature(token) < ComputerUtilCard.evaluateCreature(list.get(0)) && list.contains(token); diff --git a/forge-ai/src/main/java/forge/ai/ability/UntapAllAi.java b/forge-ai/src/main/java/forge/ai/ability/UntapAllAi.java index 97d70b3fafa..1535f2a2a23 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UntapAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UntapAllAi.java @@ -26,7 +26,7 @@ public class UntapAllAi extends SpellAbilityAi { } CardCollectionView list = CardLists.filter(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.TAPPED); final String valid = sa.getParamOrDefault("ValidCards", ""); - list = CardLists.getValidCards(list, valid.split(","), source.getController(), source, sa); + list = CardLists.getValidCards(list, valid, source.getController(), source, sa); // don't untap if only opponent benefits PlayerCollection goodControllers = aiPlayer.getAllies(); goodControllers.add(aiPlayer); @@ -43,7 +43,7 @@ public class UntapAllAi extends SpellAbilityAi { if (sa.hasParam("ValidCards")) { String valid = sa.getParam("ValidCards"); CardCollectionView list = CardLists.filter(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.TAPPED); - list = CardLists.getValidCards(list, valid.split(","), source.getController(), source, sa); + list = CardLists.getValidCards(list, valid, source.getController(), source, sa); return mandatory || !list.isEmpty(); } diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index d13706a086f..52b6b9722bc 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -368,7 +368,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, list.addAll(p.getCardsIn(presentZone)); } } - list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), this); + list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(), this.getHostCard(), this); final String rightString = presentCompare.substring(2); int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this); @@ -397,7 +397,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, } } - list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), this); + list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(), this.getHostCard(), this); final String rightString = presentCompare.substring(2); int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this); 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 b497023ff16..4426492e8c5 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -346,7 +346,7 @@ public class AbilityUtils { candidates = game.getCardsIn(ZoneType.smartValueOf(zone)); validDefined = s[1]; } - cards.addAll(CardLists.getValidCards(candidates, validDefined.split(","), hostCard.getController(), hostCard, sa)); + cards.addAll(CardLists.getValidCards(candidates, validDefined, hostCard.getController(), hostCard, sa)); return cards; } else { CardCollection list = null; @@ -977,7 +977,7 @@ public class AbilityUtils { String var = sa.getParam("AbilityCount"); valid = TextUtil.fastReplace(valid, var, Integer.toString(calculateAmount(source, var, sa))); } - return CardLists.getValidCards(list, valid.split(","), sa.getActivatingPlayer(), source, sa); + return CardLists.getValidCards(list, valid, sa.getActivatingPlayer(), source, sa); } /** @@ -1884,7 +1884,7 @@ public class AbilityUtils { return doXMath(0, expr, c, ctb); } } - list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa); + list = CardLists.getValidCards(list, k[1], sa.getActivatingPlayer(), c, sa); if (k[0].contains("TotalToughness")) { return doXMath(Aggregates.sum(list, CardPredicates.Accessors.fnGetNetToughness), expr, c, ctb); } @@ -1911,7 +1911,7 @@ public class AbilityUtils { return doXMath(0, expr, c, ctb); } } - list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa); + list = CardLists.getValidCards(list, k[1], sa.getActivatingPlayer(), c, sa); return doXMath(list.size(), expr, c, ctb); } @@ -1940,14 +1940,14 @@ public class AbilityUtils { if (sq[0].startsWith("LastStateBattlefield")) { final String[] k = l[0].split(" "); CardCollection list = new CardCollection(game.getLastStateBattlefield()); - list = CardLists.getValidCards(list, k[1].split(","), player, c, ctb); + list = CardLists.getValidCards(list, k[1], player, c, ctb); return doXMath(list.size(), expr, c, ctb); } if (sq[0].startsWith("LastStateGraveyard")) { final String[] k = l[0].split(" "); CardCollection list = new CardCollection(game.getLastStateGraveyard()); - list = CardLists.getValidCards(list, k[1].split(","), player, c, ctb); + list = CardLists.getValidCards(list, k[1], player, c, ctb); return doXMath(list.size(), expr, c, ctb); } @@ -2176,7 +2176,7 @@ public class AbilityUtils { if (sq[0].startsWith("Devoured")) { final String validDevoured = sq[0].split(" ")[1]; - CardCollection cl = CardLists.getValidCards(c.getDevouredCards(), validDevoured.split(","), player, c, ctb); + CardCollection cl = CardLists.getValidCards(c.getDevouredCards(), validDevoured, player, c, ctb); return doXMath(cl.size(), expr, c, ctb); } @@ -2451,8 +2451,7 @@ public class AbilityUtils { if (sq[0].startsWith("ColorsCtrl")) { final String restriction = l[0].substring(11); - final String[] rest = restriction.split(","); - final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); byte n = 0; for (final Card card : list) { n |= card.getColor().getColor(); @@ -2711,8 +2710,7 @@ public class AbilityUtils { // Count$SumPower_valid if (sq[0].startsWith("SumPower")) { final String[] restrictions = l[0].split("_"); - final String[] rest = restrictions[1].split(","); - CardCollection filteredCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + CardCollection filteredCards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restrictions[1], player, c, ctb); return doXMath(Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetNetPower), expr, c, ctb); } @@ -2723,9 +2721,8 @@ public class AbilityUtils { if (sq[0].contains("Graveyard")) zone = ZoneType.Graveyard; final String[] restrictions = l[0].split("_"); - final String[] rest = restrictions[1].split(","); CardCollectionView cardsonbattlefield = game.getCardsIn(zone); - CardCollection filteredCards = CardLists.getValidCards(cardsonbattlefield, rest, player, c, ctb); + CardCollection filteredCards = CardLists.getValidCards(cardsonbattlefield, restrictions[1], player, c, ctb); return Aggregates.sum(filteredCards, CardPredicates.Accessors.fnGetCmc); } @@ -2821,8 +2818,7 @@ public class AbilityUtils { if (sq[0].startsWith("GreatestToughness_")) { final String restriction = l[0].substring(18); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); int highest = 0; for (final Card crd : list) { if (crd.getNetToughness() > highest) { @@ -2834,8 +2830,7 @@ public class AbilityUtils { if (sq[0].startsWith("HighestCMC_")) { final String restriction = l[0].substring(11); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(game.getCardsInGame(), rest, player, c, ctb); + CardCollection list = CardLists.getValidCards(game.getCardsInGame(), restriction, player, c, ctb); int highest = 0; for (final Card crd : list) { // dont check for Split card anymore @@ -2874,8 +2869,7 @@ public class AbilityUtils { if (sq[0].startsWith("DifferentCardNames_")) { final List crdname = Lists.newArrayList(); final String restriction = l[0].substring(19); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(game.getCardsInGame(), rest, player, c, ctb); + CardCollection list = CardLists.getValidCards(game.getCardsInGame(), restriction, player, c, ctb); for (final Card card : list) { String name = card.getName(); // CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common @@ -2889,8 +2883,7 @@ public class AbilityUtils { if (sq[0].startsWith("DifferentPower_")) { final List powers = Lists.newArrayList(); final String restriction = l[0].substring(15); - final String[] rest = restriction.split(","); - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); for (final Card card : list) { Integer pow = card.getNetPower(); if (!powers.contains(pow)) { @@ -2915,8 +2908,7 @@ public class AbilityUtils { if (sq[0].startsWith("ColorsCtrl")) { final String restriction = l[0].substring(11); - final String[] rest = restriction.split(","); - final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), rest, player, c, ctb); + final CardCollection list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); byte n = 0; for (final Card card : list) { n |= card.getColor().getColor(); @@ -3417,16 +3409,14 @@ public class AbilityUtils { String[] lparts = l[0].split(" ", 2); final List vZone = ZoneType.listValueOf(lparts[0].split("Valid")[1]); String restrictions = TextUtil.fastReplace(l[0], TextUtil.addSuffix(lparts[0]," "), ""); - final String[] rest = restrictions.split(","); - CardCollection cards = CardLists.getValidCards(game.getCardsIn(vZone), rest, player, source, ctb); + CardCollection cards = CardLists.getValidCards(game.getCardsIn(vZone), restrictions, player, source, ctb); return doXMath(cards.size(), m, source, ctb); } // count valid cards on the battlefield if (l[0].startsWith("Valid ")) { final String restrictions = l[0].substring(6); - final String[] rest = restrictions.split(","); - CardCollection cardsonbattlefield = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), rest, player, source, ctb); + CardCollection cardsonbattlefield = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restrictions, player, source, ctb); return doXMath(cardsonbattlefield.size(), m, source, ctb); } 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 5a0168055f9..efd631c5773 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 @@ -130,7 +130,7 @@ public class AnimateAllEffect extends AnimateEffectBase { list = getTargetPlayers(sa).getCardsIn(ZoneType.Battlefield); } - list = CardLists.getValidCards(list, valid.split(","), host.getController(), host, sa); + list = CardLists.getValidCards(list, valid, host.getController(), host, sa); for (final Card c : list) { doAnimate(c, sa, power, toughness, types, removeTypes, finalColors, diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java index 23caee7e56c..31addb73b60 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java @@ -67,7 +67,7 @@ public class CharmEffect extends SpellAbilityEffect { } else { num = Math.min(AbilityUtils.calculateAmount(source, sa.getParamOrDefault("CharmNum", "1"), sa), list.size()); } - final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParamOrDefault("MinCharmNum", "1"), sa) : num; + final int min = sa.hasParam("MinCharmNum") ? AbilityUtils.calculateAmount(source, sa.getParam("MinCharmNum"), sa) : num; boolean repeat = sa.hasParam("CanRepeatModes"); boolean random = sa.hasParam("Random"); @@ -120,7 +120,7 @@ public class CharmEffect extends SpellAbilityEffect { } if (additionalDesc) { - String addDescS = (sa.getParam("AdditionalDescription")); + String addDescS = sa.getParam("AdditionalDescription"); if (optional) { sb.append(". ").append(addDescS.trim()); } else if (addDescS.startsWith(("."))) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java index 414e344ef6f..0af8297d976 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java @@ -132,7 +132,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { } } } - valid = CardLists.getValidCards(valid, type.split(","), chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa); + valid = CardLists.getValidCards(valid, type, chosenSA.getActivatingPlayer(), chosenSA.getHostCard(), sa); Card originalTarget = Iterables.getFirst(getTargetCards(chosenSA), null); valid.remove(originalTarget); 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 bce2405944c..5706e0f1732 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 @@ -209,7 +209,7 @@ public class DigEffect extends SpellAbilityEffect { if (changeValid.contains("ChosenType")) { changeValid = changeValid.replace("ChosenType", host.getChosenType()); } - valid = CardLists.getValidCards(top, changeValid.split(","), cont, host, sa); + valid = CardLists.getValidCards(top, changeValid, cont, host, sa); if (totalCMC) { valid = CardLists.getValidCards(valid, "Card.cmcLE" + totcmc, cont, host, sa); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java index e07b925e3ff..f9b9160b9e1 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java @@ -233,7 +233,7 @@ public class DiscardEffect extends SpellAbilityEffect { "X", Integer.toString(AbilityUtils.calculateAmount(source, "X", sa))); } - toBeDiscarded = CardLists.getValidCards(dPHand, valid.split(","), source.getController(), source, sa); + toBeDiscarded = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa); toBeDiscarded = CardLists.filter(toBeDiscarded, Presets.NON_TOKEN); if (toBeDiscarded.size() > 1) { toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa); @@ -250,8 +250,7 @@ public class DiscardEffect extends SpellAbilityEffect { } final String valid = sa.getParamOrDefault("DiscardValid", "Card"); - String[] dValid = valid.split(","); - CardCollection validCards = CardLists.getValidCards(dPHand, dValid, source.getController(), source, sa); + CardCollection validCards = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa); Player chooser = p; if (mode.equals("RevealYouChoose")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 23e4601b293..5befcfaad0b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -25,6 +25,9 @@ import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardFactoryUtil; import forge.game.cost.Cost; +import forge.game.cost.CostDiscard; +import forge.game.cost.CostPart; +import forge.game.cost.CostReveal; import forge.game.mana.ManaCostBeingPaid; import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; @@ -70,7 +73,7 @@ public class PlayEffect extends SpellAbilityEffect { Player controlledByPlayer = null; long controlledByTimeStamp = -1; final Game game = activator.getGame(); - final boolean optional = sa.hasParam("Optional"); + boolean optional = sa.hasParam("Optional"); boolean remember = sa.hasParam("RememberPlayed"); int amount = 1; boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC"); @@ -330,7 +333,17 @@ public class PlayEffect extends SpellAbilityEffect { } if (!optional) { - tgtSA.getPayCosts().setMandatory(true); + // 118.8c + for (CostPart cost : tgtSA.getPayCosts().getCostParts()) { + if ((cost instanceof CostDiscard || cost instanceof CostReveal) + && !cost.getType().equals("Card") && !cost.getType().equals("Random")) { + optional = true; + break; + } + } + if (!optional) { + tgtSA.getPayCosts().setMandatory(true); + } } if (sa.hasParam("PlayReduceCost")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java index f936eb2e662..16587a8e95b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RegenerateAllEffect.java @@ -29,7 +29,7 @@ public class RegenerateAllEffect extends RegenerateBaseEffect { final String valid = sa.getParamOrDefault("ValidCards", ""); CardCollectionView list = game.getCardsIn(ZoneType.Battlefield); - list = CardLists.getValidCards(list, valid.split(","), hostCard.getController(), hostCard, sa); + list = CardLists.getValidCards(list, valid, hostCard.getController(), hostCard, sa); // create Effect for Regeneration createRegenerationEffect(sa, list); diff --git a/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java index 43cb3018645..ede9d56820f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RepeatEffect.java @@ -84,7 +84,7 @@ public class RepeatEffect extends SpellAbilityEffect { } else { list = game.getCardsIn(ZoneType.Battlefield); } - list = CardLists.getValidCards(list, repeatPresent.split(","), sa.getActivatingPlayer(), sa.getHostCard(), sa); + list = CardLists.getValidCards(list, repeatPresent, sa.getActivatingPlayer(), sa.getHostCard(), sa); final String rightString = repeatCompare.substring(2); int right = AbilityUtils.calculateAmount(sa.getHostCard(), rightString, sa); diff --git a/forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java index e3f834be626..c25aae259d5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/UnattachAllEffect.java @@ -140,7 +140,7 @@ public class UnattachAllEffect extends SpellAbilityEffect { String valid = sa.getParam("UnattachValid"); CardCollectionView unattachList = game.getCardsIn(ZoneType.Battlefield); - unattachList = CardLists.getValidCards(unattachList, valid.split(","), source.getController(), source, sa); + unattachList = CardLists.getValidCards(unattachList, valid, source.getController(), source, sa); for (final Card c : unattachList) { handleUnattachment((GameEntity) o, c); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java index 89e5ec12511..da5a181d6b7 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/UntapAllEffect.java @@ -37,7 +37,7 @@ public class UntapAllEffect extends SpellAbilityEffect { } list = list2; } - list = CardLists.getValidCards(list, valid.split(","), card.getController(), card, sa); + list = CardLists.getValidCards(list, valid, card.getController(), card, sa); boolean remember = sa.hasParam("RememberUntapped"); for (Card c : list) { 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 2a8d5d8447b..190218694b2 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3491,6 +3491,9 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } public final boolean isModified() { + if (!isCreature()) { + return false; + } if (this.isEquipped() || this.hasCounters()) { return true; } 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 45f5429b828..014f784fdd6 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1452,7 +1452,7 @@ public class CardFactoryUtil { final String manacost = k[1]; final String abStrReveal = "DB$ Reveal | Defined$ You | RevealDefined$ Self" + " | MiracleCost$ " + manacost; - final String abStrPlay = "DB$ Play | Defined$ Self | PlayCost$ " + manacost; + final String abStrPlay = "DB$ Play | Defined$ Self | Optional$ True | PlayCost$ " + manacost; String revealed = "DB$ ImmediateTrigger | TriggerDescription$ CARDNAME - Miracle"; diff --git a/forge-game/src/main/java/forge/game/card/CardZoneTable.java b/forge-game/src/main/java/forge/game/card/CardZoneTable.java index cea052c8179..f0e9bb8d387 100644 --- a/forge-game/src/main/java/forge/game/card/CardZoneTable.java +++ b/forge-game/src/main/java/forge/game/card/CardZoneTable.java @@ -90,7 +90,7 @@ public class CardZoneTable extends ForwardingTable, Cloneable, Seria public final String getDescriptiveType() { String typeDesc = this.getTypeDescription(); - return typeDesc == null ? this.getType() : typeDesc; + if (typeDesc == null) { + String typeS = this.getType(); + typeDesc = CardType.CoreType.isValidEnum(typeS) ? typeS.toLowerCase() : typeS; + } + return typeDesc; } /** diff --git a/forge-game/src/main/java/forge/game/cost/CostTapType.java b/forge-game/src/main/java/forge/game/cost/CostTapType.java index ea8eda92a58..44c7e80982e 100644 --- a/forge-game/src/main/java/forge/game/cost/CostTapType.java +++ b/forge-game/src/main/java/forge/game/cost/CostTapType.java @@ -17,6 +17,7 @@ */ package forge.game.cost; +import forge.card.CardType; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardLists; @@ -87,20 +88,33 @@ public class CostTapType extends CostPartWithList { @Override public final String toString() { final StringBuilder sb = new StringBuilder(); - sb.append("Tap "); final Integer i = this.convertAmount(); final String desc = this.getDescriptiveType(); final String type = this.getType(); - + + if (type.contains("+withTotalPowerGE")) { + String num = type.split("\\+withTotalPowerGE")[1]; + sb.append("Tap any number of untapped creatures you control other than CARDNAME with total power "); + sb.append(num).append("or greater"); + return sb.toString(); + } + + sb.append("Tap "); if (type.contains("sharesCreatureTypeWith")) { sb.append("two untapped creatures you control that share a creature type"); - } else if (type.contains("+withTotalPowerGE")) { - String num = type.split("\\+withTotalPowerGE")[1]; - sb.append("Tap any number of untapped creatures you control other than CARDNAME with total power ").append(num).append("or greater"); + } else if (type.contains("Other")) { + String rep = type.contains(".Other") ? ".Other" : "+Other"; + String descTrim = desc.replace(rep, ""); + if (CardType.CoreType.isValidEnum(descTrim)) { + descTrim = descTrim.toLowerCase(); + } + sb.append("another untapped ").append(descTrim); + if (!descTrim.contains("you control")) { + sb.append(" you control"); + } } else { - sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "untapped " + desc)); - sb.append(" you control"); + sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "untapped " + desc)).append(" you control"); } return sb.toString(); } 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 ef6f9cbfd19..1ebcd148d82 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -172,7 +172,7 @@ public class AbilityManaPart implements java.io.Serializable { runParams.put(AbilityKey.Activator, root == null ? null : root.getActivatingPlayer()); player.getGame().getTriggerHandler().runTrigger(TriggerType.TapsForMana, runParams, false); - if (source.isLand() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost() ) { + if (source.isLand() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) { player.setTappedLandForManaThisTurn(true); } } // end produceMana(String) diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index e57c1c7be14..edd1fddc002 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -467,7 +467,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone CardCollectionView list = game.getCardsIn(zone); final String present = getParam("IsPresent"); - list = CardLists.getValidCards(list, present.split(","), controller, hostCard, this); + list = CardLists.getValidCards(list, present, controller, hostCard, this); int right = 1; final String rightString = compare.substring(2); 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 62dfd5e99d6..cb0c6956af2 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -1070,7 +1070,7 @@ public final class StaticAbilityContinuous { affectedCardsOriginal = new CardCollection(affectedCards); } - affectedCards = CardLists.getValidCards(affectedCards, stAb.getParam("Affected").split(","), controller, hostCard, stAb); + affectedCards = CardLists.getValidCards(affectedCards, stAb.getParam("Affected"), controller, hostCard, stAb); // Add back all cards that are in other player's graveyard, and meet the restrictions without YouOwn/YouCtrl (treat it as in your graveyard) if (affectedCardsOriginal != null) { diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java b/forge-game/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java index 15682ca3451..d106b193207 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerAttackersDeclared.java @@ -74,7 +74,7 @@ public class TriggerAttackersDeclared extends Trigger { CardCollection attackers = (CardCollection) runParams.get(AbilityKey.Attackers); if (hasParam("ValidAttackers")) { - attackers = CardLists.getValidCards(attackers, getParam("ValidAttackers").split(","), getHostCard().getController(), getHostCard(), this); + attackers = CardLists.getValidCards(attackers, getParam("ValidAttackers"), getHostCard().getController(), getHostCard(), this); FCollection defenders = new FCollection<>(); for (Card attacker : attackers) { defenders.add(attacker.getGame().getCombat().getDefenderByAttacker(attacker)); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java b/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java index 62d7b2f7f6d..02d7abc6527 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerDrawn.java @@ -74,7 +74,7 @@ public class TriggerDrawn extends Trigger { final String sIsPresent = this.getParam("ValidPlayerControls"); final Player p = ((Player)runParams.get(AbilityKey.Player)); CardCollection list = (CardCollection) p.getCardsIn(ZoneType.Battlefield); - list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), + list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(), this.getHostCard(), this); if (list.size() == 0) { return false; diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java b/forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java index e46e0ab46e9..fe7d4d23a29 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerLifeGained.java @@ -64,7 +64,7 @@ public class TriggerLifeGained extends Trigger { final String sIsPresent = this.getParam("ValidPlayerControls"); final Player p = ((Player)runParams.get(AbilityKey.Player)); CardCollection list = (CardCollection) p.getCardsIn(ZoneType.Battlefield); - list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), + list = CardLists.getValidCards(list, sIsPresent, this.getHostCard().getController(), this.getHostCard(), this); if (list.size() == 0) { return false; diff --git a/forge-gui-mobile/src/forge/menu/FDropDown.java b/forge-gui-mobile/src/forge/menu/FDropDown.java index 20da17b4a5d..84ccde8799d 100644 --- a/forge-gui-mobile/src/forge/menu/FDropDown.java +++ b/forge-gui-mobile/src/forge/menu/FDropDown.java @@ -7,6 +7,7 @@ import forge.Graphics; import forge.assets.FSkinColor; import forge.assets.FSkinColor.Colors; import forge.assets.FSkinTexture; +import forge.gui.GuiBase; import forge.screens.FScreen; import forge.toolbox.FContainer; import forge.toolbox.FDisplayObject; @@ -221,19 +222,22 @@ public abstract class FDropDown extends FScrollPane { @Override public boolean pan(float x, float y, float deltaX, float deltaY, boolean moreVertical) { - hide(); //always hide if backdrop panned + if (!GuiBase.isAndroid()) + hide(); //always hide if backdrop panned return false; //allow pan to pass through to object behind backdrop } @Override public boolean fling(float velocityX, float velocityY) { - hide(); //always hide if backdrop flung + if (!GuiBase.isAndroid()) + hide(); //always hide if backdrop flung return false; //allow fling to pass through to object behind backdrop } @Override public boolean zoom(float x, float y, float amount) { - hide(); //always hide if backdrop zoomed + if (!GuiBase.isAndroid()) + hide(); //always hide if backdrop zoomed return false; //allow zoom to pass through to object behind backdrop } diff --git a/forge-gui/res/cardsfolder/b/black_oak_of_odunos.txt b/forge-gui/res/cardsfolder/b/black_oak_of_odunos.txt index 643edf4d5da..a8f88851f5c 100644 --- a/forge-gui/res/cardsfolder/b/black_oak_of_odunos.txt +++ b/forge-gui/res/cardsfolder/b/black_oak_of_odunos.txt @@ -3,5 +3,5 @@ ManaCost:2 B Types:Creature Zombie Treefolk PT:0/5 K:Defender -A:AB$ Pump | Cost$ B tapXType<1/Creature.Other/another creature> | CostDesc$ {B}, Tap another untapped creature you control: | Defined$ Self | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn. +A:AB$ Pump | Cost$ B tapXType<1/Creature.Other> | Defined$ Self | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ CARDNAME gets +1/+1 until end of turn. Oracle:Defender\n{B}, Tap another untapped creature you control: Black Oak of Odunos gets +1/+1 until end of turn. diff --git a/forge-gui/res/cardsfolder/d/dazzling_sphinx.txt b/forge-gui/res/cardsfolder/d/dazzling_sphinx.txt index 09ba0184963..b8e017aaa3d 100644 --- a/forge-gui/res/cardsfolder/d/dazzling_sphinx.txt +++ b/forge-gui/res/cardsfolder/d/dazzling_sphinx.txt @@ -6,6 +6,6 @@ K:Flying T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDigUntil | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order. SVar:TrigDigUntil:DB$ DigUntil | Defined$ TriggeredTarget | Valid$ Instant,Sorcery | ValidDescription$ instant or sorcery | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | RememberRevealed$ True | IsCurse$ True | SubAbility$ DBPlay | SpellDescription$ Whenever CARDNAME deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order. SVar:DBPlay:DB$ Play | Defined$ Remembered | ValidZone$ Exile | Valid$ Instant.IsRemembered,Sorcery.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | RememberObjects$ Remembered | Optional$ True | ForgetTargetRemembered$ True | SubAbility$ DBRestRandomOrder -SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup +SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Exile | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Flying\nWhenever Dazzling Sphinx deals combat damage to a player, that player exiles cards from the top of their library until they exile an instant or sorcery card. You may cast that card without paying its mana cost. Then that player puts the exiled cards that weren't cast this way on the bottom of their library in a random order. diff --git a/forge-gui/res/cardsfolder/k/kumena_tyrant_of_orazca.txt b/forge-gui/res/cardsfolder/k/kumena_tyrant_of_orazca.txt index 3b59592d351..9c4cd3ceb58 100644 --- a/forge-gui/res/cardsfolder/k/kumena_tyrant_of_orazca.txt +++ b/forge-gui/res/cardsfolder/k/kumena_tyrant_of_orazca.txt @@ -2,9 +2,10 @@ Name:Kumena, Tyrant of Orazca ManaCost:1 G U Types:Legendary Creature Merfolk Shaman PT:2/4 -A:AB$ Pump | Cost$ tapXType<1/Merfolk.Other> | CostDesc$ Tap another untapped Merfolk you control: | Defined$ Self | KW$ HIDDEN Unblockable | AILogic$ BeforeCombat | SpellDescription$ CARDNAME can't be blocked this turn. -A:AB$ Draw | Cost$ tapXType<3/Merfolk> | CostDesc$ Tap three untapped Merfolk you control: | NumCards$ 1 | AILogic$ AtOppEOT | SpellDescription$ Draw a card. -A:AB$ PutCounterAll | Cost$ tapXType<5/Merfolk> | CostDesc$ Tap five untapped Merfolk you control: | ValidCards$ Merfolk.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | AILogic$ AtOppEOT | SpellDescription$ Put a +1/+1 counter on each Merfolk you control. +A:AB$ Pump | Cost$ tapXType<1/Merfolk.Other> | Defined$ Self | KW$ HIDDEN Unblockable | AILogic$ BeforeCombat | SpellDescription$ CARDNAME can't be blocked this turn. +A:AB$ Draw | Cost$ tapXType<3/Merfolk> | NumCards$ 1 | AILogic$ AtOppEOT | SpellDescription$ Draw a card. +A:AB$ PutCounterAll | Cost$ tapXType<5/Merfolk> | ValidCards$ Merfolk.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | AILogic$ AtOppEOT | SpellDescription$ Put a +1/+1 counter on each Merfolk you control. DeckHints:Type$Merfolk SVar:BuffedBy:Merfolk +DeckHas:Ability$Counters Oracle:Tap another untapped Merfolk you control: Kumena, Tyrant of Orazca can't be blocked this turn.\nTap three untapped Merfolk you control: Draw a card.\nTap five untapped Merfolk you control: Put a +1/+1 counter on each Merfolk you control. diff --git a/forge-gui/res/cardsfolder/r/radiant_serra_archangel.txt b/forge-gui/res/cardsfolder/r/radiant_serra_archangel.txt index 3bdacf1b69a..bfb04b92f43 100644 --- a/forge-gui/res/cardsfolder/r/radiant_serra_archangel.txt +++ b/forge-gui/res/cardsfolder/r/radiant_serra_archangel.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Angel PT:6/4 K:Flying K:Partner -A:AB$ Protection | Cost$ tapXType<1/Creature.untapped+withFlying+Other/another creature you control> | CostDesc$ Tap another untapped creature you control with flying: | Gains$ Choice | Choices$ AnyColor | SpellDescription$ CARDNAME gains protection from the color of your choice until end of turn. +A:AB$ Protection | Cost$ tapXType<1/Creature.withFlying+Other/creature you control with flying> | Gains$ Choice | Choices$ AnyColor | SpellDescription$ CARDNAME gains protection from the color of your choice until end of turn. SVar:BuffedBy:Creature.withFlying DeckNeeds:Keyword$Flying Oracle:Flying\nTap another untapped creature you control with flying: Radiant, Serra Archangel gains protection from the color of your choice until end of turn.\nPartner (You can have two commanders if both have partner.) diff --git a/forge-gui/res/cardsfolder/r/rangers_hawk.txt b/forge-gui/res/cardsfolder/r/rangers_hawk.txt index cd4b90c5194..0475d998cb6 100644 --- a/forge-gui/res/cardsfolder/r/rangers_hawk.txt +++ b/forge-gui/res/cardsfolder/r/rangers_hawk.txt @@ -3,5 +3,5 @@ ManaCost:W Types:Creature Bird PT:1/1 K:Flying -A:AB$ Venture | Cost$ 3 T tapXType<1/Creature/creature> | SorcerySpeed$ True | SpellDescription$ Venture into the dungeon. Activate only as a sorcery. (Enter the first room or advance to the next room.) +A:AB$ Venture | Cost$ 3 T tapXType<1/Creature.Other> | SorcerySpeed$ True | SpellDescription$ Venture into the dungeon. Activate only as a sorcery. (Enter the first room or advance to the next room.) Oracle:Flying\n{3}, {T}, Tap another untapped creature you control: Venture into the dungeon. Activate only as a sorcery. (Enter the first room or advance to the next room.) diff --git a/forge-gui/res/cardsfolder/s/shadow_stinger.txt b/forge-gui/res/cardsfolder/s/shadow_stinger.txt index 8f72805b53e..40abf26ec0a 100644 --- a/forge-gui/res/cardsfolder/s/shadow_stinger.txt +++ b/forge-gui/res/cardsfolder/s/shadow_stinger.txt @@ -2,7 +2,7 @@ Name:Shadow Stinger ManaCost:2 B Types:Creature Vampire Rogue PT:1/4 -A:AB$ Pump | Cost$ tapXType<1/Rogue> | CostDesc$ Tap another untapped Rogue you control: | Defined$ Self | KW$ Deathtouch | SpellDescription$ CARDNAME gains deathtouch until end of turn. +A:AB$ Pump | Cost$ tapXType<1/Rogue> | Defined$ Self | KW$ Deathtouch | SpellDescription$ CARDNAME gains deathtouch until end of turn. T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigMill | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player mills three cards. (They put the top three cards of their library into their graveyard.) SVar:TrigMill:DB$ Mill | Defined$ TriggeredTarget | NumCards$ 3 DeckHas:Ability$Mill diff --git a/forge-gui/res/cardsfolder/s/sure_footed_infiltrator.txt b/forge-gui/res/cardsfolder/s/sure_footed_infiltrator.txt index 0e20a4aa378..2a372b10d3a 100644 --- a/forge-gui/res/cardsfolder/s/sure_footed_infiltrator.txt +++ b/forge-gui/res/cardsfolder/s/sure_footed_infiltrator.txt @@ -2,7 +2,7 @@ Name:Sure-Footed Infiltrator ManaCost:3 U Types:Creature Merfolk Rogue PT:2/3 -A:AB$ Pump | Cost$ tapXType<1/Rogue.Other> | CostDesc$ Tap another untapped Rogue you control: | Defined$ Self | KW$ HIDDEN Unblockable | StackDescription$ SpellDescription | SpellDescription$ CARDNAME can't be blocked this turn. +A:AB$ Pump | Cost$ tapXType<1/Rogue.Other> | Defined$ Self | KW$ HIDDEN Unblockable | StackDescription$ SpellDescription | SpellDescription$ CARDNAME can't be blocked this turn. T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, draw a card. SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 DeckNeeds:Type$Rogue diff --git a/forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt b/forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt new file mode 100644 index 00000000000..56391e119e5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/iron_apprentice.txt @@ -0,0 +1,9 @@ +Name:Iron Apprentice +ManaCost:1 +Types:Artifact Creature Construct +PT:0/0 +K:etbCounter:P1P1:1 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+HasCounters | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME dies, if it had counters on it, put those counters on target creature you control. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ EachFromSource | EachFromSource$ TriggeredCardLKICopy +DeckHas:Ability$Counters +Oracle:Iron Apprentice enters the battlefield with a +1/+1 counter on it.\nWhen Iron Apprentice dies, if it had counters on it, put those counters on target creature you control. diff --git a/forge-gui/res/cardsfolder/upcoming/papercraft_decoy.txt b/forge-gui/res/cardsfolder/upcoming/papercraft_decoy.txt new file mode 100644 index 00000000000..1c2d3930744 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/papercraft_decoy.txt @@ -0,0 +1,7 @@ +Name:Papercraft Decoy +ManaCost:2 +Types:Artifact Creature Frog +PT:2/1 +T:Mode$ ChangesZone | Origin$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw1 | TriggerDescription$ When CARDNAME leaves the battlefield, you may pay {2}. If you do, draw a card. +SVar:TrigDraw1:AB$ Draw | Cost$ 2 | Defined$ You | NumCards$ 1 +Oracle:When Papercraft Decoy leaves the battlefield, you may pay {2}. If you do, draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt b/forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt new file mode 100644 index 00000000000..e5f99e5d535 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/patchwork_automaton.txt @@ -0,0 +1,10 @@ +Name:Patchwork Automaton +ManaCost:2 +Types:Artifact Creature Construct +PT:1/1 +K:Ward:2 +T:Mode$ SpellCast | ValidCard$ Artifact | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast an artifact spell, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +DeckHas:Ability$Counters +DeckHints:Type$Artifact +Oracle:Ward {2} (Whenever this creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {2}.)\nWhenever you cast an artifact spell, put a +1/+1 counter on Patchwork Automaton. diff --git a/forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt b/forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt new file mode 100644 index 00000000000..fb5b0e0b55e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/reito_sentinel.txt @@ -0,0 +1,10 @@ +Name:Reito Sentinel +ManaCost:3 +Types:Artifact Creature Construct +PT:3/3 +K:Defender +T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME enters the battlefield, target player mills three cards. +SVar:TrigMill:DB$ Mill | NumCards$ 3 | ValidTgts$ Player | TgtPrompt$ Select target player +A:AB$ ChangeZone | Cost$ 3 | ValidTgts$ Card | TgtPrompt$ Select target card in a graveyard | Origin$ Graveyard | Destination$ Library | LibraryPosition$ -1 | SpellDescription$ Put target card from a graveyard on the bottom of its owner's library. +DeckHas:Ability$Mill|Graveyard +Oracle:Defender\nWhen Reito Sentinel enters the battlefield, target player mills three cards. (They put the top three cards of their library into their graveyard.)\n{3}: Put target card from a graveyard on the bottom of its owner's library. diff --git a/forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt b/forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt new file mode 100644 index 00000000000..11f3ac80e8a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/searchlight_companion.txt @@ -0,0 +1,9 @@ +Name:Searchlight Companion +ManaCost:3 +Types:Artifact Creature Drone +PT:1/1 +K:Flying +T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create a 1/1 colorless Spirit creature token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_1_1_spirit +DeckHas:Ability$Token & Type$Spirit +Oracle:Flying\nWhen Searchlight Companion enters the battlefield, create a 1/1 colorless Spirit creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/shrine_steward.txt b/forge-gui/res/cardsfolder/upcoming/shrine_steward.txt new file mode 100644 index 00000000000..0dab6c9f5ea --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shrine_steward.txt @@ -0,0 +1,8 @@ +Name:Shrine Steward +ManaCost:5 +Types:Artifact Creature Construct +PT:3/2 +T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may search your library for an Aura or Shrine card, reveal it, put it into your hand, then shuffle. +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Aura,Shrine | ChangeNum$ 1 | ChangeTypeDesc$ Aura or Shrine card | ShuffleNonMandatory$ True +DeckNeeds:Type$Aura|Shrine +Oracle:When Shrine Steward enters the battlefield, you may search your library for an Aura or Shrine card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/upcoming/thundersteel_colossus.txt b/forge-gui/res/cardsfolder/upcoming/thundersteel_colossus.txt new file mode 100644 index 00000000000..0d276413a99 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/thundersteel_colossus.txt @@ -0,0 +1,8 @@ +Name:Thundersteel Colossus +ManaCost:7 +Types:Artifact Vehicle +PT:7/7 +K:Trample +K:Haste +K:Crew:2 +Oracle:Trample, haste\nCrew 2 (Tap any number of creatures you control with total power 2 or more: This Vehicle becomes an artifact creature until end of turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt b/forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt new file mode 100644 index 00000000000..5dde7a6fa14 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/towashi_guide_bot.txt @@ -0,0 +1,11 @@ +Name:Towashi Guide-Bot +ManaCost:4 +Types:Artifact Creature Construct +PT:3/2 +T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, put a +1/+1 counter on target creature you control. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 1 +A:AB$ Draw | Cost$ 4 T | NumCards$ 1 | ReduceCost$ X | SpellDescription$ Draw a card. This ability costs {1} less to activate for each modified creature you control. +SVar:X:Count$Valid Creature.modified+YouCtrl +DeckHas:Ability$Counters +DeckHints:Type$Aura|Equipment & Ability$Counters +Oracle:When Towashi Guide-Bot enters the battlefield, put a +1/+1 counter on target creature you control.\n{4}, {T}: Draw a card. This ability costs {1} less to activate for each modified creature you control. (Equipment, Auras you control, and counters are modifications.) diff --git a/forge-gui/res/cardsfolder/v/veteran_warleader.txt b/forge-gui/res/cardsfolder/v/veteran_warleader.txt index 969b2e0c112..9f2b1eb80cb 100644 --- a/forge-gui/res/cardsfolder/v/veteran_warleader.txt +++ b/forge-gui/res/cardsfolder/v/veteran_warleader.txt @@ -4,7 +4,7 @@ Types:Creature Human Soldier Ally PT:*/* S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of creatures you control. SVar:X:Count$Valid Creature.YouCtrl -A:AB$ GenericChoice | Cost$ tapXType<1/Ally.Other> | CostDesc$ Tap another untapped Ally you control: | Choices$ ChooseFirstStrike,ChooseVigilance,ChooseTrample | SpellDescription$ CARDNAME gains your choice of first strike, vigilance, or trample until end of turn. +A:AB$ GenericChoice | Cost$ tapXType<1/Ally.Other> | Choices$ ChooseFirstStrike,ChooseVigilance,ChooseTrample | SpellDescription$ CARDNAME gains your choice of first strike, vigilance, or trample until end of turn. SVar:ChooseFirstStrike:DB$ Pump | Defined$ Self | KW$ First Strike | SpellDescription$ CARDNAME gains first strike until end of turn. SVar:ChooseVigilance:DB$ Pump | Defined$ Self | KW$ Vigilance | SpellDescription$ CARDNAME gains vigilance until end of turn. SVar:ChooseTrample:DB$ Pump | Defined$ Self | KW$ Trample | SpellDescription$ CARDNAME gains trample until end of turn. diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index a61c9244ba8..0ff244777f4 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -463,6 +463,7 @@ ScryfallCode=SLD 588 R Sphere of Safety @Johannes Voss 589 R Arcane Signet @Dan Frazier 591 R Crash Through @Tyler Walpole +596 R Persistent Petitioners @Crom 597 R Persistent Petitioners @Death Burger 598 R Persistent Petitioners @Feifei Ruan 603 M Eldrazi Monument @Cosmin Podar diff --git a/forge-gui/res/editions/Year of the Tiger 2022.txt b/forge-gui/res/editions/Year of the Tiger 2022.txt new file mode 100644 index 00000000000..6997068ab13 --- /dev/null +++ b/forge-gui/res/editions/Year of the Tiger 2022.txt @@ -0,0 +1,13 @@ +[metadata] +Code=PL22 +Date=2022-02-25 +Name=Year of the Tiger 2022 +Type=Promo +ScryfallCode=PL22 + +[cards] +1 R Temur Sabertooth @tswck +2 R Jedit Ojanen @ +3 M Yuriko, the Tiger's Shadow @ +4 M Snapdax, Apex of the Hunt @ +5 R Herald's Horn @tswck diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 58e3e5e5919..54886c64129 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -760,7 +760,8 @@ public class HumanCostDecision extends CostDecisionMakerBase { if (num == 0) { return PaymentDecision.number(0); } - if (hand.size() == num) { + // player might not want to pay if from a trigger + if (!ability.hasSVar("IsCastFromPlayEffect") && hand.size() == num) { return PaymentDecision.card(hand); } diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 39c02c9a9bb..1a7b26f5b8f 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -472,7 +472,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont return null; } - String announceTitle = ("X".equals(announce)) ? announce : ability.getParamOrDefault("AnnounceTitle", announce); + String announceTitle = "X".equals(announce) ? announce : ability.getParamOrDefault("AnnounceTitle", announce); if (cost.isMandatory()) { return chooseNumber(ability, localizer.getMessage("lblChooseAnnounceForCard", announceTitle, CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max);