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.