diff --git a/forge-game/src/main/java/forge/game/GlobalRuleChange.java b/forge-game/src/main/java/forge/game/GlobalRuleChange.java index 26480436bba..8c7e3e535db 100644 --- a/forge-game/src/main/java/forge/game/GlobalRuleChange.java +++ b/forge-game/src/main/java/forge/game/GlobalRuleChange.java @@ -26,8 +26,6 @@ public enum GlobalRuleChange { attackerChoosesBlockers ("The attacking player chooses how each creature blocks each combat."), manaBurn ("A player losing unspent mana causes that player to lose that much life."), noNight ("It can't become night."), - /* onlyOneAttackerATurn ("No more than one creature can attack each turn."), */ - onlyOneAttackerACombat ("No more than one creature can attack each combat."), onlyOneBlocker ("No more than one creature can block each combat."), onlyOneBlockerPerOpponent ("Each opponent can't block with more than one creature."), onlyTwoBlockers ("No more than two creatures can block each combat."), diff --git a/forge-game/src/main/java/forge/game/combat/GlobalAttackRestrictions.java b/forge-game/src/main/java/forge/game/combat/GlobalAttackRestrictions.java index f62bb00a819..d7410b8033a 100644 --- a/forge-game/src/main/java/forge/game/combat/GlobalAttackRestrictions.java +++ b/forge-game/src/main/java/forge/game/combat/GlobalAttackRestrictions.java @@ -7,10 +7,9 @@ import com.google.common.primitives.Ints; import forge.game.Game; import forge.game.GameEntity; -import forge.game.GlobalRuleChange; import forge.game.card.Card; import forge.game.player.Player; -import forge.game.zone.ZoneType; +import forge.game.staticability.StaticAbilityAttackRestrict; import forge.util.collect.FCollectionView; import forge.util.maps.LinkedHashMapToAmount; import forge.util.maps.MapToAmount; @@ -107,33 +106,13 @@ public class GlobalAttackRestrictions { * @return a {@link GlobalAttackRestrictions} object. */ public static GlobalAttackRestrictions getGlobalRestrictions(final Player attackingPlayer, final FCollectionView possibleDefenders) { - int max = -1; final MapToAmount defenderMax = new LinkedHashMapToAmount<>(possibleDefenders.size()); final Game game = attackingPlayer.getGame(); - /* if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerATurn)) { - if (!attackingPlayer.getAttackedWithCreatureThisTurn().isEmpty()) { - max = 0; - } else { - max = 1; - } - } */ - - if (max == -1 && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.onlyOneAttackerACombat)) { - max = 1; - } - - if (max == -1) { - for (final Card card : game.getCardsIn(ZoneType.Battlefield)) { - if (card.hasKeyword("No more than two creatures can attack each combat.")) { - max = 2; - break; - } - } - } + int max = StaticAbilityAttackRestrict.globalAttackRestrict(game); for (final GameEntity defender : possibleDefenders) { - final int defMax = getMaxAttackTo(defender); + final int defMax = StaticAbilityAttackRestrict.attackRestrictNum(defender); if (defMax != -1) { defenderMax.add(defender, defMax); } @@ -145,27 +124,4 @@ public class GlobalAttackRestrictions { return new GlobalAttackRestrictions(max, defenderMax); } - - /** - *

- * Get the maximum number of creatures allowed to attack a certain defender. - *

- * - * @param defender - * the defending {@link GameEntity}. - * @return the maximum number of creatures, or -1 if it is unlimited. - */ - private static int getMaxAttackTo(final GameEntity defender) { - if (defender instanceof Player) { - for (final Card card : ((Player) defender).getCardsIn(ZoneType.Battlefield)) { - if (card.hasKeyword("No more than one creature can attack you each combat.")) { - return 1; - } else if (card.hasKeyword("No more than two creatures can attack you each combat.")) { - return 2; - } - } - } - - return -1; - } } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityAttackRestrict.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityAttackRestrict.java new file mode 100644 index 00000000000..047b01ca08f --- /dev/null +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityAttackRestrict.java @@ -0,0 +1,53 @@ +package forge.game.staticability; + +import forge.game.Game; +import forge.game.GameEntity; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.zone.ZoneType; + +public class StaticAbilityAttackRestrict { + + static String MODE = "AttackRestrict"; + + static public int globalAttackRestrict(Game game) { + int max = Integer.MAX_VALUE; + for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions() + || stAb.hasParam("ValidDefender")) { + continue; + } + int stMax = AbilityUtils.calculateAmount(stAb.getHostCard(), + stAb.getParamOrDefault("MaxAttackers", "1"), stAb); + if (stMax < max) { + max = stMax; + } + } + } + return max < Integer.MAX_VALUE ? max : -1; + } + + static public int attackRestrictNum(GameEntity defender) { + final Game game = defender.getGame(); + for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) { + continue; + } + if (attackRestrict(stAb, defender)) { + return AbilityUtils.calculateAmount(stAb.getHostCard(), + stAb.getParamOrDefault("MaxAttackers", "1"), stAb); + } + } + } + return -1; + } + + static public boolean attackRestrict(StaticAbility stAb, GameEntity defender) { + if (!stAb.matchesValidParam("ValidDefender", defender)) { + return false; + } + return true; + } +} diff --git a/forge-gui/res/cardsfolder/a/astral_arena.txt b/forge-gui/res/cardsfolder/a/astral_arena.txt index 28baaab266b..0bf2f017133 100644 --- a/forge-gui/res/cardsfolder/a/astral_arena.txt +++ b/forge-gui/res/cardsfolder/a/astral_arena.txt @@ -1,7 +1,7 @@ Name:Astral Arena ManaCost:no cost Types:Plane Kolbahan -S:Mode$ Continuous | EffectZone$ Command | GlobalRule$ No more than one creature can attack each combat. | Description$ No more than one creature can attack each combat. +S:Mode$ AttackRestrict | EffectZone$ Command | MaxAttackers$ 1 | Description$ No more than one creature can attack each combat. S:Mode$ Continuous | EffectZone$ Command | GlobalRule$ No more than one creature can block each combat. | Description$ No more than one creature can block each combat. T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, CARDNAME deals 2 damage to each creature. SVar:RolledChaos:DB$ DamageAll | NumDmg$ 2 | ValidCards$ Creature | ValidDescription$ each creature. diff --git a/forge-gui/res/cardsfolder/c/caverns_of_despair.txt b/forge-gui/res/cardsfolder/c/caverns_of_despair.txt index 979aa20dc81..22d7b51e0d1 100644 --- a/forge-gui/res/cardsfolder/c/caverns_of_despair.txt +++ b/forge-gui/res/cardsfolder/c/caverns_of_despair.txt @@ -1,7 +1,7 @@ Name:Caverns of Despair ManaCost:2 R R Types:World Enchantment -K:No more than two creatures can attack each combat. +S:Mode$ AttackRestrict | MaxAttackers$ 2 | Description$ No more than two creatures can attack each combat. S:Mode$ Continuous | GlobalRule$ No more than two creatures can block each combat. | Description$ No more than two creatures can block each combat. SVar:NonStackingEffect:True AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/c/crawlspace.txt b/forge-gui/res/cardsfolder/c/crawlspace.txt index 9d8e8192f8b..9eda574faaa 100644 --- a/forge-gui/res/cardsfolder/c/crawlspace.txt +++ b/forge-gui/res/cardsfolder/c/crawlspace.txt @@ -1,7 +1,6 @@ Name:Crawlspace ManaCost:3 Types:Artifact -K:No more than two creatures can attack you each combat. +S:Mode$ AttackRestrict | MaxAttackers$ 2 | ValidDefender$ You | Description$ No more than two creatures can attack you each combat. SVar:NonStackingEffect:True -AI:RemoveDeck:Random Oracle:No more than two creatures can attack you each combat. diff --git a/forge-gui/res/cardsfolder/d/dueling_grounds.txt b/forge-gui/res/cardsfolder/d/dueling_grounds.txt index 05593d9b71d..efa784fac6e 100644 --- a/forge-gui/res/cardsfolder/d/dueling_grounds.txt +++ b/forge-gui/res/cardsfolder/d/dueling_grounds.txt @@ -1,7 +1,7 @@ Name:Dueling Grounds ManaCost:1 G W Types:Enchantment -S:Mode$ Continuous | GlobalRule$ No more than one creature can attack each combat. | Description$ No more than one creature can attack each combat. +S:Mode$ AttackRestrict | MaxAttackers$ 1 | Description$ No more than one creature can attack each combat. S:Mode$ Continuous | GlobalRule$ No more than one creature can block each combat. | Description$ No more than one creature can block each combat. AI:RemoveDeck:Random Oracle:No more than one creature can attack each combat.\nNo more than one creature can block each combat. diff --git a/forge-gui/res/cardsfolder/g/grunn_the_lonely_king.txt b/forge-gui/res/cardsfolder/g/grunn_the_lonely_king.txt index 7e64b7aa7b8..fda945ad053 100644 --- a/forge-gui/res/cardsfolder/g/grunn_the_lonely_king.txt +++ b/forge-gui/res/cardsfolder/g/grunn_the_lonely_king.txt @@ -5,7 +5,7 @@ PT:5/5 K:Kicker:3 K:etbCounter:P1P1:5:CheckSVar$ WasKicked:If CARDNAME was kicked, it enters the battlefield with five +1/+1 counters on it. SVar:WasKicked:Count$Kicked.1.0 -T:Mode$ Attacks | ValidCard$ Card.Self | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks alone, double its power and toughness until end of turn. +T:Mode$ Attacks | ValidCard$ Card.Self | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever NICKNAME attacks alone, double its power and toughness until end of turn. SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ +X | NumDef$ +Y | Double$ True SVar:X:Count$CardPower SVar:Y:Count$CardToughness diff --git a/forge-gui/res/cardsfolder/m/mirri_weatherlight_duelist.txt b/forge-gui/res/cardsfolder/m/mirri_weatherlight_duelist.txt index b143431a3a3..8cc85202be3 100644 --- a/forge-gui/res/cardsfolder/m/mirri_weatherlight_duelist.txt +++ b/forge-gui/res/cardsfolder/m/mirri_weatherlight_duelist.txt @@ -3,8 +3,8 @@ ManaCost:1 G W Types:Legendary Creature Cat Warrior PT:3/2 K:First Strike -S:Mode$ Continuous | EffectZone$ Battlefield | Affected$ Card.Self+tapped | AddKeyword$ No more than one creature can attack you each combat. | Description$ As long as CARDNAME is tapped, no more than one creature can attack you each combat. +S:Mode$ AttackRestrict | IsPresent$ Card.Self+tapped | MaxAttackers$ 1 | ValidDefender$ You | Description$ As long as CARDNAME is tapped, no more than one creature can attack you each combat. T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigLimitBlock | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, each opponent can't block with more than one creature this combat. -SVar:TrigLimitBlock:DB$ Effect | Name$ Mirri, Weatherlight Duelist Effect | StaticAbilities$ STLimitBlock | Duration$ UntilEndOfCombat +SVar:TrigLimitBlock:DB$ Effect | StaticAbilities$ STLimitBlock | Duration$ UntilEndOfCombat SVar:STLimitBlock:Mode$ Continuous | EffectZone$ Command | GlobalRule$ Each opponent can't block with more than one creature. | Description$ Each opponent can't block with more than one creature this combat. Oracle:First strike\nWhenever Mirri, Weatherlight Duelist attacks, each opponent can't block with more than one creature this combat.\nAs long as Mirri, Weatherlight Duelist is tapped, no more than one creature can attack you each combat. diff --git a/forge-gui/res/cardsfolder/s/silent_arbiter.txt b/forge-gui/res/cardsfolder/s/silent_arbiter.txt index 2fb48fc7ef8..579ee4b920a 100644 --- a/forge-gui/res/cardsfolder/s/silent_arbiter.txt +++ b/forge-gui/res/cardsfolder/s/silent_arbiter.txt @@ -2,6 +2,6 @@ Name:Silent Arbiter ManaCost:4 Types:Artifact Creature Construct PT:1/5 -S:Mode$ Continuous | GlobalRule$ No more than one creature can attack each combat. | Description$ No more than one creature can attack each combat. +S:Mode$ AttackRestrict | MaxAttackers$ 1 | Description$ No more than one creature can attack each combat. S:Mode$ Continuous | GlobalRule$ No more than one creature can block each combat. | Description$ No more than one creature can block each combat. Oracle:No more than one creature can attack each combat.\nNo more than one creature can block each combat. diff --git a/forge-gui/res/cardsfolder/s/suppress.txt b/forge-gui/res/cardsfolder/s/suppress.txt index 7bda4d8154f..6e0f8b108e9 100644 --- a/forge-gui/res/cardsfolder/s/suppress.txt +++ b/forge-gui/res/cardsfolder/s/suppress.txt @@ -1,9 +1,9 @@ Name:Suppress ManaCost:2 B Types:Sorcery -A:SP$ ChangeZoneAll | Cost$ 2 B | ValidTgts$ Player | TgtPrompt$ Select target player | ChangeType$ Card | Origin$ Hand | Destination$ Exile | Hidden$ True | ExileFaceDown$ True | RememberTargets$ True | RememberChanged$ True | Reveal$ False | SubAbility$ DelTrig | SpellDescription$ Target player exiles all cards from their hand face down. At the beginning of the end step of that player's next turn, that player returns those cards to their hand. -SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | ValidPlayer$ Player.IsRemembered | Execute$ TrigReturn | Secondary$ True | TriggerDescription$ Return exiled cards to targeted player's hand. -SVar:TrigReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Hand | SubAbility$ DBCleanup +A:SP$ ChangeZoneAll | ValidTgts$ Player | ChangeType$ Card | Origin$ Hand | Destination$ Exile | Hidden$ True | ExileFaceDown$ True | RememberChanged$ True | Reveal$ False | SubAbility$ DelTrig | StackDescription$ {p:Targeted} exiles all cards from their hand face down. | SpellDescription$ Target player exiles all cards from their hand face down. At the beginning of the end step of that player's next turn, that player returns those cards to their hand. +SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | DelayedTriggerDefinedPlayer$ Targeted | ThisTurn$ True | RememberObjects$ Remembered | Execute$ TrigReturn | Secondary$ True | SubAbility$ DBCleanup | TriggerDescription$ At the beginning of the end step of that player's next turn, that player returns those cards to their hand. | StackDescription$ At the beginning of the end step of that player's next turn, that player returns those cards to their hand. +SVar:TrigReturn:DB$ ChangeZone | Defined$ DelayTriggerRememberedLKI | Origin$ Exile | Destination$ Hand SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All Oracle:Target player exiles all cards from their hand face down. At the beginning of the end step of that player's next turn, that player returns those cards to their hand. diff --git a/forge-gui/res/cardsfolder/t/teleportation_circle.txt b/forge-gui/res/cardsfolder/t/teleportation_circle.txt index 8320fb8b621..d56a98b2267 100644 --- a/forge-gui/res/cardsfolder/t/teleportation_circle.txt +++ b/forge-gui/res/cardsfolder/t/teleportation_circle.txt @@ -2,7 +2,7 @@ Name:Teleportation Circle ManaCost:3 W Types:Enchantment T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigExile | TriggerDescription$ At the beginning of your end step, exile up to one target artifact or creature you control, then return that card to the battlefield under its owner's control. -SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | ValidTgts$ Artifact.YouCtrl,Creature.YouCtrl | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target artifact or creature | SubAbility$ DBReturn +SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | ValidTgts$ Artifact.YouCtrl,Creature.YouCtrl | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target artifact or creature you control | SubAbility$ DBReturn SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ All | Destination$ Battlefield | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:At the beginning of your end step, exile up to one target artifact or creature you control, then return that card to the battlefield under its owner's control. diff --git a/forge-gui/res/cardsfolder/upcoming/the_eternal_wanderer.txt b/forge-gui/res/cardsfolder/upcoming/the_eternal_wanderer.txt new file mode 100644 index 00000000000..74a5e783026 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_eternal_wanderer.txt @@ -0,0 +1,17 @@ +Name:The Eternal Wanderer +ManaCost:4 W W +Types:Legendary Planeswalker +Loyalty:5 +S:Mode$ AttackRestrict | MaxAttackers$ 1 | ValidDefender$ Card.Self | Description$ No more than one creature can attack CARDNAME each combat. +A:AB$ ChangeZone | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Artifact,Creature | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target artifact or creature | RememberChanged$ True | SubAbility$ DBDelTrig | SpellDescription$ Exile up to one target artifact or creature. Return that card to the battlefield under its owner's control at the beginning of that player's next end step. +SVar:DBDelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | ValidPlayer$ Player.IsTriggerRemembered | Execute$ TrigReturn | RememberObjects$ TargetedOwner,RememberedLKI | SubAbility$ DBCleanup | TriggerDescription$ Return that card to the battlefield under its owner's control at the beginning of that player's next end step. | StackDescription$ Return that card to the battlefield under its owner's control at the beginning of that player's next end step. +SVar:TrigReturn:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | Defined$ DelayTriggerRememberedLKI +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +A:AB$ Token | Cost$ AddCounter<0/LOYALTY> | Planeswalker$ True | TokenScript$ w_2_2_samurai_double_strike | SpellDescription$ Create a 2/2 white Samurai creature token with double strike. +A:AB$ RepeatEach | Cost$ SubCounter<4/LOYALTY> | Planeswalker$ True | RepeatPlayers$ Player | RepeatSubAbility$ DBChoose | SubAbility$ DBSacAll | StackDescription$ SpellDescription | SpellDescription$ For each player, choose a creature that player controls. Each player sacrifices all creatures they control not chosen this way. +SVar:DBChoose:DB$ ChooseCard | Defined$ You | Choices$ Creature.RememberedPlayerCtrl | Mandatory$ True | Amount$ 1 | ChoiceTitle$ Choose a creature this player controls | ImprintChosen$ True +SVar:DBSacAll:DB$ SacrificeAll | ValidCards$ Creature.IsNotImprinted | SubAbility$ DBCleanup2 | StackDescription$ None +SVar:DBCleanup2:DB$ Cleanup | ClearChosenCard$ True | ClearImprinted$ True +DeckHas:Ability$Token|Sacrifice & Type$Samurai +DeckHints:Keyword$Exalted +Oracle:No more than one creature can attack The Eternal Wanderer each combat.\n[+1]: Exile up to one target artifact or creature. Return that card to the battlefield under its owner's control at the beginning of that player's next end step.\n[0]: Create a 2/2 white Samurai creature token with double strike.\n[−4]: For each player, choose a creature that player controls. Each player sacrifices all creatures they control not chosen this way.