From c02f7c698dc6bbfb0a841b52439aae80bcb29265 Mon Sep 17 00:00:00 2001 From: Klisz Date: Sat, 30 Dec 2023 23:11:30 -0700 Subject: [PATCH] Planechase inherent effects (#4408) * Planechase effects draft Draft to close #4262, by moving the planeswalking ability and the static chaos trigger to an effect carried with the player in a planechase game. Currently this effect is visible in the command zone much like the monarch or initiative (but with no image), which is something that needs fixing; I'd have it be a DetachedCardEffect but there's no linked card for it like there are for commander and companion effects (the active plane [or the face-up plane with the earliest timestamp, I suppose] would be a logical choice for the getCardForUi, but that would require updating it as the plane changes, and in any case because there's a separate effect for each player, the fact that the owner of a DetachedCardEffect is set as the owner of the linked card also adds some wrinkles. In any case, I've set the effects to be created in startGame after initPlane is run, rather than in initVariantZones as is the case for the commander and companion DetachedCardEffects, in anticipation of hopefully in the future making the effect be linked to an active plane). * Update Player.java * Move planar dice special action to effect * Update ComputerUtilAbility.java * Move die roll chaos to PlanarDice.java --- .../java/forge/ai/ComputerUtilAbility.java | 23 +++++++++++--- .../forge/ai/ability/RollPlanarDiceAi.java | 11 +++++-- .../src/main/java/forge/game/GameAction.java | 3 ++ .../src/main/java/forge/game/PlanarDice.java | 5 +++ .../java/forge/game/card/CardFactory.java | 31 ------------------- .../main/java/forge/game/player/Player.java | 30 ++++++++++++++++++ 6 files changed, 66 insertions(+), 37 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java index d8750ad2e02..13fbee4c46c 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java @@ -255,10 +255,25 @@ public class ComputerUtilAbility { } // deprioritize planar die roll marked with AIRollPlanarDieParams:LowPriority$ True - if (ApiType.RollPlanarDice == a.getApi() && a.getHostCard() != null && a.getHostCard().hasSVar("AIRollPlanarDieParams") && a.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) { - return 1; - } else if (ApiType.RollPlanarDice == b.getApi() && b.getHostCard() != null && b.getHostCard().hasSVar("AIRollPlanarDieParams") && b.getHostCard().getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) { - return -1; + if (ApiType.RollPlanarDice == a.getApi() || ApiType.RollPlanarDice == b.getApi()) { + Card hostCardForGame = a.getHostCard(); + if (hostCardForGame == null) { + if (b.getHostCard() != null) { + hostCardForGame = b.getHostCard(); + } else { + return 0; // fallback if neither SA have a host card somehow + } + } + Game game = hostCardForGame.getGame(); + for (Card c : game.getActivePlanes()) { + if (c.hasSVar("AIRollPlanarDieParams") && c.getSVar("AIRollPlanarDieParams").toLowerCase().matches(".*lowpriority\\$\\s*true.*")) { + if (ApiType.RollPlanarDice == a.getApi()) { + return 1; + } else { + return -1; + } + } + } } // deprioritize pump spells with pure energy cost (can be activated last, diff --git a/forge-ai/src/main/java/forge/ai/ability/RollPlanarDiceAi.java b/forge-ai/src/main/java/forge/ai/ability/RollPlanarDiceAi.java index ca686734f15..32b21c964cb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RollPlanarDiceAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RollPlanarDiceAi.java @@ -19,9 +19,16 @@ public class RollPlanarDiceAi extends SpellAbilityAi { */ @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); - Card plane = sa.getHostCard(); + for (Card c : ai.getGame().getActivePlanes()) { + if (willRollOnPlane(ai, c)) { + return true; + } + } + return false; + } + private boolean willRollOnPlane(Player ai, Card plane) { + AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); boolean decideToRoll = false; boolean rollInMain1 = false; String modeName = "never"; diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 8af6addff4b..0304efda411 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -2090,6 +2090,9 @@ public class GameAction { // if (game.getRules().hasAppliedVariant(GameType.Planechase)) { first.initPlane(); + for (final Player p1 : game.getPlayers()) { + p1.createPlanechaseEffects(game); + } } first = runOpeningHandActions(first); diff --git a/forge-game/src/main/java/forge/game/PlanarDice.java b/forge-game/src/main/java/forge/game/PlanarDice.java index 7f0b92c7f60..11dcc7d442b 100644 --- a/forge-game/src/main/java/forge/game/PlanarDice.java +++ b/forge-game/src/main/java/forge/game/PlanarDice.java @@ -87,6 +87,11 @@ public enum PlanarDice { runParams.put(AbilityKey.Result, Arrays.asList(0)); roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false); + if (res == Chaos) { + runParams = AbilityKey.mapFromPlayer(roller); + roller.getGame().getTriggerHandler().runTrigger(TriggerType.ChaosEnsues, runParams, false); + } + return res; } 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 7c3be8266c2..0624524101e 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -318,42 +318,11 @@ public class CardFactory { // ****************************************************************** // ************** Link to different CardFactories ******************* - if (card.isPlane()) { - buildPlaneAbilities(card); - } buildBattleAbilities(card); CardFactoryUtil.setupKeywordedAbilities(card); // Should happen AFTER setting left/right split abilities to set Fuse ability to both sides card.updateStateForView(); } - private static void buildPlaneAbilities(Card card) { - String trigger = "Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | Secondary$ True | " + - "TriggerDescription$ Whenever you roll the Planeswalker symbol on the planar die, planeswalk."; - - String rolledWalk = "DB$ Planeswalk | Cause$ PlanarDie"; - - Trigger planesWalkTrigger = TriggerHandler.parseTrigger(trigger, card, true); - planesWalkTrigger.setOverridingAbility(AbilityFactory.getAbility(rolledWalk, card)); - card.addTrigger(planesWalkTrigger); - - String chaosTrig = "Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Static$ True"; - - String rolledChaos = "DB$ ChaosEnsues"; - - Trigger chaosTrigger = TriggerHandler.parseTrigger(chaosTrig, card, true); - chaosTrigger.setOverridingAbility(AbilityFactory.getAbility(rolledChaos, card)); - card.addTrigger(chaosTrigger); - - String specialA = "ST$ RollPlanarDice | Cost$ X | SorcerySpeed$ True | Activator$ Player | SpecialAction$ True" + - " | ActivationZone$ Command | SpellDescription$ Roll the planar dice. X is equal to the number of " + - "times you have previously taken this action this turn. | CostDesc$ {X}: "; - - SpellAbility planarRoll = AbilityFactory.getAbility(specialA, card); - planarRoll.setSVar("X", "Count$PlanarDiceSpecialActionThisTurn"); - - card.addSpellAbility(planarRoll); - } - private static void buildBattleAbilities(Card card) { if (!card.isBattle()) { return; diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 9b9bc6cb576..230ae9e9e71 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -3176,6 +3176,36 @@ public class Player extends GameEntity implements Comparable { return eff; } + public void createPlanechaseEffects(Game game) { + final PlayerZone com = getZone(ZoneType.Command); + final String name = "Planar Dice"; + final Card eff = new Card(game.nextCardId(), game); + eff.setTimestamp(game.getNextTimestamp()); + eff.setName(name); + eff.setOwner(this); + eff.setImmutable(true); + String image = ImageKeys.getTokenKey("planechase"); + eff.setImageKey(image); + + String trigger = "Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | ValidPlayer$ You | Secondary$ True | " + + "TriggerDescription$ Whenever you roll the Planeswalker symbol on the planar die, planeswalk."; + String rolledWalk = "DB$ Planeswalk | Cause$ PlanarDie"; + Trigger planesWalkTrigger = TriggerHandler.parseTrigger(trigger, eff, true); + planesWalkTrigger.setOverridingAbility(AbilityFactory.getAbility(rolledWalk, eff)); + eff.addTrigger(planesWalkTrigger); + + String specialA = "ST$ RollPlanarDice | Cost$ X | SorcerySpeed$ True | Activator$ Player | SpecialAction$ True" + + " | ActivationZone$ Command | SpellDescription$ Roll the planar dice. X is equal to the number of " + + "times you have previously taken this action this turn. | CostDesc$ {X}: "; + SpellAbility planarRoll = AbilityFactory.getAbility(specialA, eff); + planarRoll.setSVar("X", "Count$PlanarDiceSpecialActionThisTurn"); + eff.addSpellAbility(planarRoll); + + eff.updateStateForView(); + com.add(eff); + this.updateZoneForView(com); + } + public void createTheRing(Card host) { final PlayerZone com = getZone(ZoneType.Command); if (theRing == null) {