mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-15 02:08:00 +00:00
Merge branch 'attackfix' into 'master'
declareAttackers fixes See merge request core-developers/forge!6443
This commit is contained in:
@@ -47,6 +47,7 @@ import forge.game.cost.Cost;
|
|||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.SpellAbilityPredicates;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.Zone;
|
import forge.game.zone.Zone;
|
||||||
@@ -135,13 +136,11 @@ public class AiAttackController {
|
|||||||
if (c.isToken() && c.getCopiedPermanent() == null) {
|
if (c.isToken() && c.getCopiedPermanent() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (SpellAbility sa : c.getSpellAbilities()) {
|
for (SpellAbility sa : Iterables.filter(c.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.Animate))) {
|
||||||
if (sa.getApi() == ApiType.Animate) {
|
if (ComputerUtilCost.canPayCost(sa, defender, false)
|
||||||
if (ComputerUtilCost.canPayCost(sa, defender, false)
|
&& sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
|
||||||
&& sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
|
Card animatedCopy = AnimateAi.becomeAnimated(c, sa);
|
||||||
Card animatedCopy = AnimateAi.becomeAnimated(c, sa);
|
defenders.add(animatedCopy);
|
||||||
defenders.add(animatedCopy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1150,9 +1150,9 @@ public class AiBlockController {
|
|||||||
//Check for validity of blocks in case something slipped through
|
//Check for validity of blocks in case something slipped through
|
||||||
for (Card attacker : attackers) {
|
for (Card attacker : attackers) {
|
||||||
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size(), combat)) {
|
if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size(), combat)) {
|
||||||
for (final Card blocker : combat.getBlockers(attacker)) {
|
for (final Card blocker : CardLists.filterControlledBy(combat.getBlockers(attacker), ai)) {
|
||||||
if (blocker.getController() == ai) // don't touch other player's blockers
|
// don't touch other player's blockers
|
||||||
combat.removeFromCombat(blocker);
|
combat.removeFromCombat(blocker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2037,7 +2037,7 @@ public class ComputerUtilCombat {
|
|||||||
|
|
||||||
} // attacker no double strike
|
} // attacker no double strike
|
||||||
return false;// should never arrive here
|
return false;// should never arrive here
|
||||||
} // canDestroyBlocker
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
|
|||||||
if (sa.hasParam("CanTargetPlayer")) {
|
if (sa.hasParam("CanTargetPlayer")) {
|
||||||
// Radiate
|
// Radiate
|
||||||
// Remove targeted players because getAllCandidates include all the valid players
|
// 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);
|
candidates.remove(p);
|
||||||
|
|
||||||
for (GameEntity o : candidates) {
|
for (GameEntity o : candidates) {
|
||||||
|
|||||||
@@ -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
|
violations++; // no one is attacking that defender or any of his PWs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,14 +173,7 @@ public class AttackRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// now, count everything else
|
// now, count everything else
|
||||||
final MapToAmount<GameEntity> defenderSpecificCostFree = new LinkedHashMapToAmount<>();
|
violations += defenderSpecific.countAll() - (isAttacking ? defenderSpecific.count(defender) : 0);
|
||||||
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);
|
|
||||||
if (isAttacking) {
|
if (isAttacking) {
|
||||||
final Combat combat = defender.getGame().getCombat();
|
final Combat combat = defender.getGame().getCombat();
|
||||||
final Map<Card, AttackRestriction> constraints = combat.getAttackConstraints().getRestrictions();
|
final Map<Card, AttackRestriction> constraints = combat.getAttackConstraints().getRestrictions();
|
||||||
|
|||||||
@@ -567,24 +567,34 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
continue;
|
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()) {
|
for (final Card attacker : combat.getAttackers()) {
|
||||||
final boolean shouldTapForAttack = !attacker.hasKeyword(Keyword.VIGILANCE) && !attacker.hasKeyword("Attacking doesn't cause CARDNAME to tap.");
|
final boolean shouldTapForAttack = !attacker.hasKeyword(Keyword.VIGILANCE) && !attacker.hasKeyword("Attacking doesn't cause CARDNAME to tap.");
|
||||||
if (shouldTapForAttack) {
|
if (shouldTapForAttack) {
|
||||||
// set tapped to true without firing triggers because it may affect propaganda costs
|
// set tapped to true without firing triggers because it may affect propaganda costs
|
||||||
attacker.setTapped(true);
|
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);
|
final boolean canAttack = CombatUtil.checkPropagandaEffects(game, attacker, combat);
|
||||||
attacker.setTapped(false);
|
|
||||||
|
|
||||||
if (canAttack) {
|
if (!canAttack) {
|
||||||
if (shouldTapForAttack) {
|
|
||||||
attacker.tap(true, true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
combat.removeFromCombat(attacker);
|
combat.removeFromCombat(attacker);
|
||||||
|
if (untapFromCancel.contains(attacker)) {
|
||||||
|
attacker.setTapped(false);
|
||||||
|
}
|
||||||
success = CombatUtil.validateAttackers(combat);
|
success = CombatUtil.validateAttackers(combat);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
for (Card c : untapFromCancel) {
|
||||||
|
c.setTapped(false);
|
||||||
|
}
|
||||||
|
// might have been sacrificed while paying
|
||||||
|
combat.removeAbsentCombatants();
|
||||||
|
combat.initConstraints();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,6 +602,14 @@ public class PhaseHandler implements java.io.Serializable {
|
|||||||
|
|
||||||
} while (!success);
|
} 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
|
// Exert creatures here
|
||||||
List<Card> possibleExerters = CardLists.getKeyword(combat.getAttackers(),
|
List<Card> possibleExerters = CardLists.getKeyword(combat.getAttackers(),
|
||||||
"You may exert CARDNAME as it attacks.");
|
"You may exert CARDNAME as it attacks.");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Name:Chef's Kiss
|
Name:Chef's Kiss
|
||||||
ManaCost:1 R R
|
ManaCost:1 R R
|
||||||
Types:Instant
|
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: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
|
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.
|
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.
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ Types:Creature Human Wizard
|
|||||||
PT:2/2
|
PT:2/2
|
||||||
K:Flash
|
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.
|
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.
|
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.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Name:Radiate
|
Name:Radiate
|
||||||
ManaCost:3 R R
|
ManaCost:3 R R
|
||||||
Types:Instant
|
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
|
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.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user