From d4f7cfc8cf7667d27309602d7bea2ba3b2ddba04 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 2 May 2026 08:39:04 +0200 Subject: [PATCH 1/4] Player: store Land Played as LKI --- .../src/main/java/forge/ai/GameState.java | 12 ------ .../src/main/java/forge/ai/SpecialCardAi.java | 4 +- .../java/forge/ai/ability/ChangeZoneAi.java | 2 +- .../main/java/forge/ai/ability/DestroyAi.java | 4 +- .../java/forge/ai/simulation/GameCopier.java | 1 - .../src/main/java/forge/game/GameAction.java | 2 +- .../main/java/forge/game/GameSnapshot.java | 1 - .../java/forge/game/ability/AbilityUtils.java | 10 ++++- .../ability/effects/RestartGameEffect.java | 2 +- .../main/java/forge/game/player/Player.java | 43 +++++++++---------- .../java/forge/game/player/PlayerView.java | 2 +- .../forge/game/trigger/TriggerLandPlayed.java | 2 +- .../rebalanced/a-visions_of_phyrexia.txt | 6 +-- .../res/cardsfolder/s/spider_man_2099.txt | 6 +-- .../res/cardsfolder/v/visions_of_phyrexia.txt | 6 +-- forge-gui/res/puzzle/MTGP_01.pzl | 2 - forge-gui/res/puzzle/MTGP_02.pzl | 4 -- forge-gui/res/puzzle/MTGP_03.pzl | 4 -- forge-gui/res/puzzle/MTGP_04.pzl | 4 -- forge-gui/res/puzzle/MTGP_05.pzl | 4 -- forge-gui/res/puzzle/MTGP_06.pzl | 4 -- forge-gui/res/puzzle/MTGP_08.pzl | 4 -- forge-gui/res/puzzle/MTGP_09.pzl | 4 -- forge-gui/res/puzzle/PS_DFT2.pzl | 4 -- forge-gui/res/puzzle/PS_DFT3.pzl | 4 -- forge-gui/res/puzzle/PS_ELD4.pzl | 1 - forge-gui/res/puzzle/PS_ELD9.pzl | 1 - forge-gui/res/puzzle/PS_GUEST0.pzl | 1 - forge-gui/res/puzzle/PS_LTR1.pzl | 2 - forge-gui/res/puzzle/PS_MOM1.pzl | 4 -- forge-gui/res/puzzle/PS_MOM2.pzl | 4 -- forge-gui/res/puzzle/PS_MOM3.pzl | 2 - forge-gui/res/puzzle/PS_MOM4.pzl | 4 -- forge-gui/res/puzzle/PS_MOM5.pzl | 4 -- forge-gui/res/puzzle/PS_ONE3.pzl | 4 -- forge-gui/res/puzzle/PS_ONE4.pzl | 4 -- forge-gui/res/puzzle/PS_ONE5.pzl | 4 -- forge-gui/res/puzzle/PS_RNA8.pzl | 1 - forge-gui/res/puzzle/PS_RVR1.pzl | 2 - forge-gui/res/puzzle/PS_WAR0.pzl | 1 - 40 files changed, 41 insertions(+), 139 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index b18ea26cc70..d36ce30cf37 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -59,8 +59,6 @@ static class PlayerState { private String counters = ""; private String manaPool = ""; private String persistentMana = ""; - private int landsPlayed = 0; - private int landsPlayedLastTurn = 0; private int numRingTemptedYou = 0; private int speed = 0; private String precast = null; @@ -136,8 +134,6 @@ public String toString() { for (PlayerState p : playerStates) { String prefix = "p" + playerIndex++; sb.append(TextUtil.concatNoSpace(prefix + "life=", String.valueOf(p.life), "\n")); - sb.append(TextUtil.concatNoSpace(prefix + "landsplayed=", String.valueOf(p.landsPlayed), "\n")); - sb.append(TextUtil.concatNoSpace(prefix + "landsplayedlastturn=", String.valueOf(p.landsPlayedLastTurn), "\n")); sb.append(TextUtil.concatNoSpace(prefix + "numringtemptedyou=", String.valueOf(p.numRingTemptedYou), "\n")); sb.append(TextUtil.concatNoSpace(prefix + "speed=", String.valueOf(p.speed), "\n")); if (!p.counters.isEmpty()) { @@ -165,8 +161,6 @@ public void initFromGame(Game game) { for (Player player : game.getPlayers()) { PlayerState p = new PlayerState(); p.life = player.getLife(); - p.landsPlayed = player.getLandsPlayedThisTurn(); - p.landsPlayedLastTurn = player.getLandsPlayedLastTurn(); p.counters = countersToString(player.getCounters()); p.manaPool = processManaPool(player.getManaPool()); p.numRingTemptedYou = player.getNumRingTemptedYou(); @@ -545,10 +539,6 @@ else if (categoryName.endsWith("phaseadvance")) getPlayerState(categoryName).life = Integer.parseInt(categoryValue); } else if (categoryName.endsWith("counters")) { getPlayerState(categoryName).counters = categoryValue; - } else if (categoryName.endsWith("landsplayed")) { - getPlayerState(categoryName).landsPlayed = Integer.parseInt(categoryValue); - } else if (categoryName.endsWith("landsplayedlastturn")) { - getPlayerState(categoryName).landsPlayedLastTurn = Integer.parseInt(categoryValue); } else if (categoryName.endsWith("numringtemptedyou")) { getPlayerState(categoryName).numRingTemptedYou = Integer.parseInt(categoryValue); } else if (categoryName.endsWith("speed")) { @@ -1156,8 +1146,6 @@ private void setupPlayerState(final Player p, final PlayerState state) { } if (state.life >= 0) p.setLife(state.life, null); - p.setLandsPlayedThisTurn(state.landsPlayed); - p.setLandsPlayedLastTurn(state.landsPlayedLastTurn); p.setNumRingTemptedYou(state.numRingTemptedYou); p.setSpeed(state.speed); diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 753d060c565..95a382f46ad 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -1359,7 +1359,7 @@ public static AiAbilityDecision consider(final Player ai, final SpellAbility sa) if (ph.getNextTurn().equals(ai) && ph.is(PhaseType.MAIN2) && ai.getSpellsCastLastTurn() == 0 && ai.getSpellsCastThisTurn() == 0 - && ai.getLandsPlayedLastTurn() == 0) { + && ai.getLandsPlayedMyLastTurn() == 0) { // We're in a situation when we have nothing castable in hand, something needs to be done if (!blackViseOTB) { // exile-loot +1 card when at max hand size, hoping to get a workable spell or land @@ -2067,7 +2067,7 @@ public static boolean consider(final Player ai, final SpellAbility sa) { if (ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN) && ai.getSpellsCastLastTurn() == 0 && ai.getSpellsCastThisTurn() == 0 - && ai.getLandsPlayedLastTurn() == 0) { + && ai.getLandsPlayedMyLastTurn() == 0) { // We're in a situation when we have nothing castable in hand, something needs to be done if (!blackViseOTB) { // draw +1 card when at max hand size, hoping to draw a workable spell or land diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 6dc6a606b5e..44cb78d9de0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -356,7 +356,7 @@ private static AiAbilityDecision hiddenOriginCanPlayAI(final Player ai, final Sp return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } if ("Atarka's Command".equals(sourceName) - && (list.size() < 2 || ai.getLandsPlayedThisTurn() < 1)) { + && (list.size() < 2 || ai.getLandsPlayedThisTurn().isEmpty())) { // be strict on playing lands off charms return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java index 146db23a4d0..6070ed2d45c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java @@ -415,8 +415,8 @@ public boolean doLandForLandRemovalLogic(SpellAbility sa, Player ai, Card tgtLan // if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him PhaseHandler ph = ai.getGame().getPhaseHandler(); - boolean oppSkippedLandDrop = (tgtPlayer.getLandsPlayedLastTurn() == 0 && ph.isPlayerTurn(ai)) - || (tgtPlayer.getLandsPlayedThisTurn() == 0 && ph.isPlayerTurn(tgtPlayer) && ph.getPhase().isAfter(PhaseType.MAIN2)); + boolean oppSkippedLandDrop = (tgtPlayer.getLandsPlayedMyLastTurn() == 0 && ph.isPlayerTurn(ai)) + || (tgtPlayer.getLandsPlayedThisTurn().isEmpty() && ph.isPlayerTurn(tgtPlayer) && ph.getPhase().isAfter(PhaseType.MAIN2)); boolean canManaLock = oppLandsOTB <= amountLandsToManalock && oppSkippedLandDrop; // Best target is a basic land, and there's only one of it, so destroying it may potentially color-lock the opponent diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index a367ab10d3d..ad7c6148945 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -94,7 +94,6 @@ public Game makeCopy(PhaseType advanceToPhase, Player aiPlayer) { newPlayer.setCommitedCrimeThisTurn(origPlayer.getCommittedCrimeThisTurn()); newPlayer.setLifeStartedThisTurnWith(origPlayer.getLifeStartedThisTurnWith()); newPlayer.setDamageReceivedThisTurn(origPlayer.getDamageReceivedThisTurn()); - newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn()); newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters())); newPlayer.setSpeed(origPlayer.getSpeed()); newPlayer.setBlessing(origPlayer.hasBlessing(), null); diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index fce3fd4a6ed..692ee2231c1 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -758,7 +758,7 @@ public final Card moveTo(final ZoneType name, final Card c, final int libPositio case Stack -> moveToStack(c, cause, params); case PlanarDeck, SchemeDeck, AttractionDeck, ContraptionDeck -> moveToVariantDeck(c, name, libPosition, cause, params); case Junkyard -> moveToJunkyard(c, cause, params); - default -> moveTo(c.getOwner().getZone(name), c, cause); // sideboard will also get there + default -> moveTo(c.getOwner().getZone(name), c, cause, params); // sideboard will also get there }; } catch (Exception e) { String msg = "GameAction:moveTo: Exception occurred"; diff --git a/forge-game/src/main/java/forge/game/GameSnapshot.java b/forge-game/src/main/java/forge/game/GameSnapshot.java index 716b4367f7d..734a8a357b4 100644 --- a/forge-game/src/main/java/forge/game/GameSnapshot.java +++ b/forge-game/src/main/java/forge/game/GameSnapshot.java @@ -173,7 +173,6 @@ public void assignPlayerState(Player origPlayer, Player newPlayer) { newPlayer.setLifeGainedThisTurn(origPlayer.getLifeGainedThisTurn()); newPlayer.setLifeStartedThisTurnWith(origPlayer.getLifeStartedThisTurnWith()); newPlayer.setDamageReceivedThisTurn(origPlayer.getDamageReceivedThisTurn()); - newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn()); newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters())); newPlayer.setBlessing(origPlayer.hasBlessing(), null); newPlayer.setLibrarySearched(origPlayer.getLibrarySearched()); 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 97a5399df12..8a2c1847fd3 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -3523,8 +3523,14 @@ public static int playerXProperty(final Player player, final String s, final Car return doXMath(Aggregates.sum(player.getCardsIn(ZoneType.Library, 1), Card::getCMC), m, source, ctb); } - if (value.contains("LandsPlayed")) { - return doXMath(player.getLandsPlayedThisTurn(), m, source, ctb); + if (value.startsWith("LandsPlayed")) { + List list = player.getLandsPlayedThisTurn(); + if (l[0].contains(" ")) { + String[] lparts = l[0].split(" ", 2); + String restrictions = TextUtil.fastReplace(l[0], TextUtil.addSuffix(lparts[0]," "), ""); + list = CardLists.getValidCardsAsList(list, restrictions, player, source, ctb); + } + return doXMath(list.size(), m, source, ctb); } if (value.contains("SpellsCastThisTurn")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java index 92fbc180d36..242762d7c21 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java @@ -63,7 +63,7 @@ public void resolve(SpellAbility sa) { p.clearCounters(); p.resetSpellCastThisGame(); p.onCleanupPhase(); - p.setLandsPlayedLastTurn(0); + p.setLandsPlayedMyLastTurn(0); p.setSpellsCastLastTurn(0); p.setLifeLostLastTurn(0); p.resetCommanderStats(); 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 9f10fe91aee..3ada0d7ce7e 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -97,8 +97,7 @@ public class Player extends GameEntity implements Comparable { private int numExploredThisTurn; private int numTokenCreatedThisTurn; private int numForetoldThisTurn; - private int landsPlayedThisTurn; - private int landsPlayedLastTurn; + private int landsPlayedMyLastTurn; private int numPowerSurgeLands; private int spellsCastThisTurn; private int spellsCastThisGame; @@ -122,6 +121,7 @@ public class Player extends GameEntity implements Comparable { private List discardedThisTurn = new ArrayList<>(); private List sacrificedThisTurn = new ArrayList<>(); + private List landsPlayedThisTurn = new ArrayList<>(); private int simultaneousDamage = 0; @@ -1627,6 +1627,7 @@ public final void shuffle(final SpellAbility sa) { } public final Card playLand(final Card land, SpellAbility cause) { + Card lki = CardCopyService.getLKICopy(land); land.setController(this, 0); if (land.isFaceDown()) { land.turnFaceUp(null); @@ -1638,8 +1639,11 @@ public final Card playLand(final Card land, SpellAbility cause) { Map runParams = AbilityKey.mapFromCard(land); runParams.put(AbilityKey.Origin, land.getZone().getZoneType().name()); + Map moveParams = AbilityKey.newMap(); + moveParams.put(AbilityKey.CardLKI, lki); + game.copyLastState(); - final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, cause); + final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, cause, moveParams); game.updateLastStateForCard(c); // Run triggers @@ -1647,7 +1651,7 @@ public final Card playLand(final Card land, SpellAbility cause) { game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false); game.getStack().unfreezeStack(); - addLandPlayedThisTurn(); + addLandPlayedThisTurn(lki); // play a sound game.fireEvent(new GameEventLandPlayed(PlayerView.get(this), CardView.get(c))); @@ -1689,7 +1693,7 @@ public final boolean canPlayLand(final Card land, final boolean ignoreZoneAndTim } // check for adjusted max lands play per turn - return getLandsPlayedThisTurn() < getMaxLandPlays(); + return getLandsPlayedThisTurn().size() < getMaxLandPlays(); } public final int getMaxLandPlays() { @@ -2229,32 +2233,26 @@ public void setStartingHandSize(int shs) { startingHandSize = shs; } - public final int getLandsPlayedThisTurn() { + public final List getLandsPlayedThisTurn() { return landsPlayedThisTurn; } - public final int getLandsPlayedLastTurn() { - return landsPlayedLastTurn; + public final int getLandsPlayedMyLastTurn() { + return landsPlayedMyLastTurn; } - public final void addLandPlayedThisTurn() { - landsPlayedThisTurn++; + public final void addLandPlayedThisTurn(Card lki) { + landsPlayedThisTurn.add(lki); achievementTracker.landsPlayed++; view.updateNumLandThisTurn(this); } public final void resetLandsPlayedThisTurn() { - landsPlayedThisTurn = 0; - view.updateNumLandThisTurn(this); - } - public final void setLandsPlayedThisTurn(int num) { - // This method should only be used directly when setting up the game state. - landsPlayedThisTurn = num; - + landsPlayedThisTurn.clear(); view.updateNumLandThisTurn(this); } - public final void setLandsPlayedLastTurn(int num) { - landsPlayedLastTurn = num; + public final void setLandsPlayedMyLastTurn(int num) { + landsPlayedMyLastTurn = num; } public final void setNumDrawnLastTurn(int num) { - numDrawnLastTurn= num; + numDrawnLastTurn = num; } public final int getInvestigateNumThisTurn() { @@ -2486,8 +2484,7 @@ public void onCleanupPhase() { resetNumTokenCreatedThisTurn(); setNumCardsInHandStartedThisTurnWith(getCardsIn(ZoneType.Hand).size()); setTappedLandForManaThisTurn(false); - setLandsPlayedLastTurn(getLandsPlayedThisTurn()); - resetLandsPlayedThisTurn(); + resetInvestigatedThisTurn(); resetSurveilThisTurn(); resetDiscardedThisTurn(); @@ -2518,9 +2515,11 @@ public void onCleanupPhase() { if (game.getPhaseHandler().isPlayerTurn(this)) { setBeenDealtCombatDamageSinceLastTurn(false); setAttackedPlayersMyLastTurn(getAttackedPlayersMyTurn()); + setLandsPlayedMyLastTurn(getLandsPlayedThisTurn().size()); clearAttackedMyTurn(); this.lastTurnNr = game.getPhaseHandler().getTurn(); } + resetLandsPlayedThisTurn(); } public boolean canCastSorcery() { diff --git a/forge-game/src/main/java/forge/game/player/PlayerView.java b/forge-game/src/main/java/forge/game/player/PlayerView.java index daddba99a62..b4a4cd4f3f8 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerView.java +++ b/forge-game/src/main/java/forge/game/player/PlayerView.java @@ -279,7 +279,7 @@ public int getNumLandThisTurn() { return get(TrackableProperty.NumLandThisTurn); } void updateNumLandThisTurn(Player p) { - set(TrackableProperty.NumLandThisTurn, p.getLandsPlayedThisTurn()); + set(TrackableProperty.NumLandThisTurn, p.getLandsPlayedThisTurn().size()); } public int getNumManaShards() { diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerLandPlayed.java b/forge-game/src/main/java/forge/game/trigger/TriggerLandPlayed.java index 9c146e9d03a..13a065a2138 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerLandPlayed.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerLandPlayed.java @@ -92,7 +92,7 @@ public final boolean performTest(final Map runParams) { if (hasParam("NotFirstLand")) { Card land = (Card) runParams.get(AbilityKey.Card); - if (land.getController().getLandsPlayedThisTurn() < 1) { + if (land.getController().getLandsPlayedThisTurn().isEmpty()) { return false; } } diff --git a/forge-gui/res/cardsfolder/rebalanced/a-visions_of_phyrexia.txt b/forge-gui/res/cardsfolder/rebalanced/a-visions_of_phyrexia.txt index 568f04e7896..0fef90e0e31 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-visions_of_phyrexia.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-visions_of_phyrexia.txt @@ -11,11 +11,7 @@ SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | CheckSVar$ X | SVarCompare$ EQ0 | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, if you didn't play a card from exile this turn, create a tapped Powerstone token. SVar:TrigToken:DB$ Token | TokenTapped$ True | TokenScript$ c_a_powerstone -T:Mode$ LandPlayed | Origin$ Exile | ValidCard$ Land.YouCtrl | Execute$ StoreVar | Static$ True -SVar:StoreVar:DB$ StoreSVar | SVar$ LandsPlayedFromExile | Type$ Number | Expression$ 1 -T:Mode$ Phase | Phase$ Cleanup | Execute$ TrigReset | Static$ True -SVar:TrigReset:DB$ StoreSVar | SVar$ LandsPlayedFromExile | Type$ Number | Expression$ 0 SVar:X:Count$ThisTurnCast_Card.YouCtrl+wasCastFromExile/Plus.LandsPlayedFromExile -SVar:LandsPlayedFromExile:Number$0 +SVar:LandsPlayedFromExile:PlayerCountPropertyYou$LandsPlayed Land.inZoneExile DeckHas:Ability$Token & Type$Artifact Oracle:At the beginning of your upkeep, exile the top two cards of your library. You may play one of those cards this turn.\nAt the beginning of your end step, if you didn't play a card from exile this turn, create a tapped Powerstone token. diff --git a/forge-gui/res/cardsfolder/s/spider_man_2099.txt b/forge-gui/res/cardsfolder/s/spider_man_2099.txt index 1092d3418ba..0f17ad32b60 100644 --- a/forge-gui/res/cardsfolder/s/spider_man_2099.txt +++ b/forge-gui/res/cardsfolder/s/spider_man_2099.txt @@ -10,10 +10,6 @@ SVar:Z:Count$YourTurns T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | CheckSVar$ Y | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, CARDNAME deals damage equal to his power to any target. SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ X | AILogic$ PowerDmg SVar:X:Count$CardPower -T:Mode$ LandPlayed | Origin$ Exile,Library,Graveyard | ValidCard$ Land.YouCtrl | Execute$ StoreVar | Static$ True -SVar:StoreVar:DB$ StoreSVar | SVar$ LandsPlayedFromNotHand | Type$ Number | Expression$ 1 -T:Mode$ Phase | Phase$ Cleanup | Execute$ TrigReset | Static$ True -SVar:TrigReset:DB$ StoreSVar | SVar$ LandsPlayedFromNotHand | Type$ Number | Expression$ 0 SVar:Y:Count$ThisTurnCast_Card.YouCtrl+!wasCastFromYourHand/Plus.LandsPlayedFromNotHand -SVar:LandsPlayedFromNotHand:Number$0 +SVar:LandsPlayedFromNotHand:PlayerCountPropertyYou$LandsPlayed Land.!inZoneHand Oracle:From the Future — You can't cast Spider-Man 2099 during your first, second, or third turns of the game.\nDouble strike, vigilance\nAt the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, Spider-Man 2099 deals damage equal to his power to any target. diff --git a/forge-gui/res/cardsfolder/v/visions_of_phyrexia.txt b/forge-gui/res/cardsfolder/v/visions_of_phyrexia.txt index 32b2a3e3c40..48d0990b7ab 100644 --- a/forge-gui/res/cardsfolder/v/visions_of_phyrexia.txt +++ b/forge-gui/res/cardsfolder/v/visions_of_phyrexia.txt @@ -8,11 +8,7 @@ SVar:Play:Mode$ Continuous | MayPlay$ True | Affected$ Card.IsRemembered | Affec SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | CheckSVar$ X | SVarCompare$ EQ0 | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, if you didn't play a card from exile this turn, create a tapped Powerstone token. (It's an artifact with "{T}: Add {C}. This mana can't be spent to cast a nonartifact spell.") SVar:TrigToken:DB$ Token | TokenTapped$ True | TokenScript$ c_a_powerstone -T:Mode$ LandPlayed | Origin$ Exile | ValidCard$ Land.YouCtrl | Execute$ StoreVar | Static$ True -SVar:StoreVar:DB$ StoreSVar | SVar$ LandsPlayedFromExile | Type$ Number | Expression$ 1 -T:Mode$ Phase | Phase$ Cleanup | Execute$ TrigReset | Static$ True -SVar:TrigReset:DB$ StoreSVar | SVar$ LandsPlayedFromExile | Type$ Number | Expression$ 0 SVar:X:Count$ThisTurnCast_Card.YouCtrl+wasCastFromExile/Plus.LandsPlayedFromExile -SVar:LandsPlayedFromExile:Number$0 +SVar:LandsPlayedFromExile:PlayerCountPropertyYou$LandsPlayed Land.inZoneExile DeckHas:Ability$Token & Type$Artifact Oracle:At the beginning of your upkeep, exile the top card of your library. You may play that card this turn.\nAt the beginning of your end step, if you didn't play a card from exile this turn, create a tapped Powerstone token. (It's an artifact with "{T}: Add {C}. This mana can't be spent to cast a nonartifact spell.") diff --git a/forge-gui/res/puzzle/MTGP_01.pzl b/forge-gui/res/puzzle/MTGP_01.pzl index 22a900411be..19abec10c21 100644 --- a/forge-gui/res/puzzle/MTGP_01.pzl +++ b/forge-gui/res/puzzle/MTGP_01.pzl @@ -14,6 +14,4 @@ p0hand=Banners Raised;Electrickery;Temporary Insanity p0graveyard=Opt;Volcanic Spray p0battlefield=Mizzix of the Izmagnus|Tapped;Mountain;Mountain;Sulfur Falls|NoETBTrigs;Sulfur Falls|NoETBTrigs p1life=4 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Nebelgast Herald;Treetop Ambusher diff --git a/forge-gui/res/puzzle/MTGP_02.pzl b/forge-gui/res/puzzle/MTGP_02.pzl index 14326be338f..9d72b2fd6e8 100644 --- a/forge-gui/res/puzzle/MTGP_02.pzl +++ b/forge-gui/res/puzzle/MTGP_02.pzl @@ -11,11 +11,7 @@ activeplayer=p1 activephase=MAIN1 activephaseadvance=COMBAT_DECLARE_BLOCKERS p0life=7 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Arrow Volley Trap;Pitfall Trap;Inferno Trap p0battlefield=Viashino Fangtail;Rockslide Sorcerer;Mountain;Mountain;Plains;Plains p1life=20 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Throne of the God-Pharaoh;T:c_4_4_a_construct;T:c_4_4_a_construct;T:c_4_4_a_construct;T:c_4_4_a_construct;T:c_4_4_a_construct diff --git a/forge-gui/res/puzzle/MTGP_03.pzl b/forge-gui/res/puzzle/MTGP_03.pzl index 596ac556817..2f66ab0e09d 100644 --- a/forge-gui/res/puzzle/MTGP_03.pzl +++ b/forge-gui/res/puzzle/MTGP_03.pzl @@ -10,11 +10,7 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=3 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Renegade Wheelsmith;Chaos Charm p0battlefield=Mobile Garrison;Granger Guildmage;Knotvine Paladin;Mountain;Mountain;Mountain;Temple Garden|NoETBTrigs;Temple Garden|NoETBTrigs p1life=8 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=T:u_1_1_illusion_flying;T:u_1_1_illusion_flying;T:u_1_1_illusion_flying diff --git a/forge-gui/res/puzzle/MTGP_04.pzl b/forge-gui/res/puzzle/MTGP_04.pzl index f634956d84d..ea958740020 100644 --- a/forge-gui/res/puzzle/MTGP_04.pzl +++ b/forge-gui/res/puzzle/MTGP_04.pzl @@ -10,11 +10,7 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=5 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Dark Withering;Tahngarth's Rage;Alms of the Vein p0battlefield=Frilled Deathspitter;Niblis of Dusk;Supreme Phantom;Pyrohemia;Mountain;Mountain;Mountain;Mountain;Mountain;Watery Grave|NoETBTrigs;Watery Grave|NoETBTrigs p1life=23 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Jin-Gitaxias, Core Augur diff --git a/forge-gui/res/puzzle/MTGP_05.pzl b/forge-gui/res/puzzle/MTGP_05.pzl index 6c8229620f9..544b5e69337 100644 --- a/forge-gui/res/puzzle/MTGP_05.pzl +++ b/forge-gui/res/puzzle/MTGP_05.pzl @@ -10,12 +10,8 @@ turn=20 activeplayer=p0 activephase=UPKEEP p0life=6 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Electrostatic Bolt;Inner Calm, Outer Strength;Crash p0library=Blessings of Nature p0battlefield=Goblin Spy;Frontline Devastator;Forest;Forest;Forest;Mountain;Mountain;Mountain p1life=6 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Butcher Ghoul;Guardian Automaton;Damping Sphere diff --git a/forge-gui/res/puzzle/MTGP_06.pzl b/forge-gui/res/puzzle/MTGP_06.pzl index 757047abf29..bb528a8de9b 100644 --- a/forge-gui/res/puzzle/MTGP_06.pzl +++ b/forge-gui/res/puzzle/MTGP_06.pzl @@ -10,12 +10,8 @@ turn=1 activeplayer=p1 activephase=CLEANUP p0life=10 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Blazing Salvo p0battlefield=Oreskos Sun Guide|Tapped;Nocturnal Feeder;Axis of Mortality;Triskaidekaphobia;Savai Triome|Tapped|NoETBTrigs;Savai Triome|Tapped|NoETBTrigs;Savai Triome|Tapped|NoETBTrigs p0library=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt p1life=14 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Bogardan Firefiend diff --git a/forge-gui/res/puzzle/MTGP_08.pzl b/forge-gui/res/puzzle/MTGP_08.pzl index c46ed428d6b..f420b207e85 100644 --- a/forge-gui/res/puzzle/MTGP_08.pzl +++ b/forge-gui/res/puzzle/MTGP_08.pzl @@ -12,11 +12,7 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=2 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Scar p0battlefield=Dread Shade;Carnifex Demon|Counters:M1M1=2;Dread Shade;Swamp;Swamp;Swamp p1life=10 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Ashling the Pilgrim;Cascade Bluffs;Cascade Bluffs;Cascade Bluffs;Shivan Reef;Shivan Reef;Shivan Reef \ No newline at end of file diff --git a/forge-gui/res/puzzle/MTGP_09.pzl b/forge-gui/res/puzzle/MTGP_09.pzl index 929149a83c7..6b42b89535d 100644 --- a/forge-gui/res/puzzle/MTGP_09.pzl +++ b/forge-gui/res/puzzle/MTGP_09.pzl @@ -10,11 +10,7 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=3 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Watery Grave;Talisman of Impulse;Mana Crypt p0battlefield=Legacy Weapon;Nimbus Maze;City of Traitors;Fungal Reaches;River of Tears;Caves of Koilos p1life=-1 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Platinum Angel diff --git a/forge-gui/res/puzzle/PS_DFT2.pzl b/forge-gui/res/puzzle/PS_DFT2.pzl index 9769c07d00e..58191944af2 100644 --- a/forge-gui/res/puzzle/PS_DFT2.pzl +++ b/forge-gui/res/puzzle/PS_DFT2.pzl @@ -10,16 +10,12 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=20 -p0landsplayed=0 -p0landsplayedlastturn=0 p0numringtemptedyou=0 p0speed=3 p0hand=Krenko, Mob Boss;Full Throttle;Agatha's Soul Cauldron;Shock p0library=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt p0battlefield=Howlsquad Heavy;Breeches, Eager Pillager;Prowcatcher Specialist;Enterprising Scallywag;Mountain;Mountain;Avishkar Raceway p1life=23 -p1landsplayed=0 -p1landsplayedlastturn=0 p1numringtemptedyou=0 p1speed=0 p1battlefield=Caelorna, Coral Tyrant;Sun-Blessed Healer;Lyra Dawnbringer diff --git a/forge-gui/res/puzzle/PS_DFT3.pzl b/forge-gui/res/puzzle/PS_DFT3.pzl index 6b18d5bf556..97e744a3231 100644 --- a/forge-gui/res/puzzle/PS_DFT3.pzl +++ b/forge-gui/res/puzzle/PS_DFT3.pzl @@ -10,8 +10,6 @@ turn=1 activeplayer=p0 activephase=UPKEEP p0life=20 -p0landsplayed=0 -p0landsplayedlastturn=0 p0numringtemptedyou=0 p0speed=0 p0hand=Terror Tide;Locust Spray;Atraxa's Skitterfang;Violent Urge;Overrun @@ -22,8 +20,6 @@ p0exile= p0command= p0sideboard= p1life=10 -p1landsplayed=0 -p1landsplayedlastturn=0 p1numringtemptedyou=0 p1speed=0 p1hand= diff --git a/forge-gui/res/puzzle/PS_ELD4.pzl b/forge-gui/res/puzzle/PS_ELD4.pzl index 8c0588ab6ea..ce8f376416b 100644 --- a/forge-gui/res/puzzle/PS_ELD4.pzl +++ b/forge-gui/res/puzzle/PS_ELD4.pzl @@ -8,7 +8,6 @@ Description:Win this turn. You have not played a land yet this turn. You own an [state] humanlife=20 ailife=10 -humanlandsplayed=0 turn=1 activeplayer=human activephase=MAIN1 diff --git a/forge-gui/res/puzzle/PS_ELD9.pzl b/forge-gui/res/puzzle/PS_ELD9.pzl index 00fd396bd8a..ba566de94ae 100644 --- a/forge-gui/res/puzzle/PS_ELD9.pzl +++ b/forge-gui/res/puzzle/PS_ELD9.pzl @@ -8,7 +8,6 @@ Description:Win this turn. Your opponent has a Hypnotic Sprite in their hand. As [state] humanlife=20 ailife=13 -humanlandsplayed=0 turn=1 activeplayer=human activephase=MAIN1 diff --git a/forge-gui/res/puzzle/PS_GUEST0.pzl b/forge-gui/res/puzzle/PS_GUEST0.pzl index 2333456c88e..4ba77047e2b 100644 --- a/forge-gui/res/puzzle/PS_GUEST0.pzl +++ b/forge-gui/res/puzzle/PS_GUEST0.pzl @@ -8,7 +8,6 @@ Description:Win this turn. Start at the beginning of your draw step (card alread [state] humanlife=20 ailife=4 -humanlandsplayed=0 turn=1 activeplayer=human activephase=DRAW diff --git a/forge-gui/res/puzzle/PS_LTR1.pzl b/forge-gui/res/puzzle/PS_LTR1.pzl index f95bb70165a..00f6bdc0f9e 100644 --- a/forge-gui/res/puzzle/PS_LTR1.pzl +++ b/forge-gui/res/puzzle/PS_LTR1.pzl @@ -16,8 +16,6 @@ p0battlefield=Cankerbloom|Set:ONE|Art:3;Shelob, Child of Ungoliant|Set:LTR|Art:3 p0library=Swamp;Swamp;Swamp;Swamp;Swamp;Forest;Forest;Forest;Forest;Forest p0numringtemptedyou=1 p1life=21 -p1landsplayed=0 -p1landsplayedlastturn=0 p1numringtemptedyou=0 p1battlefield=Sméagol, Helpful Guide|Set:LTR|Art:2;Eastfarthing Farmer|Set:LTR|Art:1;Braids's Frightful Return|Set:DMU|Art:1|Counters:LORE=1;Phyrexian Fleshgorger|Set:BRO|Art:2 p1library=Swamp;Swamp;Swamp;Swamp;Swamp;Plains;Plains;Plains;Plains;Plains;Forest;Forest;Forest;Forest;Forest diff --git a/forge-gui/res/puzzle/PS_MOM1.pzl b/forge-gui/res/puzzle/PS_MOM1.pzl index a6f40d11954..32dbff99c35 100644 --- a/forge-gui/res/puzzle/PS_MOM1.pzl +++ b/forge-gui/res/puzzle/PS_MOM1.pzl @@ -10,12 +10,8 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=20 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Annihilating Glare;Boon-Bringer Valkyrie;Night Clubber p0library=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt p0battlefield=Drana and Linvala;Knight of Dusk's Shadow;Swamp;Swamp;Swamp;Shattered Sanctum;Shattered Sanctum p1life=10 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Cutthroat Centurion;Cutthroat Centurion;Mandible Justiciar diff --git a/forge-gui/res/puzzle/PS_MOM2.pzl b/forge-gui/res/puzzle/PS_MOM2.pzl index d9d6543c19d..64375527b3f 100644 --- a/forge-gui/res/puzzle/PS_MOM2.pzl +++ b/forge-gui/res/puzzle/PS_MOM2.pzl @@ -10,11 +10,7 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=20 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Rampaging Raptor;Mountain;Into the Fire;Volcanic Spite p0battlefield=Invasion of Karsus|Counters:DEFENSE=2;Invasion of Kaldheim|Counters:DEFENSE=2;Dwarven Forge-Chanter;Mountain;Mountain;Mountain;Mountain p1life=8 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Invasion of Xerex|Counters:DEFENSE=2;Swordsworn Cavalier;Swordsworn Cavalier diff --git a/forge-gui/res/puzzle/PS_MOM3.pzl b/forge-gui/res/puzzle/PS_MOM3.pzl index 798c030c4c0..a04499430c6 100644 --- a/forge-gui/res/puzzle/PS_MOM3.pzl +++ b/forge-gui/res/puzzle/PS_MOM3.pzl @@ -10,8 +10,6 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=20 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Aspirant's Ascent;Lagrella, the Magpie;White Sun's Twilight;Bladehold War-Whip;Mysterious Limousine;Coming in Hot p0library=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt p0battlefield=In the Trenches;Veil of Assimilation;Tribute to the World Tree;Bitter Reunion;T:w_1_1_soldier|Set:DMU;T:w_1_1_soldier|Set:DMU;Rockfall Vale|NoETBTrigs;Rockfall Vale|NoETBTrigs;Rockfall Vale|NoETBTrigs;Rockfall Vale|NoETBTrigs;Deserted Beach|NoETBTrigs;Deserted Beach|NoETBTrigs;Deserted Beach|NoETBTrigs;Deserted Beach|NoETBTrigs;King Darien XLVIII|Id:1 diff --git a/forge-gui/res/puzzle/PS_MOM4.pzl b/forge-gui/res/puzzle/PS_MOM4.pzl index 8665e106699..ab7c7489756 100644 --- a/forge-gui/res/puzzle/PS_MOM4.pzl +++ b/forge-gui/res/puzzle/PS_MOM4.pzl @@ -10,11 +10,7 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=20 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Furnace Reins;Mirran Banesplitter;Voldaren Thrillseeker;Enduring Bondwarden;Boon-Bringer Valkyrie p0battlefield=Trailblazing Historian;Mirror-Shield Hoplite;Plains;Plains;Mountain;Mountain;Wind-Scarred Crag|NoETBTrigs p1life=14 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=T:incubator_c_0_0_a_phyrexian|Transformed|Counters:P1P1=3 diff --git a/forge-gui/res/puzzle/PS_MOM5.pzl b/forge-gui/res/puzzle/PS_MOM5.pzl index 223c9943585..0e8bb7f0233 100644 --- a/forge-gui/res/puzzle/PS_MOM5.pzl +++ b/forge-gui/res/puzzle/PS_MOM5.pzl @@ -10,12 +10,8 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=20 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Grafted Butcher;Ironhoof Boar;Knight of Dusk's Shadow;Volcanic Spite p0library=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt p0battlefield=Etched Familiar;Etched Familiar;Swamp;Swamp;Mountain;Mountain p1life=9 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Metropolis Reformer;Tomakul Honor Guard;Guardian of Ghirapur diff --git a/forge-gui/res/puzzle/PS_ONE3.pzl b/forge-gui/res/puzzle/PS_ONE3.pzl index 26123fa3bcb..7f08bede5b2 100644 --- a/forge-gui/res/puzzle/PS_ONE3.pzl +++ b/forge-gui/res/puzzle/PS_ONE3.pzl @@ -10,11 +10,7 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=20 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Cement Shoes;Prophetic Prism p0battlefield=Kaito, Dancing Shadow|Counters:LOYALTY=3;Tezzeret, Betrayer of Flesh|Counters:LOYALTY=2;Urza, Planeswalker|Meld:The Mightstone and Weakstone|Counters:LOYALTY=7;Levitating Statue;Ichormoon Gauntlet p0library=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt p1life=21 -p1landsplayed=0 -p1landsplayedlastturn=0 diff --git a/forge-gui/res/puzzle/PS_ONE4.pzl b/forge-gui/res/puzzle/PS_ONE4.pzl index fe017d9786b..de50835f5c9 100644 --- a/forge-gui/res/puzzle/PS_ONE4.pzl +++ b/forge-gui/res/puzzle/PS_ONE4.pzl @@ -10,13 +10,9 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=5 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Volt Charge;Furnace Skullbomb;Experimental Augury;Blazing Crescendo p0library=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt p0battlefield=Tekuthal, Inquiry Dominus;Ichor Synthesizer;Serum-Core Chimera|Counters:OIL=2;Cacophony Scamp;Urabrask's Forge|Counters:OIL=2;Island;Island;Mountain;Mountain;Mountain p1life=16 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Phyrexian Vindicator;Porcelain Zealot;Porcelain Zealot;Porcelain Zealot p1counters=POISON=4 diff --git a/forge-gui/res/puzzle/PS_ONE5.pzl b/forge-gui/res/puzzle/PS_ONE5.pzl index 6560c4e3c70..7128320a60e 100644 --- a/forge-gui/res/puzzle/PS_ONE5.pzl +++ b/forge-gui/res/puzzle/PS_ONE5.pzl @@ -10,12 +10,8 @@ turn=1 activeplayer=p0 activephase=MAIN1 p0life=20 -p0landsplayed=0 -p0landsplayedlastturn=0 p0hand=Annihilating Glare;Drown in Ichor;Charforger p0library=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt p0battlefield=Churning Reservoir;Cutthroat Centurion;Cutthroat Centurion;Furnace Strider|Counters:OIL=1;Swamp;Swamp;Mountain;Mountain;Mountain p1life=8 -p1landsplayed=0 -p1landsplayedlastturn=0 p1battlefield=Drana and Linvala;Benalish Sleeper;Necrosquito|Counters:OIL=3 diff --git a/forge-gui/res/puzzle/PS_RNA8.pzl b/forge-gui/res/puzzle/PS_RNA8.pzl index a991506d33b..affebc257a3 100644 --- a/forge-gui/res/puzzle/PS_RNA8.pzl +++ b/forge-gui/res/puzzle/PS_RNA8.pzl @@ -8,7 +8,6 @@ Description:Win this turn. Assume any cards drawn from your library are irreleva [state] humanlife=20 ailife=12 -humanlandsplayed=0 turn=1 activeplayer=human activephase=MAIN1 diff --git a/forge-gui/res/puzzle/PS_RVR1.pzl b/forge-gui/res/puzzle/PS_RVR1.pzl index fbc2bcdbfeb..acb73b646a0 100644 --- a/forge-gui/res/puzzle/PS_RVR1.pzl +++ b/forge-gui/res/puzzle/PS_RVR1.pzl @@ -13,8 +13,6 @@ p0life=4 p0hand=Devouring Light;Fists of Ironwood;Overwhelm;Angelic Exaltation p0battlefield=T:g_1_1_saproling;T:g_1_1_saproling;T:g_1_1_saproling;T:g_1_1_saproling;T:g_1_1_saproling;Forest;Forest;Forest;Plains;Plains;Plains p1life=8 -p1landsplayed=0 -p1landsplayedlastturn=0 p1numringtemptedyou=0 p1library=Island;Mountain p1battlefield=Niv-Mizzet, Parun diff --git a/forge-gui/res/puzzle/PS_WAR0.pzl b/forge-gui/res/puzzle/PS_WAR0.pzl index b3055d90e96..5e07d00ab28 100644 --- a/forge-gui/res/puzzle/PS_WAR0.pzl +++ b/forge-gui/res/puzzle/PS_WAR0.pzl @@ -8,7 +8,6 @@ Description:Win this turn. You have not played a land yet. Your opponent has fou [state] humanlife=20 ailife=7 -humanlandsplayed=0 turn=1 activeplayer=human activephase=MAIN1 From 5280426c892e1729e81b19ca7cdf0c0d88e35f02 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 2 May 2026 18:20:25 +0200 Subject: [PATCH 2/4] Player: use SpellAbility List for MayPlay uses later --- .../java/forge/game/ability/AbilityUtils.java | 6 ++++-- .../src/main/java/forge/game/player/Player.java | 15 +++++++++------ .../rebalanced/a-visions_of_phyrexia.txt | 2 +- forge-gui/res/cardsfolder/s/spider_man_2099.txt | 2 +- .../res/cardsfolder/v/visions_of_phyrexia.txt | 2 +- 5 files changed, 16 insertions(+), 11 deletions(-) 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 8a2c1847fd3..5120f4045ea 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -3524,11 +3524,13 @@ public static int playerXProperty(final Player player, final String s, final Car } if (value.startsWith("LandsPlayed")) { - List list = player.getLandsPlayedThisTurn(); + List list = player.getLandsPlayedThisTurn(); if (l[0].contains(" ")) { String[] lparts = l[0].split(" ", 2); String restrictions = TextUtil.fastReplace(l[0], TextUtil.addSuffix(lparts[0]," "), ""); - list = CardLists.getValidCardsAsList(list, restrictions, player, source, ctb); + list = list.stream() + .filter(SpellAbilityPredicates.isValid(restrictions.split(","), player, source, ctb)) + .collect(Collectors.toList()); } return doXMath(list.size(), m, source, ctb); } 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 3ada0d7ce7e..e119c5d13c5 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -121,7 +121,7 @@ public class Player extends GameEntity implements Comparable { private List discardedThisTurn = new ArrayList<>(); private List sacrificedThisTurn = new ArrayList<>(); - private List landsPlayedThisTurn = new ArrayList<>(); + private List landsPlayedThisTurn = new ArrayList<>(); private int simultaneousDamage = 0; @@ -1627,7 +1627,7 @@ public final void shuffle(final SpellAbility sa) { } public final Card playLand(final Card land, SpellAbility cause) { - Card lki = CardCopyService.getLKICopy(land); + land.setController(this, 0); if (land.isFaceDown()) { land.turnFaceUp(null); @@ -1635,6 +1635,7 @@ public final Card playLand(final Card land, SpellAbility cause) { land.changeToState(cause.getCardStateName()); } } + Card lki = CardCopyService.getLKICopy(land); Map runParams = AbilityKey.mapFromCard(land); runParams.put(AbilityKey.Origin, land.getZone().getZoneType().name()); @@ -1646,12 +1647,14 @@ public final Card playLand(final Card land, SpellAbility cause) { final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, cause, moveParams); game.updateLastStateForCard(c); + SpellAbility causeLKI = cause.copy(lki, true); + // Run triggers runParams.put(AbilityKey.SpellAbility, cause); game.getTriggerHandler().runTrigger(TriggerType.LandPlayed, runParams, false); game.getStack().unfreezeStack(); - addLandPlayedThisTurn(lki); + addLandPlayedThisTurn(causeLKI); // play a sound game.fireEvent(new GameEventLandPlayed(PlayerView.get(this), CardView.get(c))); @@ -2233,14 +2236,14 @@ public void setStartingHandSize(int shs) { startingHandSize = shs; } - public final List getLandsPlayedThisTurn() { + public final List getLandsPlayedThisTurn() { return landsPlayedThisTurn; } public final int getLandsPlayedMyLastTurn() { return landsPlayedMyLastTurn; } - public final void addLandPlayedThisTurn(Card lki) { - landsPlayedThisTurn.add(lki); + public final void addLandPlayedThisTurn(SpellAbility landLki) { + landsPlayedThisTurn.add(landLki); achievementTracker.landsPlayed++; view.updateNumLandThisTurn(this); } diff --git a/forge-gui/res/cardsfolder/rebalanced/a-visions_of_phyrexia.txt b/forge-gui/res/cardsfolder/rebalanced/a-visions_of_phyrexia.txt index 0fef90e0e31..0299941d46a 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-visions_of_phyrexia.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-visions_of_phyrexia.txt @@ -12,6 +12,6 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | CheckSVar$ X | SVarCompare$ EQ0 | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, if you didn't play a card from exile this turn, create a tapped Powerstone token. SVar:TrigToken:DB$ Token | TokenTapped$ True | TokenScript$ c_a_powerstone SVar:X:Count$ThisTurnCast_Card.YouCtrl+wasCastFromExile/Plus.LandsPlayedFromExile -SVar:LandsPlayedFromExile:PlayerCountPropertyYou$LandsPlayed Land.inZoneExile +SVar:LandsPlayedFromExile:PlayerCountPropertyYou$LandsPlayed SpellAbility.inZoneExile DeckHas:Ability$Token & Type$Artifact Oracle:At the beginning of your upkeep, exile the top two cards of your library. You may play one of those cards this turn.\nAt the beginning of your end step, if you didn't play a card from exile this turn, create a tapped Powerstone token. diff --git a/forge-gui/res/cardsfolder/s/spider_man_2099.txt b/forge-gui/res/cardsfolder/s/spider_man_2099.txt index 0f17ad32b60..003a3c1bed4 100644 --- a/forge-gui/res/cardsfolder/s/spider_man_2099.txt +++ b/forge-gui/res/cardsfolder/s/spider_man_2099.txt @@ -11,5 +11,5 @@ T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | CheckSVar$ Y | TriggerZo SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ X | AILogic$ PowerDmg SVar:X:Count$CardPower SVar:Y:Count$ThisTurnCast_Card.YouCtrl+!wasCastFromYourHand/Plus.LandsPlayedFromNotHand -SVar:LandsPlayedFromNotHand:PlayerCountPropertyYou$LandsPlayed Land.!inZoneHand +SVar:LandsPlayedFromNotHand:PlayerCountPropertyYou$LandsPlayed SpellAbility.!inZoneHand Oracle:From the Future — You can't cast Spider-Man 2099 during your first, second, or third turns of the game.\nDouble strike, vigilance\nAt the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, Spider-Man 2099 deals damage equal to his power to any target. diff --git a/forge-gui/res/cardsfolder/v/visions_of_phyrexia.txt b/forge-gui/res/cardsfolder/v/visions_of_phyrexia.txt index 48d0990b7ab..fa5f8a6d25b 100644 --- a/forge-gui/res/cardsfolder/v/visions_of_phyrexia.txt +++ b/forge-gui/res/cardsfolder/v/visions_of_phyrexia.txt @@ -9,6 +9,6 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | CheckSVar$ X | SVarCompare$ EQ0 | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, if you didn't play a card from exile this turn, create a tapped Powerstone token. (It's an artifact with "{T}: Add {C}. This mana can't be spent to cast a nonartifact spell.") SVar:TrigToken:DB$ Token | TokenTapped$ True | TokenScript$ c_a_powerstone SVar:X:Count$ThisTurnCast_Card.YouCtrl+wasCastFromExile/Plus.LandsPlayedFromExile -SVar:LandsPlayedFromExile:PlayerCountPropertyYou$LandsPlayed Land.inZoneExile +SVar:LandsPlayedFromExile:PlayerCountPropertyYou$LandsPlayed SpellAbility.inZoneExile DeckHas:Ability$Token & Type$Artifact Oracle:At the beginning of your upkeep, exile the top card of your library. You may play that card this turn.\nAt the beginning of your end step, if you didn't play a card from exile this turn, create a tapped Powerstone token. (It's an artifact with "{T}: Add {C}. This mana can't be spent to cast a nonartifact spell.") From a95246a3c408714db44a82fc9ef7141c90f29128 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 3 May 2026 08:38:10 +0200 Subject: [PATCH 3/4] StaticAbility: calculate used MayPlayLimit --- .../src/main/java/forge/game/card/Card.java | 7 ------- .../forge/game/spellability/LandAbility.java | 4 ---- .../game/staticability/StaticAbility.java | 20 ++++++------------- .../main/java/forge/game/zone/MagicStack.java | 1 - 4 files changed, 6 insertions(+), 26 deletions(-) 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 c3eded97553..3abb4341aee 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3897,12 +3897,6 @@ public final Map setMayPlay(Map layers; private CardCollectionView ignoreEffectCards = new CardCollection(); private final List ignoreEffectPlayers = Lists.newArrayList(); - private int mayPlayTurn = 0; private SpellAbility payingTrigSA; private StaticAbilityView view = null; @@ -552,15 +551,12 @@ public Set getLayers() { } public int getMayPlayTurn() { - return mayPlayTurn; - } - - public void incMayPlayTurn() { - this.mayPlayTurn++; - } - - public void resetMayPlayTurn() { - this.mayPlayTurn = 0; + final Player controller = this.getHostCard().getController(); + int castSpells = (int) this.getHostCard().getGame().getStack().getSpellsCastThisTurn().stream() + .map(Card::getCastSA).filter(s -> s != null && s.getActivatingPlayer().equals(controller) && this.equals(s.getMayPlay())) + .count(); + int landPlayed = (int) controller.getLandsPlayedThisTurn().stream().filter(s -> this.equals(s.getMayPlay())).count(); + return castSpells + landPlayed; } @Override @@ -600,10 +596,6 @@ public StaticAbility copy(Card host, final boolean lki, boolean keepTextChanges) // reset to force refresh if needed clone.payingTrigSA = null; - if (!lki) { - clone.mayPlayTurn = 0; - } - clone.layers = this.generateLayer(); if (validHostZones != null) { clone.setActiveZone(EnumSet.copyOf(validHostZones)); diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 6efcc01cf8c..8502b18469f 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -537,7 +537,6 @@ private SpellAbilityStackInstance push(final SpellAbility sp, SpellAbilityStackI } if (sp.isSpell() && sp.getMayPlay() != null) { - sp.getMayPlay().incMayPlayTurn(); if (sp.getMayPlay().hasParam("ReplaceGraveyard")) { PlayEffect.addReplaceGraveyardEffect(sp.getHostCard(), sp.getMayPlay().getHostCard(), sp, sp, sp.getMayPlay().getParam("ReplaceGraveyard")); } From 960d239522be832cf5f0a76f9e90f4ac9e017160 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 3 May 2026 18:41:32 +0200 Subject: [PATCH 4/4] StaticAbility: fix MayPlayLimit for Ian Malcom, Chaotician --- .../game/staticability/StaticAbility.java | 7 ++--- .../StaticAbilityContinuous.java | 31 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) 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 babc97f9c5f..e14bd931f14 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -550,12 +550,11 @@ public Set getLayers() { return layers; } - public int getMayPlayTurn() { - final Player controller = this.getHostCard().getController(); + public int getMayPlayTurn(final Player player) { int castSpells = (int) this.getHostCard().getGame().getStack().getSpellsCastThisTurn().stream() - .map(Card::getCastSA).filter(s -> s != null && s.getActivatingPlayer().equals(controller) && this.equals(s.getMayPlay())) + .map(Card::getCastSA).filter(s -> s != null && s.getActivatingPlayer().equals(player) && this.equals(s.getMayPlay())) .count(); - int landPlayed = (int) controller.getLandsPlayedThisTurn().stream().filter(s -> this.equals(s.getMayPlay())).count(); + int landPlayed = (int) player.getLandsPlayedThisTurn().stream().filter(s -> this.equals(s.getMayPlay())).count(); return castSpells + landPlayed; } 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 778c556811a..80b6664a981 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -886,7 +886,7 @@ public static CardCollectionView applyContinuousAbility(final StaticAbility stAb } } - if (controllerMayPlay && (mayPlayLimit == null || stAb.getMayPlayTurn() < mayPlayLimit)) { + if (controllerMayPlay) { String mayPlayAltCost = mayPlayAltManaCost; if (mayPlayAltCost != null) { @@ -899,21 +899,24 @@ public static CardCollectionView applyContinuousAbility(final StaticAbility stAb Player mayPlayController = params.containsKey("MayPlayPlayer") ? AbilityUtils.getDefinedPlayers(affectedCard, params.get("MayPlayPlayer"), stAb).get(0) : controller; - affectedCard.setMayPlay(mayPlayController, mayPlayWithoutManaCost, - mayPlayAltCost != null ? new Cost(mayPlayAltCost, false, affectedCard.equals(hostCard)) : null, mayPlayWithFlash, - mayPlayGrantZonePermissions, stAb); - if (mayLookAt != null && mayLookAt.isEmpty()) { - mayLookAt.add(mayPlayController); - } + if (mayPlayLimit == null || stAb.getMayPlayTurn(mayPlayController) < mayPlayLimit) { + affectedCard.setMayPlay(mayPlayController, mayPlayWithoutManaCost, + mayPlayAltCost != null ? new Cost(mayPlayAltCost, false, affectedCard.equals(hostCard)) : null, mayPlayWithFlash, + mayPlayGrantZonePermissions, stAb); + + if (mayLookAt != null && mayLookAt.isEmpty()) { + mayLookAt.add(mayPlayController); + } - // If the MayPlay effect only affected itself, check if it is in graveyard and give other player who cast Shaman's Trance MayPlay - if (stAb.hasParam("Affected") && stAb.getParam("Affected").equals("Card.Self") && affectedCard.isInZone(ZoneType.Graveyard)) { - for (final Player p : game.getPlayers()) { - if (p.hasKeyword("Shaman's Trance") && mayPlayController != p) { - affectedCard.setMayPlay(p, mayPlayWithoutManaCost, - mayPlayAltCost != null ? new Cost(mayPlayAltCost, false) : null, - mayPlayWithFlash, mayPlayGrantZonePermissions, stAb); + // If the MayPlay effect only affected itself, check if it is in graveyard and give other player who cast Shaman's Trance MayPlay + if (stAb.hasParam("Affected") && stAb.getParam("Affected").equals("Card.Self") && affectedCard.isInZone(ZoneType.Graveyard)) { + for (final Player p : game.getPlayers()) { + if (p.hasKeyword("Shaman's Trance") && mayPlayController != p) { + affectedCard.setMayPlay(p, mayPlayWithoutManaCost, + mayPlayAltCost != null ? new Cost(mayPlayAltCost, false) : null, + mayPlayWithFlash, mayPlayGrantZonePermissions, stAb); + } } } }