From 20bc46c0fd54d3dadc2ae590c128030f0e2b2976 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 25 Jul 2021 23:29:38 +0200 Subject: [PATCH 1/5] Correctly evaluate modal faces --- forge-ai/src/main/java/forge/ai/AiController.java | 8 ++++++++ forge-game/src/main/java/forge/game/card/Card.java | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 5ea2c1be35e..0ba68b4b601 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -750,7 +750,15 @@ public class AiController { return AiPlayDecision.CantAfford; } + // state needs to be switched here so API checks evaluate the right face + if (sa.getCardState().getStateName() == CardStateName.Modal) { + sa.getHostCard().setState(CardStateName.Modal, false); + } AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc. + if (sa.getCardState().getStateName() == CardStateName.Modal) { + sa.getHostCard().setState(CardStateName.Original, false); + } + if (canPlay != AiPlayDecision.WillPlay) { return canPlay; } 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 f6f8c452aa2..907997b3d9d 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -6342,7 +6342,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } if (isFaceDown() && isInZone(ZoneType.Exile)) { - for (final SpellAbility sa : getState(CardStateName.Original).getSpellAbilities()) { + for (final SpellAbility sa : oState.getSpellAbilities()) { abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player)); } } From 91e8c24769df3f87d54d3ddc2d32271a0c7401ee Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 26 Jul 2021 19:11:40 +0200 Subject: [PATCH 2/5] Clean up --- forge-ai/src/main/java/forge/ai/AiController.java | 3 +-- forge-ai/src/main/java/forge/ai/ComputerUtil.java | 4 +--- .../src/main/java/forge/ai/ComputerUtilCost.java | 2 +- forge-ai/src/main/java/forge/ai/SpecialCardAi.java | 2 +- .../src/main/java/forge/ai/ability/EffectAi.java | 1 - .../src/main/java/forge/ai/ability/ManaEffectAi.java | 2 +- .../forge/ai/ability/RearrangeTopOfLibraryAi.java | 2 +- forge-core/src/main/java/forge/card/CardRules.java | 2 +- .../src/main/java/forge/card/mana/ManaCost.java | 2 +- .../game/ability/effects/CopySpellAbilityEffect.java | 1 - .../forge/game/ability/effects/RollDiceEffect.java | 1 + .../java/forge/game/spellability/AbilitySub.java | 4 ---- .../src/main/java/forge/itemmanager/ColumnDef.java | 12 ++++++------ .../achievements/AchievementCollection.java | 4 ++-- .../achievements/AltWinAchievements.java | 2 +- .../achievements/ChallengeAchievements.java | 2 +- .../achievements/PlaneswalkerAchievements.java | 2 +- .../achievements/PuzzleAchievements.java | 2 +- 18 files changed, 21 insertions(+), 29 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 0ba68b4b601..02697597d23 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -818,8 +818,7 @@ public class AiController { if (!canPlay) { return AiPlayDecision.CantPlayAi; } - } - else { + } else { Cost payCosts = sa.getPayCosts(); if (payCosts != null) { ManaCost mana = payCosts.getTotalMana(); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 6acf36772da..d32aa26b545 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2913,9 +2913,7 @@ public class ComputerUtil { // at this point, we're assuming that card will be castable from whichever zone it's in by the AI player. abTest.setActivatingPlayer(ai); abTest.getRestrictions().setZone(c.getZone().getZoneType()); - final boolean play = AiPlayDecision.WillPlay == aic.canPlaySa(abTest); - final boolean pay = ComputerUtilCost.canPayCost(abTest, ai); - if (play && pay) { + if (AiPlayDecision.WillPlay == aic.canPlaySa(abTest) && ComputerUtilCost.canPayCost(abTest, ai)) { targetSpellCard = c; break; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 0f0d72882c4..409c29cfa94 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -633,7 +633,7 @@ public class ComputerUtilCost { } // Check if the AI intends to play the card and if it can pay for it with the mana it has boolean willPlay = ComputerUtil.hasReasonToPlayCardThisTurn(payer, c); - boolean canPay = c.getManaCost().canBePaidWithAvaliable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor()); + boolean canPay = c.getManaCost().canBePaidWithAvailable(ColorSet.fromNames(getAvailableManaColors(payer, source)).getColor()); return canPay && willPlay; } } diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 501a3ab4a61..0da2f61ca26 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -1099,7 +1099,7 @@ public class SpecialCardAi { for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) { ManaCost cost = testSa.getPayCosts().getTotalMana(); - boolean canPayWithAvailableColors = cost.canBePaidWithAvaliable(ColorSet.fromNames( + boolean canPayWithAvailableColors = cost.canBePaidWithAvailable(ColorSet.fromNames( ComputerUtilCost.getAvailableManaColors(ai, sa.getHostCard())).getColor()); byte colorProfile = cost.getColorProfile(); 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 e643da47300..407f96baa73 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -313,6 +313,5 @@ public class EffectAi extends SpellAbilityAi { } return super.doTriggerAINoCost(aiPlayer, sa, mandatory); - } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java index 0d26dcd0667..35bc3ad2afe 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java @@ -167,7 +167,7 @@ public class ManaEffectAi extends SpellAbilityAi { List all = ComputerUtilAbility.getSpellAbilities(ai.getCardsIn(ZoneType.Hand), ai); for (final SpellAbility testSa : ComputerUtilAbility.getOriginalAndAltCostAbilities(all, ai)) { ManaCost cost = testSa.getPayCosts().getTotalMana(); - boolean canPayWithAvailableColors = cost.canBePaidWithAvaliable(ColorSet.fromNames( + boolean canPayWithAvailableColors = cost.canBePaidWithAvailable(ColorSet.fromNames( ComputerUtilCost.getAvailableManaColors(ai, (List)null)).getColor()); if (cost.getCMC() == 0 && cost.countX() == 0) { diff --git a/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java b/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java index 004591af7a8..b3bd6f61e57 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java @@ -107,7 +107,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi { uncastableCMCThreshold = aic.getIntProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF); } - Player p = pc.getFirst(); // FIXME: is this always a single target spell? + Player p = pc.getFirst(); // currently always a single target spell Card top = p.getCardsIn(ZoneType.Library).getFirst(); int landsOTB = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS_PRODUCING_MANA).size(); int cmc = top.isSplitCard() ? Math.min(top.getCMC(Card.SplitCMCMode.LeftSplitCMC), top.getCMC(Card.SplitCMCMode.RightSplitCMC)) diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index a0d6cc930aa..d062a4a0f0e 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -182,7 +182,7 @@ public final class CardRules implements ICardCharacteristics { //if card face has no cost, assume castable only by mana of its defined color return face.getColor().hasNoColorsExcept(colorCode); } - return face.getManaCost().canBePaidWithAvaliable(colorCode); + return face.getManaCost().canBePaidWithAvailable(colorCode); } public boolean canCastWithAvailable(byte colorCode) { diff --git a/forge-core/src/main/java/forge/card/mana/ManaCost.java b/forge-core/src/main/java/forge/card/mana/ManaCost.java index 34a0387b61b..85ce4c528a7 100644 --- a/forge-core/src/main/java/forge/card/mana/ManaCost.java +++ b/forge-core/src/main/java/forge/card/mana/ManaCost.java @@ -353,7 +353,7 @@ public final class ManaCost implements Comparable, Iterable copies = Lists.newArrayList(); SpellAbility chosenSA = controller.getController().chooseSingleSpellForEffect(tgtSpells, sa, diff --git a/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java index 653f3fc4b4c..01b13539df5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java @@ -121,6 +121,7 @@ public class RollDiceEffect extends SpellAbilityEffect { int total = rollDiceForPlayer(sa, player, amount, sides, ignore, rolls); total += modifier; + total = 20; if (sa.hasParam("ResultSVar")) { host.setSVar(sa.getParam("ResultSVar"), Integer.toString(total)); } diff --git a/forge-game/src/main/java/forge/game/spellability/AbilitySub.java b/forge-game/src/main/java/forge/game/spellability/AbilitySub.java index 1986274237d..13aafddb2f3 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilitySub.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilitySub.java @@ -63,8 +63,6 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab return this.parent; } - - /** {@inheritDoc} */ @Override public boolean canPlay() { @@ -72,10 +70,8 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab return false; } - private final SpellAbilityEffect effect; - public AbilitySub(ApiType api0, final Card ca, final TargetRestrictions tgt, Map params0) { super(ca, Cost.Zero); this.setTargetRestrictions(tgt); diff --git a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java index 54721468484..9dcb3ec2157 100644 --- a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java +++ b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java @@ -697,9 +697,9 @@ public enum ColumnDef { return !(((IPaperCard) i).getRules().getType().isArtifact() && (toColor(i).isColorless() || //If it isn't colorless, see if it can be paid with only white, only blue, only black. //No need to check others since three-color hybrid shards don't exist. - manaCost.canBePaidWithAvaliable(MagicColor.WHITE) && - manaCost.canBePaidWithAvaliable(MagicColor.BLUE) && - manaCost.canBePaidWithAvaliable(MagicColor.BLACK))) + manaCost.canBePaidWithAvailable(MagicColor.WHITE) && + manaCost.canBePaidWithAvailable(MagicColor.BLUE) && + manaCost.canBePaidWithAvailable(MagicColor.BLACK))) ? "0" + toSplitLast(i) : "1"; } @@ -757,9 +757,9 @@ public enum ColumnDef { private static String toGoldFirst(final InventoryItem i) { forge.card.mana.ManaCost manaCost = ((IPaperCard) i).getRules().getManaCost(); - return !(manaCost.canBePaidWithAvaliable(MagicColor.WHITE) | manaCost.canBePaidWithAvaliable(MagicColor.BLUE) | - manaCost.canBePaidWithAvaliable(MagicColor.BLACK) | manaCost.canBePaidWithAvaliable(MagicColor.RED) | - manaCost.canBePaidWithAvaliable(MagicColor.GREEN)) ? "0" : "1"; + return !(manaCost.canBePaidWithAvailable(MagicColor.WHITE) | manaCost.canBePaidWithAvailable(MagicColor.BLUE) | + manaCost.canBePaidWithAvailable(MagicColor.BLACK) | manaCost.canBePaidWithAvailable(MagicColor.RED) | + manaCost.canBePaidWithAvailable(MagicColor.GREEN)) ? "0" : "1"; } /** diff --git a/forge-gui/src/main/java/forge/localinstance/achievements/AchievementCollection.java b/forge-gui/src/main/java/forge/localinstance/achievements/AchievementCollection.java index 053c22b1c7e..2b6c7c4acfc 100644 --- a/forge-gui/src/main/java/forge/localinstance/achievements/AchievementCollection.java +++ b/forge-gui/src/main/java/forge/localinstance/achievements/AchievementCollection.java @@ -90,12 +90,12 @@ public abstract class AchievementCollection implements Iterable { filename = filename0; isLimitedFormat = isLimitedFormat0; path = path0; - addSharedAchivements(); + addSharedAchievements(); addAchievements(); load(); } - protected void addSharedAchivements() { + protected void addSharedAchievements() { add(new GameWinStreak(10, 25, 50, 100)); add(new MatchWinStreak(10, 25, 50, 100)); add(new TotalGameWins(250, 500, 1000, 2000)); diff --git a/forge-gui/src/main/java/forge/localinstance/achievements/AltWinAchievements.java b/forge-gui/src/main/java/forge/localinstance/achievements/AltWinAchievements.java index 07047282542..8fbc04f7315 100644 --- a/forge-gui/src/main/java/forge/localinstance/achievements/AltWinAchievements.java +++ b/forge-gui/src/main/java/forge/localinstance/achievements/AltWinAchievements.java @@ -18,7 +18,7 @@ public class AltWinAchievements extends AchievementCollection { } @Override - protected void addSharedAchivements() { + protected void addSharedAchievements() { //prevent including shared achievements } diff --git a/forge-gui/src/main/java/forge/localinstance/achievements/ChallengeAchievements.java b/forge-gui/src/main/java/forge/localinstance/achievements/ChallengeAchievements.java index 3989a7ad663..bb9579dbfee 100644 --- a/forge-gui/src/main/java/forge/localinstance/achievements/ChallengeAchievements.java +++ b/forge-gui/src/main/java/forge/localinstance/achievements/ChallengeAchievements.java @@ -15,7 +15,7 @@ public class ChallengeAchievements extends AchievementCollection { } @Override - protected void addSharedAchivements() { + protected void addSharedAchievements() { //prevent including shared achievements } diff --git a/forge-gui/src/main/java/forge/localinstance/achievements/PlaneswalkerAchievements.java b/forge-gui/src/main/java/forge/localinstance/achievements/PlaneswalkerAchievements.java index 5d634c020de..dec37106514 100644 --- a/forge-gui/src/main/java/forge/localinstance/achievements/PlaneswalkerAchievements.java +++ b/forge-gui/src/main/java/forge/localinstance/achievements/PlaneswalkerAchievements.java @@ -23,7 +23,7 @@ public class PlaneswalkerAchievements extends AchievementCollection { } @Override - protected void addSharedAchivements() { + protected void addSharedAchievements() { //prevent including shared achievements } diff --git a/forge-gui/src/main/java/forge/localinstance/achievements/PuzzleAchievements.java b/forge-gui/src/main/java/forge/localinstance/achievements/PuzzleAchievements.java index b9c886d1e23..3b8d7a82d59 100644 --- a/forge-gui/src/main/java/forge/localinstance/achievements/PuzzleAchievements.java +++ b/forge-gui/src/main/java/forge/localinstance/achievements/PuzzleAchievements.java @@ -8,7 +8,7 @@ public class PuzzleAchievements extends AchievementCollection { } @Override - protected void addSharedAchivements() { + protected void addSharedAchievements() { //prevent including shared achievements } From 9c57f1046148aba2ae220801645c4303a38673e7 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 26 Jul 2021 19:12:02 +0200 Subject: [PATCH 3/5] Fix achievement --- .../main/java/forge/game/ability/effects/RollDiceEffect.java | 1 - forge-gui/res/lists/altwin-achievements.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java index 01b13539df5..653f3fc4b4c 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java @@ -121,7 +121,6 @@ public class RollDiceEffect extends SpellAbilityEffect { int total = rollDiceForPlayer(sa, player, amount, sides, ignore, rolls); total += modifier; - total = 20; if (sa.hasParam("ResultSVar")) { host.setSVar(sa.getParam("ResultSVar"), Integer.toString(total)); } diff --git a/forge-gui/res/lists/altwin-achievements.txt b/forge-gui/res/lists/altwin-achievements.txt index eb5160eb27d..6a7bf42f951 100644 --- a/forge-gui/res/lists/altwin-achievements.txt +++ b/forge-gui/res/lists/altwin-achievements.txt @@ -32,7 +32,7 @@ Strixhaven Stadium|The Mage Tower|And that's game, set, and match! Test of Endurance|The Test|So... did I pass? Thassa's Oracle|The Prophecy of Victory|I see... nothing. We must've won. The Cheese Stands Alone|The Cheese|It's cheesy, but hey, it works! -The Deck of Many Things|Down on the Deck|Lucky draw! +The Deck of Many Things's Effect|Down on the Deck|Lucky draw! Triskaidekaphobia|The Fear of 13|It's just a silly ancient superstition... right? Vorpal Sword|Snicker-Snack!|He left it dead, and with its head / He went galumphing back. Emblem - Vraska, Golgari Queen|The Flurry of Assassins|How good is your dodging? From 5de3bab789af949f7ef476d4e7f04f32a30b5512 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 26 Jul 2021 20:05:03 +0200 Subject: [PATCH 4/5] Fix targetPlayableSpellCard --- .../src/main/java/forge/ai/ComputerUtil.java | 17 ++-- .../main/java/forge/ai/ability/EffectAi.java | 4 +- .../main/java/forge/ai/ability/PlayAi.java | 82 ++++++++++--------- .../main/java/forge/ai/ability/PumpAi.java | 2 +- ...ce_vryns_prodigy_jace_telepath_unbound.txt | 2 +- .../res/cardsfolder/m/mission_briefing.txt | 2 +- 6 files changed, 57 insertions(+), 52 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index d32aa26b545..273dcdd5a73 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2890,10 +2890,10 @@ public class ComputerUtil { return false; } - public static boolean targetPlayableSpellCard(final Player ai, CardCollection options, final SpellAbility sa, final boolean withoutPayingManaCost) { + public static boolean targetPlayableSpellCard(final Player ai, CardCollection options, final SpellAbility sa, final boolean withoutPayingManaCost, boolean mandatory) { // determine and target a card with a SA that the AI can afford and will play AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - Card targetSpellCard = null; + CardCollection targets = new CardCollection(); for (Card c : options) { if (withoutPayingManaCost && c.getManaCost() != null && c.getManaCost().countX() > 0) { // The AI will otherwise cheat with the mana payment, announcing X > 0 for spells like Heat Ray when replaying them @@ -2914,15 +2914,18 @@ public class ComputerUtil { abTest.setActivatingPlayer(ai); abTest.getRestrictions().setZone(c.getZone().getZoneType()); if (AiPlayDecision.WillPlay == aic.canPlaySa(abTest) && ComputerUtilCost.canPayCost(abTest, ai)) { - targetSpellCard = c; - break; + targets.add(c); } } } - if (targetSpellCard == null) { - return false; + if (targets.isEmpty()) { + if (mandatory && !options.isEmpty()) { + targets = options; + } else { + return false; + } } - sa.getTargets().add(targetSpellCard); + sa.getTargets().add(ComputerUtilCard.getBestAI(targets)); return true; } 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 407f96baa73..7b7ee50f573 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -233,10 +233,10 @@ public class EffectAi extends SpellAbilityAi { return ai.getCreaturesInPlay().size() >= i; } return true; - } else if (logic.equals("CastFromGraveThisTurn")) { + } else if (logic.equals("ReplaySpell")) { CardCollection list = new CardCollection(game.getCardsIn(ZoneType.Graveyard)); list = CardLists.getValidCards(list, sa.getTargetRestrictions().getValidTgts(), ai, sa.getHostCard(), sa); - if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) { + if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false, false)) { return false; } } else if (logic.equals("Bribe")) { diff --git a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java index 0118d1faaec..8b57bfd3f92 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java @@ -40,33 +40,9 @@ public class PlayAi extends SpellAbilityAi { return false; // prevent infinite loop } - CardCollection cards = null; - final TargetRestrictions tgt = sa.getTargetRestrictions(); - if (tgt != null) { - ZoneType zone = tgt.getZone().get(0); - cards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), ai, source, sa); - if (cards.isEmpty()) { - return false; - } - } else if (!sa.hasParam("Valid")) { - cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); - if (cards.isEmpty()) { - return false; - } - } - - if (cards != null & sa.hasParam("ValidSA")) { - final String valid[] = sa.getParam("ValidSA").split(","); - final Iterator itr = cards.iterator(); - while (itr.hasNext()) { - final Card c = itr.next(); - if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa))) { - itr.remove(); - } - } - if (cards.isEmpty()) { - return false; - } + CardCollection cards = getPlayableCards(sa, ai); + if (cards.isEmpty()) { + return false; } if (game.getRules().hasAppliedVariant(GameType.MoJhoSto) && source.getName().equals("Jhoira of the Ghitu Avatar")) { @@ -85,25 +61,15 @@ public class PlayAi extends SpellAbilityAi { } } - // Ensure that if a ValidZone is specified, there's at least something to choose from in that zone. - CardCollectionView validOpts = new CardCollection(); - if (sa.hasParam("ValidZone")) { - validOpts = AbilityUtils.filterListByType(game.getCardsIn(ZoneType.valueOf(sa.getParam("ValidZone"))), - sa.getParam("Valid"), sa); - if (validOpts.isEmpty()) { - return false; - } - } - if ("ReplaySpell".equals(logic)) { - return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost")); + return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"), false); } else if (logic.startsWith("NeedsChosenCard")) { int minCMC = 0; if (sa.getPayCosts().getCostMana() != null) { minCMC = sa.getPayCosts().getTotalMana().getCMC(); } - validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC)); - return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null, null) != null; + cards = CardLists.filter(cards, CardPredicates.greaterCMC(minCMC)); + return chooseSingleCard(ai, sa, cards, sa.hasParam("Optional"), null, null) != null; } else if ("WithTotalCMC".equals(logic)) { // Try to play only when there are more than three playable cards. if (cards.size() < 3) @@ -154,6 +120,10 @@ public class PlayAi extends SpellAbilityAi { return false; } + if ("ReplaySpell".equals(sa.getParam("AILogic"))) { + return ComputerUtil.targetPlayableSpellCard(ai, getPlayableCards(sa, ai), sa, sa.hasParam("WithoutManaCost"), mandatory); + } + return checkApiLogic(ai, sa); } @@ -218,4 +188,36 @@ public class PlayAi extends SpellAbilityAi { }); return ComputerUtilCard.getBestAI(tgtCards); } + + private static CardCollection getPlayableCards(SpellAbility sa, Player ai) { + CardCollection cards = new CardCollection(); + final TargetRestrictions tgt = sa.getTargetRestrictions(); + final Card source = sa.getHostCard(); + + if (tgt != null) { + ZoneType zone = tgt.getZone().get(0); + cards = CardLists.getValidCards(ai.getGame().getCardsIn(zone), tgt.getValidTgts(), ai, source, sa); + } else if (!sa.hasParam("Valid")) { + cards = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); + } + + if (cards != null & sa.hasParam("ValidSA")) { + final String valid[] = sa.getParam("ValidSA").split(","); + final Iterator itr = cards.iterator(); + while (itr.hasNext()) { + final Card c = itr.next(); + if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, ai), SpellAbilityPredicates.isValid(valid, ai , c, sa))) { + itr.remove(); + } + } + } + + // Ensure that if a ValidZone is specified, there's at least something to choose from in that zone. + if (sa.hasParam("ValidZone")) { + cards = new CardCollection(AbilityUtils.filterListByType(ai.getGame().getCardsIn(ZoneType.valueOf(sa.getParam("ValidZone"))), + sa.getParam("Valid"), sa)); + } + return cards; + } + } diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index 5582a1d16dc..8bd2b53a857 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -596,7 +596,7 @@ public class PumpAi extends PumpAiBase { } if ("Snapcaster".equals(sa.getParam("AILogic"))) { - if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false)) { + if (!ComputerUtil.targetPlayableSpellCard(ai, list, sa, false, false)) { return false; } } diff --git a/forge-gui/res/cardsfolder/j/jace_vryns_prodigy_jace_telepath_unbound.txt b/forge-gui/res/cardsfolder/j/jace_vryns_prodigy_jace_telepath_unbound.txt index e97f88b3d25..7b0f189b541 100644 --- a/forge-gui/res/cardsfolder/j/jace_vryns_prodigy_jace_telepath_unbound.txt +++ b/forge-gui/res/cardsfolder/j/jace_vryns_prodigy_jace_telepath_unbound.txt @@ -20,7 +20,7 @@ Colors:blue Types:Legendary Planeswalker Jace Loyalty:5 A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | NumAtt$ -2 | IsCurse$ True | Duration$ UntilYourNextTurn | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Up to one target creature gets -2/-0 until your next turn. -A:AB$ Effect | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | AILogic$ CastFromGraveThisTurn | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TgtZone$ Graveyard | TgtPrompt$ Select target instant or sorcery card | RememberObjects$ Targeted | StaticAbilities$ Play | ExileOnMoved$ Graveyard | SubAbility$ DBEffect | SpellDescription$ You may cast target instant or sorcery card from your graveyard this turn. If that spell would be put into your graveyard this turn, exile it instead. +A:AB$ Effect | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | AILogic$ ReplaySpell | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TgtZone$ Graveyard | TgtPrompt$ Select target instant or sorcery card | RememberObjects$ Targeted | StaticAbilities$ Play | ExileOnMoved$ Graveyard | SubAbility$ DBEffect | SpellDescription$ You may cast target instant or sorcery card from your graveyard this turn. If that spell would be put into your graveyard this turn, exile it instead. SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Graveyard | Description$ You may play remembered card. SVar:DBEffect:DB$ Effect | RememberObjects$ Targeted | ExileOnMoved$ Stack | ReplacementEffects$ ReplaceGraveyard SVar:ReplaceGraveyard:Event$ Moved | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Graveyard | ReplaceWith$ MoveExile | Description$ If that card would be put into your graveyard this turn, exile it instead. diff --git a/forge-gui/res/cardsfolder/m/mission_briefing.txt b/forge-gui/res/cardsfolder/m/mission_briefing.txt index c91646c9f44..0da92376933 100644 --- a/forge-gui/res/cardsfolder/m/mission_briefing.txt +++ b/forge-gui/res/cardsfolder/m/mission_briefing.txt @@ -2,7 +2,7 @@ Name:Mission Briefing ManaCost:U U Types:Instant A:SP$ Surveil | Cost$ U U | Amount$ 2 | SubAbility$ DBChooseCard | SpellDescription$ Surveil 2, then choose an instant or sorcery card in your graveyard. You may cast it this turn. If that spell would be put into your graveyard this turn, exile it instead. (To surveil 2, look at the top two cards of your library, then put any number of them into your graveyard and the rest on top of your library in any order.) -SVar:DBChooseCard:DB$ ChooseCard | Choices$ Instant.YouCtrl,Sorcery.YouCtrl | ChoiceZone$ Graveyard | AILogic$ CastFromGraveThisTurn | Mandatory$ True | RememberChosen$ True | SubAbility$ DBEffect | SpellDescription$ You may cast that card this turn. If that card would be put into your graveyard this turn, exile it instead. +SVar:DBChooseCard:DB$ ChooseCard | Choices$ Instant.YouCtrl,Sorcery.YouCtrl | ChoiceZone$ Graveyard | Mandatory$ True | RememberChosen$ True | SubAbility$ DBEffect | SpellDescription$ You may cast that card this turn. If that card would be put into your graveyard this turn, exile it instead. SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play | SubAbility$ DBCleanup | ExileOnMoved$ Stack | ReplacementEffects$ ReplaceGraveyard SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Graveyard | Description$ You may play remembered card. SVar:ReplaceGraveyard:Event$ Moved | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Graveyard | ReplaceWith$ MoveExile | Description$ If that card would be put into your graveyard this turn, exile it instead. From 7eec98db614183fc3af9462cacde40d2fb91db0c Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 26 Jul 2021 20:37:59 +0200 Subject: [PATCH 5/5] Fix CostSacrifice OriginalHost --- forge-ai/src/main/java/forge/ai/AiCostDecision.java | 3 +-- .../src/main/java/forge/game/card/CardLists.java | 10 +++++----- forge-gui/res/cardsfolder/t/tricksters_talisman.txt | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 2126230fedb..ea131c2598d 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -484,7 +484,7 @@ public class AiCostDecision extends CostDecisionMakerBase { return PaymentDecision.card(source); } if (cost.getType().equals("OriginalHost")) { - return PaymentDecision.card(ability.getHostCard()); + return PaymentDecision.card(ability.getOriginalHost()); } if (cost.getAmount().equals("All")) { // Does the AI want to use Sacrifice All? @@ -600,7 +600,6 @@ public class AiCostDecision extends CostDecisionMakerBase { // currently if amount is bigger than one, // it tries to remove all counters from one source and type at once - int toRemove = 0; final GameEntityCounterTable table = new GameEntityCounterTable(); diff --git a/forge-game/src/main/java/forge/game/card/CardLists.java b/forge-game/src/main/java/forge/game/card/CardLists.java index ca04608085e..093010da07d 100644 --- a/forge-game/src/main/java/forge/game/card/CardLists.java +++ b/forge-game/src/main/java/forge/game/card/CardLists.java @@ -122,7 +122,7 @@ public class CardLists { */ public static void sortByCmcDesc(final List list) { Collections.sort(list, CmcComparatorInv); - } // sortByCmcDesc + } /** *

@@ -133,7 +133,7 @@ public class CardLists { */ public static void sortByToughnessAsc(final List list) { Collections.sort(list, ToughnessComparator); - } // sortByToughnessAsc() + } /** *

@@ -144,7 +144,7 @@ public class CardLists { */ public static void sortByToughnessDesc(final List list) { Collections.sort(list, ToughnessComparatorInv); - } // sortByToughnessDesc() + } /** *

@@ -155,7 +155,7 @@ public class CardLists { */ public static void sortByPowerAsc(final List list) { Collections.sort(list, PowerComparator); - } // sortAttackLowFirst() + } // the higher the attack the better /** @@ -167,7 +167,7 @@ public class CardLists { */ public static void sortByPowerDesc(final List list) { Collections.sort(list, Collections.reverseOrder(PowerComparator)); - } // sortAttack() + } /** diff --git a/forge-gui/res/cardsfolder/t/tricksters_talisman.txt b/forge-gui/res/cardsfolder/t/tricksters_talisman.txt index f84dfbd654f..f36e97112c6 100644 --- a/forge-gui/res/cardsfolder/t/tricksters_talisman.txt +++ b/forge-gui/res/cardsfolder/t/tricksters_talisman.txt @@ -4,6 +4,6 @@ Types:Artifact Equipment K:Equip:2 S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | AddTrigger$ AttackTrigger | Description$ Invoke Duplicity — Equipped creature gets +1/+1 and has "Whenever this creature deals combat damage to a player, you may sacrifice CARDNAME. If you do, create a token that's a copy of this creature." SVar:AttackTrigger:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigCopy | TriggerDescription$ Whenever this creature deals combat damage to a player, you may sacrifice Trickster's Talisman. If you do, create a token that's a copy of this creature. -SVar:TrigCopy:AB$ CopyPermanent | Cost$ Sac<1/OriginalHost/Trickster's Talisman> | Defined$ Self | NumCopies$ 1 | AILogic$ DuplicatePerms +SVar:TrigCopy:AB$ CopyPermanent | Cost$ Sac<1/OriginalHost/Trickster's Talisman> | Defined$ Self | NumCopies$ 1 DeckHas:Ability$Token Oracle:Invoke Duplicity — Equipped creature gets +1/+1 and has "Whenever this creature deals combat damage to a player, you may sacrifice Trickster's Talisman. If you do, create a token that's a copy of this creature."\nEquip {2}