From ec3f7e00ca0e1e787d531a45d7505a30f47d09a8 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 10 Jul 2022 15:51:56 +0200 Subject: [PATCH] Fix AI casting suspended spells against Drannith Magistrate (#1098) * Cleanup cards * Fix AI casting suspended spells against Drannith Magistrate Co-authored-by: tool4EvEr --- .../src/main/java/forge/ai/ComputerUtil.java | 30 ++++++++++++- .../java/forge/ai/PlayerControllerAi.java | 2 +- .../main/java/forge/game/GameActionUtil.java | 43 ++++++++++++++++++ .../ability/effects/CountersPutEffect.java | 2 +- .../res/cardsfolder/d/djerus_resolve.txt | 3 +- forge-gui/res/cardsfolder/g/godtoucher.txt | 3 +- .../cardsfolder/h/harvestguard_alseids.txt | 3 +- .../res/cardsfolder/i/indestructible_aura.txt | 3 +- .../res/cardsfolder/p/piston_fist_cyclops.txt | 2 +- .../res/cardsfolder/s/shielded_passage.txt | 3 +- .../cardsfolder/w/wellgabber_apothecary.txt | 3 +- .../forge/player/HumanPlaySpellAbility.java | 44 +------------------ 12 files changed, 88 insertions(+), 53 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 83520cd8c55..b85f75da7fd 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -250,6 +250,13 @@ public class ComputerUtil { return false; final Card source = sa.getHostCard(); + + Zone fromZone = game.getZoneOf(source); + int zonePosition = 0; + if (fromZone != null) { + zonePosition = fromZone.getCards().indexOf(source); + } + if (sa.isSpell() && !source.isCopiedSpell()) { sa.setHostCard(game.getAction().moveToStack(source, sa)); } @@ -257,11 +264,18 @@ public class ComputerUtil { sa = GameActionUtil.addExtraKeywordCost(sa); final Cost cost = sa.getPayCosts(); + final CostPayment pay = new CostPayment(cost, sa); + + // do this after card got added to stack + if (!sa.checkRestrictions(ai)) { + GameActionUtil.rollbackAbility(sa, fromZone, zonePosition, pay, source); + return false; + } + if (cost == null) { ComputerUtilMana.payManaCost(ai, sa, false); game.getStack().add(sa); } else { - final CostPayment pay = new CostPayment(cost, sa); if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) { game.getStack().add(sa); } @@ -292,6 +306,13 @@ public class ComputerUtil { newSA = GameActionUtil.addExtraKeywordCost(newSA); final Card source = newSA.getHostCard(); + + Zone fromZone = game.getZoneOf(source); + int zonePosition = 0; + if (fromZone != null) { + zonePosition = fromZone.getCards().indexOf(source); + } + if (newSA.isSpell() && !source.isCopiedSpell()) { newSA.setHostCard(game.getAction().moveToStack(source, newSA)); @@ -303,6 +324,13 @@ public class ComputerUtil { } final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA); + + // do this after card got added to stack + if (!sa.checkRestrictions(ai)) { + GameActionUtil.rollbackAbility(sa, fromZone, zonePosition, pay, source); + return false; + } + pay.payComputerCosts(new AiCostDecision(ai, newSA, false)); game.getStack().add(newSA); diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 772f0d51734..5ca41529349 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -1087,7 +1087,7 @@ public class PlayerControllerAi extends PlayerController { if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell? Spell spell = (Spell) tgtSA; // TODO if mandatory AI is only forced to use mana when it's already in the pool - if (tgtSA.checkRestrictions(brains.getPlayer()) && (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional)) { + if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) { if (noManaCost) { return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame()); } diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index f7574571163..22f49d4fa62 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -34,6 +34,7 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.CardPlayOption.PayManaCost; import forge.game.cost.Cost; +import forge.game.cost.CostPayment; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; @@ -842,4 +843,46 @@ public final class GameActionUtil { c.getGame().getTriggerHandler().resetActiveTriggers(); } + public static void rollbackAbility(SpellAbility ability, final Zone fromZone, final int zonePosition, CostPayment payment, Card oldCard) { + // cancel ability during target choosing + final Game game = ability.getActivatingPlayer().getGame(); + + if (fromZone != null) { // and not a copy + oldCard.setCastSA(null); + oldCard.setCastFrom(null); + // add back to where it came from, hopefully old state + // skip GameAction + oldCard.getZone().remove(oldCard); + fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); + ability.setHostCard(oldCard); + ability.setXManaCostPaid(null); + ability.setSpendPhyrexianMana(false); + if (ability.hasParam("Announce")) { + for (final String aVar : ability.getParam("Announce").split(",")) { + final String varName = aVar.trim(); + if (!varName.equals("X")) { + ability.setSVar(varName, "0"); + } + } + } + // better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost) + for (SpellAbility sa : oldCard.getSpells()) { + sa.setHostCard(oldCard); + } + //for Chorus of the Conclave + ability.rollback(); + + oldCard.setBackSide(false); + oldCard.setState(oldCard.getFaceupCardStateName(), true); + oldCard.unanimateBestow(); + } + + ability.clearTargets(); + + ability.resetOnceResolved(); + payment.refundPayment(); + game.getStack().clearFrozen(); + game.getTriggerHandler().clearWaitingTriggers(); + } + } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java index bab1c1bd3e0..3dec9d6ae4e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java @@ -496,7 +496,7 @@ public class CountersPutEffect extends SpellAbilityEffect { // need to unfreeze tracker game.getTracker().unfreeze(); - // check if it can recive the Tribute + // check if it can receive the Tribute if (abort) { continue; } diff --git a/forge-gui/res/cardsfolder/d/djerus_resolve.txt b/forge-gui/res/cardsfolder/d/djerus_resolve.txt index 441cab095b4..c940cf2a52e 100644 --- a/forge-gui/res/cardsfolder/d/djerus_resolve.txt +++ b/forge-gui/res/cardsfolder/d/djerus_resolve.txt @@ -2,6 +2,7 @@ Name:Djeru's Resolve ManaCost:W Types:Instant A:SP$ Untap | Cost$ W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBPump | SpellDescription$ Untap target creature. Prevent all damage that would be dealt to it this turn. -SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Prevent all damage that would be dealt to CARDNAME. +SVar:DBPump:DB$ Effect | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. K:Cycling:2 Oracle:Untap target creature. Prevent all damage that would be dealt to it this turn.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/g/godtoucher.txt b/forge-gui/res/cardsfolder/g/godtoucher.txt index ffa765c3cde..1052967e2fe 100644 --- a/forge-gui/res/cardsfolder/g/godtoucher.txt +++ b/forge-gui/res/cardsfolder/g/godtoucher.txt @@ -2,7 +2,8 @@ Name:Godtoucher ManaCost:3 G Types:Creature Elf Cleric PT:2/2 -A:AB$ Pump | Cost$ 1 W T | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature.powerGE5 | TgtPrompt$ Select target creature with power 5 or greater | SpellDescription$ Prevent all damage that would be dealt to target creature with power 5 or greater this turn. +A:AB$ Effect | Cost$ 1 W T | ValidTgts$ Creature.powerGE5 | TgtPrompt$ Select target creature with power 5 or greater | ReplacementEffects$ RPrevent| RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature with power 5 or greater this turn. +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. AI:RemoveDeck:All AI:RemoveDeck:Random Oracle:{1}{W}, {T}: Prevent all damage that would be dealt to target creature with power 5 or greater this turn. diff --git a/forge-gui/res/cardsfolder/h/harvestguard_alseids.txt b/forge-gui/res/cardsfolder/h/harvestguard_alseids.txt index 50e85f86af2..efb0d150036 100644 --- a/forge-gui/res/cardsfolder/h/harvestguard_alseids.txt +++ b/forge-gui/res/cardsfolder/h/harvestguard_alseids.txt @@ -3,7 +3,8 @@ ManaCost:2 W Types:Enchantment Creature Nymph PT:2/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self,Enchantment.Other+YouCtrl | Execute$ TrigPump | TriggerDescription$ Constellation — Whenever CARDNAME or another enchantment enters the battlefield under your control, prevent all damage that would be dealt to target creature this turn. -SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Prevent all damage that would be dealt to CARDNAME. +SVar:TrigPump:DB$ Effect | Cost$ W | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. SVar:PlayMain1:TRUE SVar:BuffedBy:Enchantment Oracle:Constellation — Whenever Harvestguard Alseids or another enchantment enters the battlefield under your control, prevent all damage that would be dealt to target creature this turn. diff --git a/forge-gui/res/cardsfolder/i/indestructible_aura.txt b/forge-gui/res/cardsfolder/i/indestructible_aura.txt index da743170761..fab165f32b9 100644 --- a/forge-gui/res/cardsfolder/i/indestructible_aura.txt +++ b/forge-gui/res/cardsfolder/i/indestructible_aura.txt @@ -1,6 +1,7 @@ Name:Indestructible Aura ManaCost:W Types:Instant -A:SP$ Pump | Cost$ W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Prevent all damage that would be dealt to target creature this turn. +A:SP$ Effect | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature this turn. +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. AI:RemoveDeck:All Oracle:Prevent all damage that would be dealt to target creature this turn. diff --git a/forge-gui/res/cardsfolder/p/piston_fist_cyclops.txt b/forge-gui/res/cardsfolder/p/piston_fist_cyclops.txt index 25f2a682d01..37fb8914942 100644 --- a/forge-gui/res/cardsfolder/p/piston_fist_cyclops.txt +++ b/forge-gui/res/cardsfolder/p/piston_fist_cyclops.txt @@ -4,7 +4,7 @@ Types:Creature Cyclops PT:4/3 K:Defender S:Mode$ CanAttackDefender | ValidCard$ Card.Self | CheckSVar$ X | Description$ As long as you've cast an instant or sorcery spell this turn, CARDNAME can attack as though it didn't have defender. -SVar:X:Count$ThisTurnCast_Instant.YouOwn,Sorcery.YouOwn +SVar:X:Count$ThisTurnCast_Instant.YouCtrl,Sorcery.YouCtrl SVar:BuffedBy:Instant,Sorcery DeckHints:Type$Instant|Sorcery Oracle:Defender\nAs long as you've cast an instant or sorcery spell this turn, Piston-Fist Cyclops can attack as though it didn't have defender. diff --git a/forge-gui/res/cardsfolder/s/shielded_passage.txt b/forge-gui/res/cardsfolder/s/shielded_passage.txt index 596241bbb38..5ab6ad87861 100644 --- a/forge-gui/res/cardsfolder/s/shielded_passage.txt +++ b/forge-gui/res/cardsfolder/s/shielded_passage.txt @@ -1,6 +1,7 @@ Name:Shielded Passage ManaCost:W Types:Instant -A:SP$ Pump | Cost$ W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Prevent all damage that would be dealt to target creature this turn. +A:SP$ Effect | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature this turn. +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. AI:RemoveDeck:All Oracle:Prevent all damage that would be dealt to target creature this turn. diff --git a/forge-gui/res/cardsfolder/w/wellgabber_apothecary.txt b/forge-gui/res/cardsfolder/w/wellgabber_apothecary.txt index 239f9a3e2b4..8dd9b05c732 100644 --- a/forge-gui/res/cardsfolder/w/wellgabber_apothecary.txt +++ b/forge-gui/res/cardsfolder/w/wellgabber_apothecary.txt @@ -2,5 +2,6 @@ Name:Wellgabber Apothecary ManaCost:4 W Types:Creature Merfolk Cleric PT:2/3 -A:AB$ Pump | Cost$ 1 W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature.Merfolk+tapped,Creature.Kithkin+tapped | TgtPrompt$ Select tapped Merfolk or Kithkin creature | SpellDescription$ Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn. +A:SP$ Effect | Cost$ 1 W | ValidTgts$ Creature.Merfolk+tapped,Creature.Kithkin+tapped | TgtPrompt$ Select tapped Merfolk or Kithkin creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn. +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. Oracle:{1}{W}: Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn. diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 74fa086002a..9d9b4ce0ebb 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -164,7 +164,7 @@ public class HumanPlaySpellAbility { if (!prerequisitesMet) { if (!ability.isTrigger()) { - rollbackAbility(fromZone, zonePosition, payment, c); + GameActionUtil.rollbackAbility(ability, fromZone, zonePosition, payment, c); if (ability.getHostCard().isMadness()) { // if a player failed to play madness cost, move the card to graveyard Card newCard = game.getAction().moveToGraveyard(c, null); @@ -209,48 +209,6 @@ public class HumanPlaySpellAbility { return true; } - private void rollbackAbility(final Zone fromZone, final int zonePosition, CostPayment payment, Card oldCard) { - // cancel ability during target choosing - final Game game = ability.getActivatingPlayer().getGame(); - - if (fromZone != null) { // and not a copy - oldCard.setCastSA(null); - oldCard.setCastFrom(null); - // add back to where it came from, hopefully old state - // skip GameAction - oldCard.getZone().remove(oldCard); - fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); - ability.setHostCard(oldCard); - ability.setXManaCostPaid(null); - ability.setSpendPhyrexianMana(false); - if (ability.hasParam("Announce")) { - for (final String aVar : ability.getParam("Announce").split(",")) { - final String varName = aVar.trim(); - if (!varName.equals("X")) { - ability.setSVar(varName, "0"); - } - } - } - // better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost) - for (SpellAbility sa : oldCard.getSpells()) { - sa.setHostCard(oldCard); - } - //for Chorus of the Conclave - ability.rollback(); - - oldCard.setBackSide(false); - oldCard.setState(oldCard.getFaceupCardStateName(), true); - oldCard.unanimateBestow(); - } - - ability.clearTargets(); - - ability.resetOnceResolved(); - payment.refundPayment(); - game.getStack().clearFrozen(); - game.getTriggerHandler().clearWaitingTriggers(); - } - private boolean announceValuesLikeX() { if (ability.isCopied() || ability.isWrapper()) { return true; } //don't re-announce for spell copies