From d6422688b8c83b851e76360c3cc73acd517f6d2c Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 13 Apr 2024 19:11:08 +0200 Subject: [PATCH] MagicStack: ThisTurnActivated --- .../java/forge/game/ability/AbilityUtils.java | 14 ++-- .../java/forge/game/card/CardFactoryUtil.java | 2 - .../main/java/forge/game/card/CardUtil.java | 6 ++ .../main/java/forge/game/player/Player.java | 68 ++++++------------- .../game/spellability/AlternativeCost.java | 1 - .../forge/game/spellability/SpellAbility.java | 2 +- .../main/java/forge/game/zone/MagicStack.java | 21 ++++-- .../cardsfolder/b/bruenor_battlehammer.txt | 2 +- forge-gui/res/cardsfolder/f/forge_anew.txt | 2 +- .../res/cardsfolder/g/gavi_nest_warden.txt | 2 +- .../res/cardsfolder/s/spellpyre_phoenix.txt | 2 +- 11 files changed, 53 insertions(+), 69 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 5067499e01b..ccf695ca712 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -2269,14 +2269,6 @@ public class AbilityUtils { return doXMath(player.getOpponentsGreatestLifeTotal(), expr, c, ctb); } - if (sq[0].equals("YouCycledThisTurn")) { - return doXMath(player.getCycledThisTurn(), expr, c, ctb); - } - - if (sq[0].equals("YouEquippedThisTurn")) { - return doXMath(player.getEquippedThisTurn(), expr, c, ctb); - } - if (sq[0].equals("YouDrewThisTurn")) { return doXMath(player.getNumDrawnThisTurn(), expr, c, ctb); } @@ -2727,6 +2719,12 @@ public class AbilityUtils { someCards = CardUtil.getLastTurnCast(validFilter, c, ctb, player); } } + if (sq[0].startsWith("ThisTurnActivated")) { + final String[] workingCopy = paidparts[0].split("_"); + final String validFilter = workingCopy[1]; + // use objectXCount ? + return CardUtil.getThisTurnActivated(validFilter, c, ctb, player).size(); + } // Count$ThisTurnEntered [from ] if (sq[0].startsWith("ThisTurnEntered") || sq[0].startsWith("LastTurnEntered")) { diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index f59603595af..f1de0e14568 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3679,7 +3679,6 @@ public class CardFactoryUtil { sb.append("| SpellDescription$ (").append(inst.getReminderText()).append(")"); SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card); - sa.setAlternativeCost(AlternativeCost.Cycling); sa.setIntrinsic(intrinsic); inst.addSpellAbility(sa); } else if (keyword.startsWith("TypeCycling")) { @@ -3704,7 +3703,6 @@ public class CardFactoryUtil { sb.append(" | SpellDescription$ (").append(inst.getReminderText()).append(")"); SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card); - sa.setAlternativeCost(AlternativeCost.Cycling); sa.setIntrinsic(intrinsic); inst.addSpellAbility(sa); } diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index 36c7fe145e8..1bcef5c9489 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Set; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -36,6 +37,7 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityPredicates; import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import forge.util.TextUtil; @@ -138,6 +140,10 @@ public final class CardUtil { return CardLists.getValidCardsAsList(src.getGame().getStack().getSpellsCastLastTurn(), valid, controller, src, ctb); } + public static List getThisTurnActivated(final String valid, final Card src, final CardTraitBase ctb, final Player controller) { + return Lists.newArrayList(Iterables.filter(src.getGame().getStack().getAbilityActivatedThisTurn(), SpellAbilityPredicates.isValid(valid.split(","), controller, src, ctb))); + } + public static CardCollection getRadiance(final SpellAbility sa) { SpellAbility targetSA = sa.getSATargetingCard(); if (targetSA == null || !targetSA.usesTargeting() || !targetSA.hasParam("Radiance")) { 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 5744a10806c..e02980d93f9 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -92,8 +92,6 @@ public class Player extends GameEntity implements Comparable { private int landsPlayedLastTurn; private int investigatedThisTurn; private int surveilThisTurn; - private int cycledThisTurn; - private int equippedThisTurn; private int lifeLostThisTurn; private int lifeLostLastTurn; private int lifeGainedThisTurn; @@ -2482,8 +2480,6 @@ public class Player extends GameEntity implements Comparable { resetLandsPlayedThisTurn(); resetInvestigatedThisTurn(); resetSurveilThisTurn(); - resetCycledThisTurn(); - resetEquippedThisTurn(); resetDiscardedThisTurn(); resetSacrificedThisTurn(); resetVenturedThisTurn(); @@ -2969,23 +2965,23 @@ public class Player extends GameEntity implements Comparable { public boolean allCardsUniqueManaSymbols() { for (final Card c : getCardsIn(ZoneType.Library)) { Set cardStateNames = c.isSplitCard() ? EnumSet.of(CardStateName.LeftSplit, CardStateName.RightSplit) : EnumSet.of(CardStateName.Original); - Set coloredManaSymbols = new HashSet<>(); - Set genericManaSymbols = new HashSet<>(); + Set coloredManaSymbols = new HashSet<>(); + Set genericManaSymbols = new HashSet<>(); - for (final CardStateName cardStateName : cardStateNames) { - final ManaCost manaCost = c.getState(cardStateName).getManaCost(); - for (final ManaCostShard manaSymbol : manaCost) { - if (!coloredManaSymbols.add(manaSymbol)) { - return false; - } - } - int generic = manaCost.getGenericCost(); - if (generic > 0 || manaCost.getCMC() == 0) { - if (!genericManaSymbols.add(Integer.valueOf(generic))) { - return false; - } - } - } + for (final CardStateName cardStateName : cardStateNames) { + final ManaCost manaCost = c.getState(cardStateName).getManaCost(); + for (final ManaCostShard manaSymbol : manaCost) { + if (!coloredManaSymbols.add(manaSymbol)) { + return false; + } + } + int generic = manaCost.getGenericCost(); + if (generic > 0 || manaCost.getCMC() == 0) { + if (!genericManaSymbols.add(Integer.valueOf(generic))) { + return false; + } + } + } } return true; } @@ -3028,9 +3024,9 @@ public class Player extends GameEntity implements Comparable { legalCompanions.add(c); } } else if (specialRules.equals("UniqueManaSymbols")) { - if (this.allCardsUniqueManaSymbols()) { - legalCompanions.add(c); - } + if (this.allCardsUniqueManaSymbols()) { + legalCompanions.add(c); + } } else if (specialRules.equals("DeckSizePlus20")) { // +20 deck size to min deck size if (deckSize >= minSize + 20) { @@ -3114,7 +3110,7 @@ public class Player extends GameEntity implements Comparable { else if (game.getRules().hasAppliedVariant(GameType.Oathbreaker)) { moved += " | Destination$ Graveyard,Exile,Hand,Library | Description$ If a commander would be exiled or put into hand, graveyard, or library from anywhere, that player may put it into the command zone instead."; } else { - // rule 903.9b + // rule 903.9b moved += " | Destination$ Hand,Library | Description$ If a commander would be put into its owner's hand or library from anywhere, its owner may put it into the command zone instead."; } ReplacementEffect re = ReplacementHandler.parseReplacement(moved, eff, true); @@ -3154,7 +3150,7 @@ public class Player extends GameEntity implements Comparable { SpellAbility planarRoll = AbilityFactory.getAbility(specialA, eff); planarRoll.setSVar("X", "Count$PlanarDiceSpecialActionThisTurn"); eff.addSpellAbility(planarRoll); - + eff.updateStateForView(); com.add(eff); this.updateZoneForView(com); @@ -3691,33 +3687,13 @@ public class Player extends GameEntity implements Comparable { } public void addCycled(SpellAbility sp) { - cycledThisTurn++; - Map cycleParams = AbilityKey.mapFromCard(CardCopyService.getLKICopy(game.getCardState(sp.getHostCard()))); cycleParams.put(AbilityKey.Cause, sp); cycleParams.put(AbilityKey.Player, this); - cycleParams.put(AbilityKey.FirstTime, cycledThisTurn == 1); + cycleParams.put(AbilityKey.FirstTime, CardUtil.getThisTurnActivated("Activated.Cycling+YouCtrl", sp.getHostCard(), sp, this).size() == 1); game.getTriggerHandler().runTrigger(TriggerType.Cycled, cycleParams, false); } - public int getCycledThisTurn() { - return cycledThisTurn; - } - - public void resetCycledThisTurn() { - cycledThisTurn = 0; - } - - public void addEquipped() { equippedThisTurn++; } - - public int getEquippedThisTurn() { - return equippedThisTurn; - } - - public void resetEquippedThisTurn() { - equippedThisTurn = 0; - } - public boolean hasUrzaLands() { final CardCollectionView landsControlled = getCardsIn(ZoneType.Battlefield); return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine"))) diff --git a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java index 6578f361be6..32710b509e6 100644 --- a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java +++ b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java @@ -4,7 +4,6 @@ public enum AlternativeCost { Awaken, Bestow, Blitz, - Cycling, // ActivatedAbility Dash, Disturb, Emerge, 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 4d43d54ee2c..c5855bb8bd7 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -560,7 +560,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean isCycling() { - return isAlternativeCost(AlternativeCost.Cycling); + return isKeyword(Keyword.CYCLING) || isKeyword(Keyword.TYPECYCLING); } public boolean isBackup() { 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 003c9786cd0..2eaa04df70b 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -85,6 +85,8 @@ public class MagicStack /* extends MyObservable */ implements Iterable thisTurnCast = Lists.newArrayList(); private List lastTurnCast = Lists.newArrayList(); + private final List abilitiesActivatedThisTurn = Lists.newArrayList(); + private Card curResolvingCard = null; private final Map> commandList = Maps.newHashMap(); @@ -263,7 +265,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable runParams = AbilityKey.mapFromPlayer(source.getController()); runParams.put(AbilityKey.Cost, sp.getPayCosts()); @@ -336,7 +338,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable getAbilityActivatedThisTurn() { + return abilitiesActivatedThisTurn; + } + public final void addCastCommand(final String valid, final GameCommand c) { if (commandList.containsKey(valid)) { commandList.get(valid).add(0, c); diff --git a/forge-gui/res/cardsfolder/b/bruenor_battlehammer.txt b/forge-gui/res/cardsfolder/b/bruenor_battlehammer.txt index 6cd7b74966c..72928db9880 100644 --- a/forge-gui/res/cardsfolder/b/bruenor_battlehammer.txt +++ b/forge-gui/res/cardsfolder/b/bruenor_battlehammer.txt @@ -5,5 +5,5 @@ PT:5/3 S:Mode$ Continuous | Affected$ Creature.YouCtrl | AffectedZone$ Battlefield | AddPower$ AffectedX | Description$ Each creature you control gets +2/+0 for each Equipment attached to it. SVar:AffectedX:Count$Valid Equipment.Attached/Times.2 S:Mode$ Continuous | Affected$ You | AddKeyword$ You may pay 0 rather than pay equip costs. | CheckSVar$ X | SVarCompare$ LT1 | Description$ You may pay {0} rather than pay the equip cost of the first equip ability you activate each turn. -SVar:X:Count$YouEquippedThisTurn +SVar:X:Count$ThisTurnActivated_Activated.Equip+YouCtrl Oracle:Each creature you control gets +2/+0 for each Equipment attached to it.\nYou may pay {0} rather than pay the equip cost of the first equip ability you activate each turn. diff --git a/forge-gui/res/cardsfolder/f/forge_anew.txt b/forge-gui/res/cardsfolder/f/forge_anew.txt index 3bcbfcea69c..2a8ca409d90 100644 --- a/forge-gui/res/cardsfolder/f/forge_anew.txt +++ b/forge-gui/res/cardsfolder/f/forge_anew.txt @@ -5,7 +5,7 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Card.Equipment+YouCtrl S:Mode$ CastWithFlash | ValidSA$ Activated.Equip | Caster$ You | Condition$ PlayerTurn | Description$ As long as it's your turn, you may activate equip abilities any time you could cast an instant. S:Mode$ Continuous | Affected$ You | AddKeyword$ You may pay 0 rather than pay equip costs. | CheckSVar$ X | SVarCompare$ LT1 | Condition$ PlayerTurn | Description$ You may pay {0} rather than pay the equip cost of the first equip ability you activate during each of your turns. -SVar:X:Count$YouEquippedThisTurn +SVar:X:Count$ThisTurnActivated_Activated.Equip+YouCtrl DeckHas:Ability$Graveyard DeckNeeds:Type$Equipment Oracle:When Forge Anew enters the battlefield, return target Equipment card from your graveyard to the battlefield.\nAs long as it's your turn, you may activate equip abilities any time you could cast an instant.\nYou may pay {0} rather than pay the equip cost of the first equip ability you activate during each of your turns. diff --git a/forge-gui/res/cardsfolder/g/gavi_nest_warden.txt b/forge-gui/res/cardsfolder/g/gavi_nest_warden.txt index 058c34b7ce6..6101fd964a6 100644 --- a/forge-gui/res/cardsfolder/g/gavi_nest_warden.txt +++ b/forge-gui/res/cardsfolder/g/gavi_nest_warden.txt @@ -3,7 +3,7 @@ ManaCost:2 U R W Types:Legendary Creature Human Shaman PT:2/5 S:Mode$ Continuous | Affected$ You | AddKeyword$ CyclingForZero | CheckSVar$ X | SVarCompare$ LT1 | Description$ You may pay {0} rather than pay the cycling cost of the first card you cycle each turn. -SVar:X:Count$YouCycledThisTurn +SVar:X:Count$ThisTurnActivated_Activated.Cycling+YouCtrl T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 2 | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you draw your second card each turn, create a 2/2 red and white Dinosaur Cat creature token. SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ rw_2_2_dinosaur_cat | TokenOwner$ You DeckHas:Ability$Token diff --git a/forge-gui/res/cardsfolder/s/spellpyre_phoenix.txt b/forge-gui/res/cardsfolder/s/spellpyre_phoenix.txt index fc6d02f6bb3..3b9785138d8 100644 --- a/forge-gui/res/cardsfolder/s/spellpyre_phoenix.txt +++ b/forge-gui/res/cardsfolder/s/spellpyre_phoenix.txt @@ -7,5 +7,5 @@ T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefi SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Instant.YouOwn+withCycling,Instant.YouOwn+withTypeCycling,Sorcery.YouOwn+withCycling,Sorcery.YouOwn+withTypeCycling | TgtPrompt$ Select target instant or sorcery card with a cycling ability from your graveyard T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Graveyard | CheckSVar$ YouCycled | SVarCompare$ GE2 | Execute$ TrigReturn | TriggerDescription$ At the beginning of each end step, if you cycled two or more cards this turn, return CARDNAME from your graveyard to your hand. SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Hand -SVar:YouCycled:Count$YouCycledThisTurn +SVar:YouCycled:Count$ThisTurnActivated_Activated.Cycling+YouCtrl Oracle:Flying\nWhen Spellpyre Phoenix enters the battlefield, you may return target instant or sorcery card with a cycling ability from your graveyard to your hand.\nAt the beginning of each end step, if you cycled two or more cards this turn, return Spellpyre Phoenix from your graveyard to your hand.