From cbb6117b8e5f2af99cba921ba2dba492f17d6b9b Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 1 Aug 2014 15:59:59 +0200 Subject: [PATCH] Replaced some replacement effects with restriction effects. Added new method to restriction effect. Improved canAttack methods (not finished yet). --- .../src/mage/player/ai/ComputerPlayer6.java | 3 +- .../src/mage/player/ai/ComputerPlayer7.java | 7 +- .../src/mage/player/ai/SimulatedPlayer2.java | 2 +- .../src/mage/player/ai/MCTSPlayer.java | 7 +- .../mage/player/ai/SimulatedPlayerMCTS.java | 5 +- .../src/mage/player/ai/ComputerPlayer3.java | 13 ++-- .../src/mage/player/ai/SimulatedPlayer.java | 2 +- .../org/mage/test/player/RandomPlayer.java | 2 +- .../java/org/mage/test/player/TestPlayer.java | 2 +- .../abilities/effects/RestrictionEffect.java | 5 ++ .../CanBlockOnlyFlyingAttachedEffect.java | 46 ++++-------- .../CantAttackControllerAttachedEffect.java | 71 ++++++------------- .../common/combat/CantAttackTargetEffect.java | 5 +- Mage/src/mage/game/permanent/Permanent.java | 8 +++ .../mage/game/permanent/PermanentImpl.java | 20 +++++- Mage/src/mage/players/Player.java | 1 + Mage/src/mage/players/PlayerImpl.java | 12 +++- 17 files changed, 101 insertions(+), 110 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index f1f4274047e..0b2fee3f093 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -944,10 +944,11 @@ public class ComputerPlayer6 extends ComputerPlayer implements Player { if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, activePlayerId, activePlayerId))) { Player attackingPlayer = game.getPlayer(activePlayerId); + // TODO: this works only in two player game, also no attack of Planeswalker UUID defenderId = game.getOpponents(playerId).iterator().next(); Player defender = game.getPlayer(defenderId); - List attackersList = super.getAvailableAttackers(game); + List attackersList = super.getAvailableAttackers(defenderId, game); if (attackersList.isEmpty()) { return; } diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java index 4e4389a99bd..33f9ff60aa5 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer7.java @@ -382,9 +382,9 @@ public class ComputerPlayer7 extends ComputerPlayer6 { Integer val = null; SimulationNode2 bestNode = null; SimulatedPlayer2 attacker = (SimulatedPlayer2) game.getPlayer(attackerId); - + UUID defenderId = game.getOpponents(attackerId).iterator().next(); if (logger.isDebugEnabled()) { - logger.debug(attacker.getName() + "'s possible attackers: " + attacker.getAvailableAttackers(game)); + logger.debug(attacker.getName() + "'s possible attackers: " + attacker.getAvailableAttackers(defenderId, game)); } for (Combat engagement: attacker.addAttackers(game)) { if (logger.isDebugEnabled()) { @@ -394,8 +394,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 { logger.debug("Sim Attackers -- pruning attackers"); break; } - Game sim = game.copy(); - UUID defenderId = game.getOpponents(attackerId).iterator().next(); + Game sim = game.copy(); for (CombatGroup group: engagement.getGroups()) { for (UUID attackId: group.getAttackers()) { sim.getPlayer(attackerId).declareAttacker(attackId, defenderId, sim, false); diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java index f355f0a3951..bc197b421a8 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java @@ -334,7 +334,7 @@ public class SimulatedPlayer2 extends ComputerPlayer { Map engagements = new HashMap<>(); //useful only for two player games - will only attack first opponent UUID defenderId = game.getOpponents(playerId).iterator().next(); - List attackersList = super.getAvailableAttackers(game); + List attackersList = super.getAvailableAttackers(defenderId, game); //use binary digits to calculate powerset of attackers int powerElements = (int) Math.pow(2, attackersList.size()); StringBuilder binary = new StringBuilder(); diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java index 423b38ef117..e653bc80db8 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/MCTSPlayer.java @@ -135,8 +135,9 @@ public class MCTSPlayer extends ComputerPlayer { } List engagement = new ArrayList(); for (int j = 0; j < attackersList.size(); j++) { - if (binary.charAt(j) == '1') + if (binary.charAt(j) == '1') { engagement.add(attackersList.get(j).getId()); + } } engagements.add(engagement); } @@ -146,7 +147,9 @@ public class MCTSPlayer extends ComputerPlayer { public List>> getBlocks(Game game) { List>> engagements = new ArrayList>>(); int numGroups = game.getCombat().getGroups().size(); - if (numGroups == 0) return engagements; + if (numGroups == 0) { + return engagements; + } //add a node with no blockers List> engagement = new ArrayList>(); diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java index 5559e2b79d3..9694ee8445e 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/SimulatedPlayerMCTS.java @@ -176,7 +176,7 @@ public class SimulatedPlayerMCTS extends MCTSPlayer { //useful only for two player games - will only attack first opponent // logger.info("select attackers"); UUID defenderId = game.getOpponents(playerId).iterator().next(); - List attackersList = super.getAvailableAttackers(game); + List attackersList = super.getAvailableAttackers(defenderId, game); //use binary digits to calculate powerset of attackers int powerElements = (int) Math.pow(2, attackersList.size()); int value = rnd.nextInt(powerElements); @@ -186,8 +186,9 @@ public class SimulatedPlayerMCTS extends MCTSPlayer { binary.insert(0, "0"); //pad with zeros } for (int i = 0; i < attackersList.size(); i++) { - if (binary.charAt(i) == '1') + if (binary.charAt(i) == '1') { game.getCombat().declareAttacker(attackersList.get(i).getId(), defenderId, game); + } } actionCount++; } diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java index 2d8d109fe2b..8b2d9cfaf17 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/ComputerPlayer3.java @@ -306,9 +306,11 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { Integer val = null; SimulationNode bestNode = null; SimulatedPlayer attacker = (SimulatedPlayer) game.getPlayer(attackerId); - - if (logger.isDebugEnabled()) - logger.debug(indent(node.depth) + attacker.getName() + "'s possible attackers: " + attacker.getAvailableAttackers(game)); + + UUID defenderId = game.getOpponents(attackerId).iterator().next(); + if (logger.isDebugEnabled()) { + logger.debug(indent(node.depth) + attacker.getName() + "'s possible attackers: " + attacker.getAvailableAttackers(defenderId, game)); + } List engagements = attacker.addAttackers(game); for (Combat engagement: engagements) { if (alpha >= beta) { @@ -316,7 +318,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { break; } Game sim = game.copy(); - UUID defenderId = game.getOpponents(attackerId).iterator().next(); + for (CombatGroup group: engagement.getGroups()) { for (UUID attackId: group.getAttackers()) { sim.getPlayer(attackerId).declareAttacker(attackId, defenderId, sim, false); @@ -324,8 +326,9 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player { } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId)); SimulationNode newNode = new SimulationNode(node, sim, attackerId); - if (logger.isDebugEnabled()) + if (logger.isDebugEnabled()) { logger.debug(indent(node.depth) + "simulating attack for player:" + game.getPlayer(attackerId).getName()); + } sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); diff --git a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java index 4c9deb07a5b..b114403fa5b 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AIMinimax/src/mage/player/ai/SimulatedPlayer.java @@ -165,7 +165,7 @@ public class SimulatedPlayer extends ComputerPlayer { Map engagements = new HashMap(); //useful only for two player games - will only attack first opponent UUID defenderId = game.getOpponents(playerId).iterator().next(); - List attackersList = super.getAvailableAttackers(game); + List attackersList = super.getAvailableAttackers(defenderId, game); //use binary digits to calculate powerset of attackers int powerElements = (int) Math.pow(2, attackersList.size()); StringBuilder binary = new StringBuilder(); diff --git a/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java index f2539b6676c..6dafa1d3572 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/RandomPlayer.java @@ -180,7 +180,7 @@ public class RandomPlayer extends ComputerPlayer { public void selectAttackers(Game game, UUID attackingPlayerId) { //useful only for two player games - will only attack first opponent UUID defenderId = game.getOpponents(playerId).iterator().next(); - List attackersList = super.getAvailableAttackers(game); + List attackersList = super.getAvailableAttackers(defenderId, game); //use binary digits to calculate powerset of attackers int powerElements = (int) Math.pow(2, attackersList.size()); int value = rnd.nextInt(powerElements); diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index ba82fd382f0..2b6d643fcbf 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -178,7 +178,7 @@ public class TestPlayer extends ComputerPlayer { FilterCreatureForCombat filter = new FilterCreatureForCombat(); filter.add(new NamePredicate(groups[0])); Permanent attacker = findPermanent(filter, playerId, game); - if (attacker != null && attacker.canAttack(game)) { + if (attacker != null && attacker.canAttack(defenderId, game)) { this.declareAttacker(attacker.getId(), defenderId, game, false); } } diff --git a/Mage/src/mage/abilities/effects/RestrictionEffect.java b/Mage/src/mage/abilities/effects/RestrictionEffect.java index d3d35e09b84..87df553a361 100644 --- a/Mage/src/mage/abilities/effects/RestrictionEffect.java +++ b/Mage/src/mage/abilities/effects/RestrictionEffect.java @@ -28,6 +28,7 @@ package mage.abilities.effects; +import java.util.UUID; import mage.abilities.Ability; import mage.constants.Duration; import mage.constants.EffectType; @@ -64,6 +65,10 @@ public abstract class RestrictionEffect extends ContinuousEffectImpl { public boolean canAttack(Game game) { return true; } + + public boolean canAttack(UUID defenderId, Ability source, Game game) { + return true; + } public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) { return true; diff --git a/Mage/src/mage/abilities/effects/common/combat/CanBlockOnlyFlyingAttachedEffect.java b/Mage/src/mage/abilities/effects/common/combat/CanBlockOnlyFlyingAttachedEffect.java index 7c22db7f865..a457e94d455 100644 --- a/Mage/src/mage/abilities/effects/common/combat/CanBlockOnlyFlyingAttachedEffect.java +++ b/Mage/src/mage/abilities/effects/common/combat/CanBlockOnlyFlyingAttachedEffect.java @@ -29,25 +29,22 @@ package mage.abilities.effects.common.combat; import mage.abilities.Ability; -import mage.abilities.MageSingleton; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.RestrictionEffect; import mage.abilities.keyword.FlyingAbility; import mage.constants.AttachmentType; import mage.constants.Duration; -import mage.constants.Outcome; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; /** * * @author LevelX2 */ -public class CanBlockOnlyFlyingAttachedEffect extends ReplacementEffectImpl implements MageSingleton { + +public class CanBlockOnlyFlyingAttachedEffect extends RestrictionEffect { public CanBlockOnlyFlyingAttachedEffect(AttachmentType attachmentType) { - super(Duration.WhileOnBattlefield, Outcome.Detriment); + super(Duration.WhileOnBattlefield); if (attachmentType.equals(AttachmentType.AURA)) { this.staticText = "Enchanted creature can block only creatures with flying"; } else { @@ -59,34 +56,19 @@ public class CanBlockOnlyFlyingAttachedEffect extends ReplacementEffectImpl impl super(effect); } + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return permanent.getAttachments().contains(source.getSourceId()); + } + + @Override + public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) { + return attacker.getAbilities().contains(FlyingAbility.getInstance()); + } + @Override public CanBlockOnlyFlyingAttachedEffect copy() { return new CanBlockOnlyFlyingAttachedEffect(this); } - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - return true; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getType() == EventType.DECLARE_BLOCKER) { - Permanent attachment = game.getPermanent(source.getSourceId()); - if (attachment != null && attachment.getAttachedTo() != null - && event.getSourceId().equals(attachment.getAttachedTo())) { - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent != null && !permanent.getAbilities().containsKey(FlyingAbility.getInstance().getId())) { - return true; - } - } - } - return false; - } - } diff --git a/Mage/src/mage/abilities/effects/common/combat/CantAttackControllerAttachedEffect.java b/Mage/src/mage/abilities/effects/common/combat/CantAttackControllerAttachedEffect.java index 3cb3ca6d9bd..9396fe95a44 100644 --- a/Mage/src/mage/abilities/effects/common/combat/CantAttackControllerAttachedEffect.java +++ b/Mage/src/mage/abilities/effects/common/combat/CantAttackControllerAttachedEffect.java @@ -28,82 +28,53 @@ package mage.abilities.effects.common.combat; +import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.MageSingleton; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.RestrictionEffect; import mage.constants.AttachmentType; import mage.constants.Duration; -import mage.constants.Outcome; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; -import mage.players.Player; /** * * @author LevelX2 */ -public class CantAttackControllerAttachedEffect extends ReplacementEffectImpl implements MageSingleton { - /** - * The creature this permanent is attached to can't attack the controller - * of the attachment nor it's plainswalkers - * - * @param attachmentType - */ +public class CantAttackControllerAttachedEffect extends RestrictionEffect { + public CantAttackControllerAttachedEffect(AttachmentType attachmentType) { - super(Duration.WhileOnBattlefield, Outcome.Detriment); + super(Duration.WhileOnBattlefield); if (attachmentType.equals(AttachmentType.AURA)) { this.staticText = "Enchanted creature can't attack you or a planeswalker you control"; } else { this.staticText = "Equiped creature can't attack you or a planeswalker you control"; - } + } } public CantAttackControllerAttachedEffect(final CantAttackControllerAttachedEffect effect) { super(effect); } + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return permanent.getAttachments().contains(source.getSourceId()); + } + + @Override + public boolean canAttack(UUID defenderId, Ability source, Game game) { + if (defenderId.equals(source.getControllerId())) { + return false; + } + Permanent plainswalker = game.getPermanent(defenderId); + return plainswalker == null || !plainswalker.getControllerId().equals(source.getSourceId()); + } + + @Override public CantAttackControllerAttachedEffect copy() { return new CantAttackControllerAttachedEffect(this); } - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - Player attackingPlayer = game.getPlayer(event.getPlayerId()); - if (attackingPlayer != null && sourcePermanent != null) { - game.informPlayer(attackingPlayer, - new StringBuilder("You can't attack this player or his or her planeswalker, because the attacking creature is enchanted by ") - .append(sourcePermanent.getName()).append(".").toString()); - } - return true; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getType() == EventType.DECLARE_ATTACKER) { - Permanent attachment = game.getPermanent(source.getSourceId()); - if (attachment != null && attachment.getAttachedTo() != null - && event.getSourceId().equals(attachment.getAttachedTo())) { - if (event.getTargetId().equals(source.getControllerId())) { - return true; - } - Permanent plainswalker = game.getPermanent(event.getTargetId()); - if (plainswalker != null && plainswalker.getControllerId().equals(source.getSourceId())) { - return true; - } - } - } - return false; - } - } diff --git a/Mage/src/mage/abilities/effects/common/combat/CantAttackTargetEffect.java b/Mage/src/mage/abilities/effects/common/combat/CantAttackTargetEffect.java index 1c31b44057e..887c8f401d5 100644 --- a/Mage/src/mage/abilities/effects/common/combat/CantAttackTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/combat/CantAttackTargetEffect.java @@ -50,10 +50,7 @@ public class CantAttackTargetEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - if (permanent.getId().equals(targetPointer.getFirst(game, source))) { - return true; - } - return false; + return permanent.getId().equals(targetPointer.getFirst(game, source)); } @Override diff --git a/Mage/src/mage/game/permanent/Permanent.java b/Mage/src/mage/game/permanent/Permanent.java index df78f27956f..5326fae9c0e 100644 --- a/Mage/src/mage/game/permanent/Permanent.java +++ b/Mage/src/mage/game/permanent/Permanent.java @@ -150,6 +150,14 @@ public interface Permanent extends Card, Controllable { void setMaxBlockedBy(int maxBlockedBy); boolean canAttack(Game game); + + /** + * + * @param defenderId id of planeswalker or player to attack + * @param game + * @return + */ + boolean canAttack(UUID defenderId, Game game); boolean canBlock(UUID attackerId, Game game); boolean canBlockAny(Game game); diff --git a/Mage/src/mage/game/permanent/PermanentImpl.java b/Mage/src/mage/game/permanent/PermanentImpl.java index 330f894d004..22166a40157 100644 --- a/Mage/src/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/mage/game/permanent/PermanentImpl.java @@ -857,8 +857,15 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return game.replaceEvent(GameEvent.getEvent(eventType, this.objectId, ownerId)); } + + @Override public boolean canAttack(Game game) { + return canAttack(null, game); + } + + @Override + public boolean canAttack(UUID defenderId, Game game) { if (tapped) { return false; } @@ -866,15 +873,22 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return false; } //20101001 - 508.1c - for (RestrictionEffect effect: game.getContinuousEffects().getApplicableRestrictionEffects(this, game).keySet()) { - if (!effect.canAttack(game)) { + for (Map.Entry> effectEntry: game.getContinuousEffects().getApplicableRestrictionEffects(this, game).entrySet()) { + if (!effectEntry.getKey().canAttack(game)) { return false; } + if (defenderId != null) { + for (Ability ability :effectEntry.getValue()) { + if (!effectEntry.getKey().canAttack(defenderId, ability, game)) { + return false; + } + } + } } return !abilities.containsKey(DefenderAbility.getInstance().getId()) || game.getContinuousEffects().asThough(this.objectId, AsThoughEffectType.ATTACK, this.getControllerId(), game); } - + @Override public boolean canBlock(UUID attackerId, Game game) { if (tapped && !game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, this.getControllerId(), game)) { diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index c32f3406a17..6ff0e24bd38 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -321,6 +321,7 @@ public interface Player extends MageItem, Copyable { void declareAttacker(UUID attackerId, UUID defenderId, Game game, boolean allowUndo); void declareBlocker(UUID defenderId, UUID blockerId, UUID attackerId, Game game); List getAvailableAttackers(Game game); + List getAvailableAttackers(UUID defenderId, Game game); List getAvailableBlockers(Game game); void beginTurn(Game game); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 7b9a30d2696..668d12a1a63 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -1673,7 +1673,7 @@ public abstract class PlayerImpl implements Player, Serializable { setStoredBookmark(game.bookmarkState()); // makes it possible to UNDO a declared attacker with costs from e.g. Propaganda } Permanent attacker = game.getPermanent(attackerId); - if (attacker != null && attacker.canAttack(game) && attacker.getControllerId().equals(playerId)) { + if (attacker != null && attacker.canAttack(defenderId, game) && attacker.getControllerId().equals(playerId)) { if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_ATTACKER, defenderId, attackerId, playerId))) { game.getCombat().declareAttacker(attackerId, defenderId, game); } @@ -1767,13 +1767,19 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public List getAvailableAttackers(Game game) { + // TODO: get available opponents and their planeswalkers, check for each if permanent can attack one + return getAvailableAttackers(null, game); + } + + @Override + public List getAvailableAttackers(UUID defenderId, Game game) { FilterCreatureForCombat filter = new FilterCreatureForCombat(); List attackers = game.getBattlefield().getAllActivePermanents(filter, playerId, game); for (Iterator i = attackers.iterator(); i.hasNext();) { Permanent entry = i.next(); - if (!entry.canAttack(game)) { + if (!entry.canAttack(defenderId, game)) { i.remove(); - } + } } return attackers; }