From c8f9db124ecbcfe10c6006a4b9113cb0ea94f05c Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 8 Apr 2023 23:47:04 +0200 Subject: [PATCH] GameAction: first state based action for Battle --- .../src/main/java/forge/game/GameAction.java | 45 +++++++++++++------ .../spellability/SpellAbilityPredicates.java | 9 ++++ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index d1e0cbb3739..39f74ef2ac1 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -1282,8 +1282,7 @@ public class GameAction { if (zt == ZoneType.Battlefield) { continue; } - final Iterable cards = p.getCardsIn(zt).threadSafeIterable(); - for (final Card c : cards) { + for (final Card c : p.getCardsIn(zt).threadSafeIterable()) { checkAgain |= stateBasedAction704_5d(c); // Dungeon Card won't affect other cards, so don't need to set checkAgain stateBasedAction_Dungeon(c); @@ -1296,6 +1295,7 @@ public class GameAction { CardCollection sacrificeList = new CardCollection(); PlayerCollection spaceSculptors = new PlayerCollection(); for (final Card c : game.getCardsIn(ZoneType.Battlefield)) { + boolean checkAgainCard = false; if (c.hasKeyword(Keyword.SPACE_SCULPTOR)) { spaceSculptors.add(c.getController()); } @@ -1303,7 +1303,7 @@ public class GameAction { // Rule 704.5f - Put into grave (no regeneration) for toughness <= 0 if (c.getNetToughness() <= 0) { noRegCreats.add(c); - checkAgain = true; + checkAgainCard = true; } else if (c.hasKeyword("CARDNAME can't be destroyed by lethal damage unless lethal damage dealt by a single source is marked on it.")) { if (c.getLethal() <= c.getMaxDamageFromSource() || c.hasBeenDealtDeathtouchDamage()) { if (desCreats == null) { @@ -1311,7 +1311,7 @@ public class GameAction { } desCreats.add(c); c.setHasBeenDealtDeathtouchDamage(false); - checkAgain = true; + checkAgainCard = true; } } // Rule 704.5g - Destroy due to lethal damage @@ -1322,20 +1322,21 @@ public class GameAction { } desCreats.add(c); c.setHasBeenDealtDeathtouchDamage(false); - checkAgain = true; + checkAgainCard = true; } } - checkAgain |= stateBasedAction_Saga(c, sacrificeList); - checkAgain |= stateBasedAction704_attach(c, unAttachList); // Attachment + checkAgainCard |= stateBasedAction_Saga(c, sacrificeList); + checkAgainCard |= stateBasedAction_Battle(c, noRegCreats); + checkAgainCard |= stateBasedAction704_attach(c, unAttachList); // Attachment - checkAgain |= stateBasedAction704_5r(c); // annihilate +1/+1 counters with -1/-1 ones + checkAgainCard |= stateBasedAction704_5r(c); // annihilate +1/+1 counters with -1/-1 ones final CounterType dreamType = CounterType.get(CounterEnumType.DREAM); if (c.getCounters(dreamType) > 7 && c.hasKeyword("CARDNAME can't have more than seven dream counters on it.")) { c.subtractCounter(dreamType, c.getCounters(dreamType) - 7); - checkAgain = true; + checkAgainCard = true; } if (c.hasKeyword("The number of loyalty counters on CARDNAME is equal to the number of Beebles you control.")) { @@ -1350,17 +1351,18 @@ public class GameAction { } // Only check again if counters actually changed if (c.getCounters(CounterEnumType.LOYALTY) != loyal) { - checkAgain = true; + checkAgainCard = true; } } // cleanup aura if (c.isAura() && c.isInPlay() && !c.isEnchanting()) { noRegCreats.add(c); - checkAgain = true; + checkAgainCard = true; } - if (checkAgain) { + if (checkAgainCard) { cardsToUpdateLKI.add(c); + checkAgain = true; } } for (Card u : unAttachList) { @@ -1503,6 +1505,23 @@ public class GameAction { return checkAgain; } + private boolean stateBasedAction_Battle(Card c, CardCollection removeList) { + boolean checkAgain = false; + if (!c.getType().isBattle()) { + return false; + } + if (c.getCounters(CounterEnumType.DEFENSE) > 0) { + return false; + } + // 704.5v If a battle has defense 0 and it isn’t the source of an ability that has triggered but not yet left the stack, + // it’s put into its owner’s graveyard. + if (!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isTrigger())) { + removeList.add(c); + checkAgain = true; + } + return checkAgain; + } + private void stateBasedAction_Dungeon(Card c) { if (!c.getType().isDungeon() || !c.isInLastRoom()) { return; @@ -1531,7 +1550,7 @@ public class GameAction { } } } - if (c.isCreature() && c.isAttachedToEntity()) { // Rule 704.5q - Creature attached to an object or player, becomes unattached + if ((c.isCreature() || c.isBattle()) && c.isAttachedToEntity()) { // Rule 704.5q - Creature attached to an object or player, becomes unattached unAttachList.add(c); checkAgain = true; } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityPredicates.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityPredicates.java index 16968b3331b..84d311f955c 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityPredicates.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityPredicates.java @@ -63,6 +63,15 @@ public final class SpellAbilityPredicates extends CardTraitPredicates { }; } + public static final Predicate isTrigger() { + return new Predicate() { + @Override + public boolean apply(final SpellAbility sa) { + return sa.isTrigger(); + } + }; + } + public static final Predicate isValid(String[] restrictions, Player sourceController, Card source, CardTraitBase spellAbility) { return new Predicate() { @Override