From 5a8429351bef24e89d45089485fed1b52b1b3caa Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Mon, 9 Nov 2020 08:43:05 +0100 Subject: [PATCH] RunChaos: better effect for Pools of Becoming --- .../src/main/java/forge/ai/AiController.java | 61 +++++-------------- .../src/main/java/forge/ai/ComputerUtil.java | 9 +-- .../src/main/java/forge/ai/SpellApiToAi.java | 2 +- .../main/java/forge/game/ability/ApiType.java | 2 +- .../game/ability/effects/CharmEffect.java | 1 - .../game/ability/effects/RunChaosEffect.java | 49 +++++++++++++++ .../ability/effects/RunSVarAbilityEffect.java | 38 ------------ .../java/forge/game/card/CardFactory.java | 17 +++--- .../forge/game/spellability/SpellAbility.java | 6 +- .../forge/game/trigger/TriggerHandler.java | 4 +- .../forge/game/trigger/WrappedAbility.java | 5 +- .../res/cardsfolder/k/kilnspire_district.txt | 8 +-- .../res/cardsfolder/p/pools_of_becoming.txt | 3 +- 13 files changed, 84 insertions(+), 121 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/ability/effects/RunChaosEffect.java delete mode 100644 forge-game/src/main/java/forge/game/ability/effects/RunSVarAbilityEffect.java diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 7ce9a83c61c..e6e4f8ee169 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -31,7 +31,6 @@ import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.DeckSection; import forge.game.*; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.SpellApiBased; @@ -64,7 +63,6 @@ import forge.util.collect.FCollectionView; import io.sentry.Sentry; import io.sentry.event.BreadcrumbBuilder; -import java.security.InvalidParameterException; import java.util.*; import java.util.Map.Entry; @@ -204,24 +202,22 @@ public class AiController { return api == null; } boolean rightapi = false; - String battlefield = ZoneType.Battlefield.toString(); Player activatingPlayer = sa.getActivatingPlayer(); // Trigger play improvements for (final Trigger tr : card.getTriggers()) { // These triggers all care for ETB effects - final Map params = tr.getMapParams(); if (tr.getMode() != TriggerType.ChangesZone) { continue; } - if (!params.get("Destination").equals(battlefield)) { + if (!ZoneType.Battlefield.toString().equals(tr.getParam("Destination"))) { continue; } - if (params.containsKey("ValidCard")) { - String validCard = params.get("ValidCard"); + if (tr.hasParam("ValidCard")) { + String validCard = tr.getParam("ValidCard"); if (!validCard.contains("Self")) { continue; } @@ -245,21 +241,11 @@ public class AiController { } // if trigger is not mandatory - no problem - if (params.get("OptionalDecider") != null && api == null) { + if (tr.hasParam("OptionalDecider") && api == null) { continue; } - SpellAbility exSA = tr.getOverridingAbility(); - - if (exSA == null) { - // Maybe better considerations - if (!params.containsKey("Execute")) { - continue; - } - exSA = AbilityFactory.getAbility(card, params.get("Execute")); - } else { - exSA = exSA.copy(); - } + SpellAbility exSA = tr.ensureAbility().copy(activatingPlayer); if (api != null) { if (exSA.getApi() != api) { @@ -273,13 +259,7 @@ public class AiController { } } - if (sa != null) { - exSA.setActivatingPlayer(activatingPlayer); - } - else { - exSA.setActivatingPlayer(player); - } - exSA.setTrigger(true); + exSA.setTrigger(tr); // for trigger test, need to ignore the conditions SpellAbilityCondition cons = exSA.getConditions(); @@ -304,18 +284,16 @@ public class AiController { // Replacement effects for (final ReplacementEffect re : card.getReplacementEffects()) { // These Replacements all care for ETB effects - - final Map params = re.getMapParams(); if (!(re instanceof ReplaceMoved)) { continue; } - if (!params.get("Destination").equals(battlefield)) { + if (!ZoneType.Battlefield.toString().equals(re.getParam("Destination"))) { continue; } - if (params.containsKey("ValidCard")) { - String validCard = params.get("ValidCard"); + if (re.hasParam("ValidCard")) { + String validCard = re.getParam("ValidCard"); if (!validCard.contains("Self")) { continue; } @@ -337,26 +315,17 @@ public class AiController { if (!re.requirementsCheck(game)) { continue; } - final SpellAbility exSA = re.getOverridingAbility(); + SpellAbility exSA = re.getOverridingAbility(); if (exSA != null) { - if (sa != null) { - exSA.setActivatingPlayer(activatingPlayer); - } - else { - exSA.setActivatingPlayer(player); - } + exSA = exSA.copy(activatingPlayer); - if (exSA.getActivatingPlayer() == null) { - throw new InvalidParameterException("Executing SpellAbility for Replacement Effect has no activating player"); + // ETBReplacement uses overriding abilities. + // These checks only work if the Executing SpellAbility is an Ability_Sub. + if ((exSA instanceof AbilitySub) && !doTrigger(exSA, false)) { + return false; } } - - // ETBReplacement uses overriding abilities. - // These checks only work if the Executing SpellAbility is an Ability_Sub. - if (exSA != null && (exSA instanceof AbilitySub) && !doTrigger(exSA, false)) { - return false; - } } return true; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 57457ea89fa..29ca893d82f 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -745,14 +745,9 @@ public class ComputerUtil { if (source.hasParam("Exploit")) { for (Trigger t : host.getTriggers()) { if (t.getMode() == TriggerType.Exploited) { - final String execute = t.getParam("Execute"); - if (execute == null) { - continue; - } - final SpellAbility exSA = AbilityFactory.getAbility(host.getSVar(execute), host); + final SpellAbility exSA = t.ensureAbility().copy(ai); - exSA.setActivatingPlayer(ai); - exSA.setTrigger(true); + exSA.setTrigger(t); // Run non-mandatory trigger. // These checks only work if the Executing SpellAbility is an Ability_Sub. diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 460e0390e29..81fa2071d21 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -142,7 +142,7 @@ public enum SpellApiToAi { .put(ApiType.RevealHand, RevealHandAi.class) .put(ApiType.ReverseTurnOrder, AlwaysPlayAi.class) .put(ApiType.RollPlanarDice, RollPlanarDiceAi.class) - .put(ApiType.RunSVarAbility, AlwaysPlayAi.class) + .put(ApiType.RunChaos, AlwaysPlayAi.class) .put(ApiType.Sacrifice, SacrificeAi.class) .put(ApiType.SacrificeAll, SacrificeAllAi.class) .put(ApiType.Scry, ScryAi.class) diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 30f174e7da7..13c08677a42 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -142,7 +142,7 @@ public enum ApiType { RevealHand (RevealHandEffect.class), ReverseTurnOrder (ReverseTurnOrderEffect.class), RollPlanarDice (RollPlanarDiceEffect.class), - RunSVarAbility (RunSVarAbilityEffect.class), + RunChaos (RunChaosEffect.class), Sacrifice (SacrificeEffect.class), SacrificeAll (SacrificeAllEffect.class), Scry (ScryEffect.class), 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 73b5cc4b05a..8e325e59d75 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 @@ -44,7 +44,6 @@ public class CharmEffect extends SpellAbilityEffect { } // set CharmOrder for (AbilitySub sub : choices) { - sub.setTrigger(sa.isTrigger()); sub.setSVar("CharmOrder", Integer.toString(indx)); indx++; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/RunChaosEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RunChaosEffect.java new file mode 100644 index 00000000000..1f2ab0d4ac1 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/RunChaosEffect.java @@ -0,0 +1,49 @@ +package forge.game.ability.effects; + +import java.util.List; +import java.util.Map; + +import com.google.common.collect.Lists; + +import forge.game.PlanarDice; +import forge.game.ability.AbilityKey; +import forge.game.ability.AbilityUtils; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.trigger.Trigger; +import forge.game.trigger.TriggerType; +import forge.game.trigger.WrappedAbility; + +public class RunChaosEffect extends SpellAbilityEffect { + + @Override + public void resolve(SpellAbility sa) { + Map map = AbilityKey.newMap(); + map.put(AbilityKey.Player, sa.getActivatingPlayer()); + map.put(AbilityKey.Result, PlanarDice.Chaos); + + List validSA = Lists.newArrayList(); + for (final Card c : getTargetCards(sa)) { + for (Trigger t : c.getTriggers()) { + if (TriggerType.PlanarDice.equals(t.getMode()) && t.performTest(map)) { + SpellAbility triggerSA = t.ensureAbility().copy(sa.getActivatingPlayer()); + + Player decider = sa.getActivatingPlayer(); + if (t.hasParam("OptionalDecider")) { + sa.setOptionalTrigger(true); + decider = AbilityUtils.getDefinedPlayers(c, t.getParam("OptionalDecider"), sa).get(0); + } else if (t.hasParam("Cost")) { + sa.setOptionalTrigger(true); + } + + final WrappedAbility wrapperAbility = new WrappedAbility(t, triggerSA, decider); + validSA.add(wrapperAbility); + } + } + } + sa.getActivatingPlayer().getController().orderAndPlaySimultaneousSa(validSA); + } + +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/RunSVarAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RunSVarAbilityEffect.java deleted file mode 100644 index ab24cfa830d..00000000000 --- a/forge-game/src/main/java/forge/game/ability/effects/RunSVarAbilityEffect.java +++ /dev/null @@ -1,38 +0,0 @@ -package forge.game.ability.effects; - -import forge.game.ability.AbilityFactory; -import forge.game.ability.SpellAbilityEffect; -import forge.game.card.Card; -import forge.game.spellability.SpellAbility; - -import java.util.ArrayList; -import java.util.List; - -public class RunSVarAbilityEffect extends SpellAbilityEffect { - - /* (non-Javadoc) - * @see forge.card.abilityfactory.SpellEffect#resolve(forge.card.spellability.SpellAbility) - */ - @Override - public void resolve(SpellAbility sa) { - String sVars = sa.getParam("SVars"); - List cards = getTargetCards(sa); - if (sVars == null || cards.isEmpty()) { - return; - } - List validSA = new ArrayList<>(); - final boolean isTrigger = sa.hasParam("IsTrigger"); - for (final Card tgtC : cards) { - if (!tgtC.hasSVar(sVars)) { - continue; - } - final SpellAbility actualSA = AbilityFactory.getAbility(tgtC.getSVar(sVars), tgtC); - actualSA.setTrigger(isTrigger); - actualSA.setActivatingPlayer(sa.getActivatingPlayer()); - actualSA.setDescription(tgtC.getName() + "'s ability"); - validSA.add(actualSA); - } - sa.getActivatingPlayer().getController().orderAndPlaySimultaneousSa(validSA); - - } -} diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index 1c9162cf4e2..ba50d232383 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -299,19 +299,20 @@ public class CardFactory { private static void buildPlaneAbilities(Card card) { StringBuilder triggerSB = new StringBuilder(); - triggerSB.append("Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | Execute$ RolledWalk | "); - triggerSB.append("Secondary$ True | TriggerDescription$ Whenever you roll Planeswalk, put this card on the "); - triggerSB.append("bottom of its owner's planar deck face down, then move the top card of your planar deck off "); - triggerSB.append("that planar deck and turn it face up"); + triggerSB.append("Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | Secondary$ True | "); + triggerSB.append("TriggerDescription$ Whenever you roll Planeswalk, put this card on the bottom of its owner's planar deck face down, "); + triggerSB.append("then move the top card of your planar deck off that planar deck and turn it face up"); + + String rolledWalk = "DB$ Planeswalk"; + + Trigger planesWalkTrigger = TriggerHandler.parseTrigger(triggerSB.toString(), card, true); + planesWalkTrigger.setOverridingAbility(AbilityFactory.getAbility(rolledWalk, card)); + card.addTrigger(planesWalkTrigger); StringBuilder saSB = new StringBuilder(); saSB.append("AB$ RollPlanarDice | Cost$ X | SorcerySpeed$ True | Activator$ Player | ActivationZone$ Command | "); saSB.append("SpellDescription$ Roll the planar dice. X is equal to the amount of times the planar die has been rolled this turn."); - card.setSVar("RolledWalk", "DB$ Planeswalk | Cost$ 0"); - Trigger planesWalkTrigger = TriggerHandler.parseTrigger(triggerSB.toString(), card, true); - card.addTrigger(planesWalkTrigger); - SpellAbility planarRoll = AbilityFactory.getAbility(saSB.toString(), card); planarRoll.setSVar("X", "Count$RolledThisTurn"); diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index e57222034c3..9e9f79c25d8 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -99,7 +99,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private CardCollection splicedCards = null; private boolean basicSpell = true; - private boolean trigger = false; private Trigger triggerObj = null; private boolean optionalTrigger = false; private ReplacementEffect replacementEffect = null; @@ -938,10 +937,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean isTrigger() { - return trigger; - } - public void setTrigger(final boolean trigger0) { - trigger = trigger0; + return triggerObj != null; } public Trigger getTrigger() { diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 36639a9c947..b1bd91ba7d5 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -539,7 +539,7 @@ public class TriggerHandler { sa.setLastStateBattlefield(game.getLastStateBattlefield()); sa.setLastStateGraveyard(game.getLastStateGraveyard()); - sa.setTrigger(true); + sa.setTrigger(regtrig); sa.setSourceTrigger(regtrig.getId()); regtrig.setTriggeringObjects(sa, runParams); sa.setTriggerRemembered(regtrig.getTriggerRemembered()); @@ -560,8 +560,6 @@ public class TriggerHandler { sa.setStackDescription(sa.toString()); if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - // need to be set for demonic pact to look for chosen modes - sa.setTrigger(regtrig); if (!CharmEffect.makeChoices(sa)) { // 603.3c If no mode is chosen, the ability is removed from the stack. return; diff --git a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java index b4b4833853f..4e308ca1576 100644 --- a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java +++ b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java @@ -51,8 +51,8 @@ public class WrappedAbility extends Ability { public WrappedAbility(final Trigger regtrig0, final SpellAbility sa0, final Player decider0) { super(sa0.getHostCard(), ManaCost.ZERO, sa0.getView()); setTrigger(regtrig0); - setTrigger(true); sa = sa0; + sa.setTrigger(regtrig0); decider = decider0; sa.setDescription(this.getStackDescription()); } @@ -474,9 +474,6 @@ public class WrappedAbility extends Ability { } } - // set Trigger - sa.setTrigger(regtrig); - if (decider != null && !decider.getController().confirmTrigger(this)) { return; } diff --git a/forge-gui/res/cardsfolder/k/kilnspire_district.txt b/forge-gui/res/cardsfolder/k/kilnspire_district.txt index 4bb0df42f1c..4f0d18ef0ea 100644 --- a/forge-gui/res/cardsfolder/k/kilnspire_district.txt +++ b/forge-gui/res/cardsfolder/k/kilnspire_district.txt @@ -5,11 +5,9 @@ T:Mode$ PlaneswalkedTo | ValidCard$ Card.Self | Execute$ PutCounter | TriggerDes T:Mode$ Phase | PreCombatMain$ True | ValidPlayer$ You | TriggerZones$ Command | Execute$ PutCounter | Secondary$ True | TriggerDescription$ When you planeswalk to CARDNAME or at the beginning of your precombat main phase, put a charge counter on CARDNAME, then add {R} for each charge counter on it. SVar:PutCounter:DB$PutCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | SubAbility$ DBMana SVar:DBMana:DB$ Mana | Produced$ R | Amount$ Y | References$ Y -T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ DBPay | TriggerDescription$ Whenever you roll {CHAOS}, you may pay {X}. If you do, CARDNAME deals X damage to any target. -SVar:DBPay:DB$ ChooseNumber | Defined$ TriggeredPlayer | ChooseAnyNumber$ True | ListTitle$ X to pay | SubAbility$ RolledChaos -SVar:RolledChaos:DB$ DealDamage | UnlessCost$ ChosenNumber | UnlessPayer$ TriggeredPlayer | UnlessSwitched$ True | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ PaidChaos | References$ PaidChaos -SVar:PaidChaos:Count$ChosenNumber +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, you may pay {X}. If you do, CARDNAME deals X damage to any target. +SVar:RolledChaos:AB$ DealDamage | Cost$ X | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | References$ X +SVar:X:Count$xPaid SVar:Y:Count$CardCounters.CHARGE SVar:AIRollPlanarDieParams:Mode$ Always -SVar:Picture:http://www.wizards.com/global/images/magic/general/kilnspire_district.jpg Oracle:When you planeswalk to Kilnspire District or at the beginning of your precombat main phase, put a charge counter on Kilnspire District, then add {R} for each charge counter on it.\nWhenever you roll {CHAOS}, you may pay {X}. If you do, Kilnspire District deals X damage to any target. diff --git a/forge-gui/res/cardsfolder/p/pools_of_becoming.txt b/forge-gui/res/cardsfolder/p/pools_of_becoming.txt index 407ea9289ed..797f2452f79 100644 --- a/forge-gui/res/cardsfolder/p/pools_of_becoming.txt +++ b/forge-gui/res/cardsfolder/p/pools_of_becoming.txt @@ -8,8 +8,7 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Y:Remembered$Amount T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, reveal the top three cards of your planar deck. Each of the revealed cards' {CHAOS} abilities triggers. Then put the revealed cards on the bottom of your planar deck in any order. SVar:RolledChaos:DB$ Dig | DigNum$ 3 | NoMove$ True | Reveal$ True | SourceZone$ PlanarDeck | RememberRevealed$ True | SubAbility$ DBRunChaos -SVar:DBRunChaos:DB$ RunSVarAbility | Defined$ Remembered | SVars$ RolledChaos | IsTrigger$ True | SubAbility$ DBChangeZone +SVar:DBRunChaos:DB$ RunChaos | Defined$ Remembered | SubAbility$ DBChangeZone SVar:DBChangeZone:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ PlanarDeck | Destination$ PlanarDeck | LibraryPosition$ -1 | SubAbility$ DBCleanup -SVar:Picture:http://www.wizards.com/global/images/magic/general/pools_of_becoming.jpg SVar:AIRollPlanarDieParams:Mode$ Always Oracle:At the beginning of your end step, put the cards in your hand on the bottom of your library in any order, then draw that many cards.\nWhenever you roll {CHAOS}, reveal the top three cards of your planar deck. Each of the revealed cards' {CHAOS} abilities triggers. Then put the revealed cards on the bottom of your planar deck in any order.