From c0ede21c3118749473cf4b70a9d167703e7097bd Mon Sep 17 00:00:00 2001 From: Bug Hunter Date: Sat, 26 Mar 2022 00:36:17 +0000 Subject: [PATCH] declareAttackers fixes --- .../java/forge/ai/AiAttackController.java | 13 ++++---- .../main/java/forge/ai/AiBlockController.java | 6 ++-- .../java/forge/ai/ComputerUtilCombat.java | 2 +- .../effects/CopySpellAbilityEffect.java | 2 +- .../forge/game/combat/AttackRequirement.java | 11 ++----- .../java/forge/game/phase/PhaseHandler.java | 30 +++++++++++++++---- forge-gui/res/cardsfolder/c/chefs_kiss.txt | 2 +- .../res/cardsfolder/r/radiant_performer.txt | 2 +- forge-gui/res/cardsfolder/r/radiate.txt | 2 +- 9 files changed, 40 insertions(+), 30 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 8147d5a95c2..8479062b148 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -47,6 +47,7 @@ import forge.game.cost.Cost; import forge.game.keyword.Keyword; import forge.game.player.Player; import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityPredicates; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; import forge.game.zone.Zone; @@ -135,13 +136,11 @@ public class AiAttackController { if (c.isToken() && c.getCopiedPermanent() == null) { continue; } - for (SpellAbility sa : c.getSpellAbilities()) { - if (sa.getApi() == ApiType.Animate) { - if (ComputerUtilCost.canPayCost(sa, defender, false) - && sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) { - Card animatedCopy = AnimateAi.becomeAnimated(c, sa); - defenders.add(animatedCopy); - } + for (SpellAbility sa : Iterables.filter(c.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.Animate))) { + if (ComputerUtilCost.canPayCost(sa, defender, false) + && sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) { + Card animatedCopy = AnimateAi.becomeAnimated(c, sa); + defenders.add(animatedCopy); } } } diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java index 350833a9d6e..07fa3d0766b 100644 --- a/forge-ai/src/main/java/forge/ai/AiBlockController.java +++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java @@ -1150,9 +1150,9 @@ public class AiBlockController { //Check for validity of blocks in case something slipped through for (Card attacker : attackers) { if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size(), combat)) { - for (final Card blocker : combat.getBlockers(attacker)) { - if (blocker.getController() == ai) // don't touch other player's blockers - combat.removeFromCombat(blocker); + for (final Card blocker : CardLists.filterControlledBy(combat.getBlockers(attacker), ai)) { + // don't touch other player's blockers + combat.removeFromCombat(blocker); } } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 4e08397a9bf..28d7406df18 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2037,7 +2037,7 @@ public class ComputerUtilCombat { } // attacker no double strike return false;// should never arrive here - } // canDestroyBlocker + } /** *

diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java index 491f1beb55f..59b3c86585d 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java @@ -108,7 +108,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect { if (sa.hasParam("CanTargetPlayer")) { // Radiate // Remove targeted players because getAllCandidates include all the valid players - for(Player p : targetedSA.getTargets().getTargetPlayers()) + for (Player p : targetedSA.getTargets().getTargetPlayers()) candidates.remove(p); for (GameEntity o : candidates) { diff --git a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java index 3c96d54787a..e11d907f3e2 100644 --- a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java +++ b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java @@ -165,7 +165,7 @@ public class AttackRequirement { } } } - if (!isAttackingDefender && CombatUtil.getAttackCost(attacker.getGame(), attacker, defender) == null) { + if (!isAttackingDefender && CombatUtil.getAttackCost(attacker.getGame(), attacker, def) == null) { violations++; // no one is attacking that defender or any of his PWs } } @@ -173,14 +173,7 @@ public class AttackRequirement { } // now, count everything else - final MapToAmount defenderSpecificCostFree = new LinkedHashMapToAmount<>(); - for (GameEntity e : defenderSpecific.keySet()) { - if (CombatUtil.getAttackCost(attacker.getGame(), attacker, defender) == null) { - defenderSpecificCostFree.put(e, defenderSpecific.get(e)); - } - } - - violations += defenderSpecificCostFree.countAll() - (isAttacking ? defenderSpecificCostFree.count(defender) : 0); + violations += defenderSpecific.countAll() - (isAttacking ? defenderSpecific.count(defender) : 0); if (isAttacking) { final Combat combat = defender.getGame().getCombat(); final Map constraints = combat.getAttackConstraints().getRestrictions(); diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index 3fd70bba0db..bda92edbf13 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -567,24 +567,34 @@ public class PhaseHandler implements java.io.Serializable { continue; } + final CardCollection untapFromCancel = new CardCollection(); + // do a full loop first so attackers can't be used to pay for Propaganda for (final Card attacker : combat.getAttackers()) { final boolean shouldTapForAttack = !attacker.hasKeyword(Keyword.VIGILANCE) && !attacker.hasKeyword("Attacking doesn't cause CARDNAME to tap."); if (shouldTapForAttack) { // set tapped to true without firing triggers because it may affect propaganda costs attacker.setTapped(true); + untapFromCancel.add(attacker); } + } + for (final Card attacker : combat.getAttackers()) { + // TODO currently doesn't refund (can really only happen if you cancel paying for a creature with an attacking requirement that could be satisfied without a tax) final boolean canAttack = CombatUtil.checkPropagandaEffects(game, attacker, combat); - attacker.setTapped(false); - if (canAttack) { - if (shouldTapForAttack) { - attacker.tap(true, true); - } - } else { + if (!canAttack) { combat.removeFromCombat(attacker); + if (untapFromCancel.contains(attacker)) { + attacker.setTapped(false); + } success = CombatUtil.validateAttackers(combat); if (!success) { + for (Card c : untapFromCancel) { + c.setTapped(false); + } + // might have been sacrificed while paying + combat.removeAbsentCombatants(); + combat.initConstraints(); break; } } @@ -592,6 +602,14 @@ public class PhaseHandler implements java.io.Serializable { } while (!success); + for (final Card attacker : combat.getAttackers()) { + final boolean shouldTapForAttack = !attacker.hasKeyword(Keyword.VIGILANCE) && !attacker.hasKeyword("Attacking doesn't cause CARDNAME to tap."); + if (shouldTapForAttack) { + attacker.setTapped(false); + attacker.tap(true, true); + } + } + // Exert creatures here List possibleExerters = CardLists.getKeyword(combat.getAttackers(), "You may exert CARDNAME as it attacks."); diff --git a/forge-gui/res/cardsfolder/c/chefs_kiss.txt b/forge-gui/res/cardsfolder/c/chefs_kiss.txt index bca9676325a..53e2b33f31c 100644 --- a/forge-gui/res/cardsfolder/c/chefs_kiss.txt +++ b/forge-gui/res/cardsfolder/c/chefs_kiss.txt @@ -1,7 +1,7 @@ Name:Chef's Kiss ManaCost:1 R R Types:Instant -A:SP$ ControlSpell | ValidTgts$ Card | TgtPrompt$ Select target spell that targets only a single permanent or player | TargetType$ Spell | Mode$ Gain | TargetsSingleTarget$ True | TargetValidTargeting$ Permanent,Player | SubAbility$ DBCopy | StackDescription$ SpellDescription | SpellDescription$ Gain control of target spell that targets only a single permanent or player. Copy it, then reselect the targets at random for the spell and the copy. The new targets can't be you or a permanent you control. +A:SP$ ControlSpell | ValidTgts$ Card | TgtPrompt$ Select target spell that targets only a single permanent or player | TargetType$ Spell | Mode$ Gain | TargetsSingleTarget$ True | TargetValidTargeting$ Permanent.inZoneBattlefield,Player | SubAbility$ DBCopy | StackDescription$ SpellDescription | SpellDescription$ Gain control of target spell that targets only a single permanent or player. Copy it, then reselect the targets at random for the spell and the copy. The new targets can't be you or a permanent you control. SVar:DBCopy:DB$ CopySpellAbility | Defined$ Targeted | RandomTarget$ True | RandomTargetRestriction$ Player.Other,Permanent.YouDontCtrl | SubAbility$ DBChangeTargets | StackDescription$ None SVar:DBChangeTargets:DB$ ChangeTargets | Defined$ Targeted | RandomTarget$ True | RandomTargetRestriction$ Player.Other,Permanent.YouDontCtrl Oracle:Gain control of target spell that targets only a single permanent or player. Copy it, then reselect the targets at random for the spell and the copy. The new targets can't be you or a permanent you control. diff --git a/forge-gui/res/cardsfolder/r/radiant_performer.txt b/forge-gui/res/cardsfolder/r/radiant_performer.txt index c77d3bc5966..9db7febc942 100644 --- a/forge-gui/res/cardsfolder/r/radiant_performer.txt +++ b/forge-gui/res/cardsfolder/r/radiant_performer.txt @@ -4,5 +4,5 @@ Types:Creature Human Wizard PT:2/2 K:Flash T:Mode$ ChangesZone | ValidCard$ Card.wasCastFromYourHandByYou+Self | Destination$ Battlefield | Execute$ TrigRadiate | TriggerDescription$ When CARDNAME enters the battlefield, if you cast it from your hand, choose target spell or ability that targets only a single permanent or player. Copy that spell or ability for each other permanent or player the spell or ability could target. Each copy targets a different one of those permanents and players. -SVar:TrigRadiate:DB$ CopySpellAbility | ValidTgts$ Card | TgtPrompt$ Select target spell or ability that targets a single permanent or player | TargetType$ Spell,Activated,Triggered | TargetsSingleTarget$ True | TargetValidTargeting$ Permanent,Player | Controller$ You | CopyForEachCanTarget$ True | CanTargetPlayer$ True | SpellDescription$ Choose target spell or ability spell that targets only a single permanent or player. Copy that spell for each other permanent or player the spell could target. Each copy targets a different one of those permanents and players. +SVar:TrigRadiate:DB$ CopySpellAbility | ValidTgts$ Card | TgtPrompt$ Select target spell or ability that targets a single permanent or player | TargetType$ Spell,Activated,Triggered | TargetsSingleTarget$ True | TargetValidTargeting$ Permanent.inZoneBattlefield,Player | Controller$ You | CopyForEachCanTarget$ True | CanTargetPlayer$ True | SpellDescription$ Choose target spell or ability that targets only a single permanent or player. Copy that spell for each other permanent or player the spell could target. Each copy targets a different one of those permanents and players. Oracle:Flash\nWhen Radiant Performer enters the battlefield, if you cast it from your hand, choose target spell or ability that targets only a single permanent or player. Copy that spell or ability for each other permanent or player the spell or ability could target. Each copy targets a different one of those permanents and players. diff --git a/forge-gui/res/cardsfolder/r/radiate.txt b/forge-gui/res/cardsfolder/r/radiate.txt index d9541e2ed1c..ff8fb6fc763 100644 --- a/forge-gui/res/cardsfolder/r/radiate.txt +++ b/forge-gui/res/cardsfolder/r/radiate.txt @@ -1,6 +1,6 @@ Name:Radiate ManaCost:3 R R Types:Instant -A:SP$ CopySpellAbility | Cost$ 3 R R | ValidTgts$ Instant,Sorcery | TargetType$ Spell | TargetsSingleTarget$ True | TargetValidTargeting$ Permanent,Player | Controller$ You | CopyForEachCanTarget$ True | CanTargetPlayer$ True | SpellDescription$ Choose target instant or sorcery spell that targets only a single permanent or player. Copy that spell for each other permanent or player the spell could target. Each copy targets a different one of those permanents and players. +A:SP$ CopySpellAbility | Cost$ 3 R R | ValidTgts$ Instant,Sorcery | TargetType$ Spell | TargetsSingleTarget$ True | TargetValidTargeting$ Permanent.inZoneBattlefield,Player | Controller$ You | CopyForEachCanTarget$ True | CanTargetPlayer$ True | SpellDescription$ Choose target instant or sorcery spell that targets only a single permanent or player. Copy that spell for each other permanent or player the spell could target. Each copy targets a different one of those permanents and players. AI:RemoveDeck:Random Oracle:Choose target instant or sorcery spell that targets only a single permanent or player. Copy that spell for each other permanent or player the spell could target. Each copy targets a different one of those permanents and players.