diff --git a/Mage.Sets/src/mage/sets/magic2013/OdricMasterTactician.java b/Mage.Sets/src/mage/sets/magic2013/OdricMasterTactician.java index 58489b1153..2a2985edc5 100644 --- a/Mage.Sets/src/mage/sets/magic2013/OdricMasterTactician.java +++ b/Mage.Sets/src/mage/sets/magic2013/OdricMasterTactician.java @@ -152,6 +152,7 @@ class OdricMasterTacticianEffect extends ReplacementEffectImpl implements MageSingleton { + + private static final CantAttackAloneAbility fINSTANCE = new CantAttackAloneAbility(); + + private Object readResolve() throws ObjectStreamException { + return fINSTANCE; + } + + public static CantAttackAloneAbility getInstance() { + return fINSTANCE; + } + + private CantAttackAloneAbility() { + super(Zone.BATTLEFIELD, null); + } + + @Override + public String getRule() { + return "Can't attack alone"; + } + + @Override + public CantAttackAloneAbility copy() { + return fINSTANCE; + } + +} diff --git a/Mage/src/mage/abilities/keyword/CantBlockAloneAbility.java b/Mage/src/mage/abilities/keyword/CantBlockAloneAbility.java new file mode 100644 index 0000000000..02305501b6 --- /dev/null +++ b/Mage/src/mage/abilities/keyword/CantBlockAloneAbility.java @@ -0,0 +1,66 @@ +/* +* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, are +* permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, this list of +* conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, this list +* of conditions and the following disclaimer in the documentation and/or other materials +* provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR +* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* The views and conclusions contained in the software and documentation are those of the +* authors and should not be interpreted as representing official policies, either expressed +* or implied, of BetaSteward_at_googlemail.com. +*/ + +package mage.abilities.keyword; + +import mage.Constants.Zone; +import mage.abilities.MageSingleton; +import mage.abilities.StaticAbility; + +import java.io.ObjectStreamException; + +/** + * @author magenoxx_at_googlemail.com + */ +public class CantBlockAloneAbility extends StaticAbility implements MageSingleton { + + private static final CantBlockAloneAbility fINSTANCE = new CantBlockAloneAbility(); + + private Object readResolve() throws ObjectStreamException { + return fINSTANCE; + } + + public static CantBlockAloneAbility getInstance() { + return fINSTANCE; + } + + private CantBlockAloneAbility() { + super(Zone.BATTLEFIELD, null); + } + + @Override + public String getRule() { + return "Can't block alone"; + } + + @Override + public CantBlockAloneAbility copy() { + return fINSTANCE; + } + +} diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index 5f50600a14..4497aa291f 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -30,6 +30,7 @@ package mage.game.combat; import mage.Constants.Outcome; import mage.abilities.effects.RequirementEffect; +import mage.abilities.keyword.CantAttackAloneAbility; import mage.abilities.keyword.VigilanceAbility; import mage.filter.common.FilterCreatureForCombat; import mage.filter.common.FilterPlaneswalkerPermanent; @@ -128,6 +129,7 @@ public class Combat implements Serializable, Copyable { player.selectAttackers(game, attackerId); if (game.isPaused() || game.isGameOver()) return; + checkAttackRestrictions(player, game); resumeSelectAttackers(game); } } @@ -167,6 +169,28 @@ public class Combat implements Serializable, Copyable { } } + protected void checkAttackRestrictions(Player player, Game game) { + int count = 0; + for (CombatGroup group: groups) { + count += group.getAttackers().size(); + } + if (count == 1) { + for (CombatGroup group: groups) { + List tobeRemoved = new ArrayList(); + for (UUID attackerId: group.getAttackers()) { + Permanent attacker = game.getPermanent(attackerId); + if (attacker != null && attacker.getAbilities().containsKey(CantAttackAloneAbility.getInstance().getId())) { + game.informPlayers(attacker.getName() + " can't attack alone. Removing it from combat."); + tobeRemoved.add(attackerId); + } + } + for (UUID attackerId : tobeRemoved) { + group.remove(attackerId); + } + } + } + } + public void selectBlockers(Game game) { if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_BLOCKERS, attackerId, attackerId))) { Player player = game.getPlayer(attackerId); @@ -176,11 +200,28 @@ public class Combat implements Serializable, Copyable { game.getPlayer(defenderId).selectBlockers(game, defenderId); if (game.isPaused() || game.isGameOver()) return; + checkBlockRestrictions(game.getPlayer(defenderId), game); game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); } } } + public void checkBlockRestrictions(Player player, Game game) { + int count = 0; + for (CombatGroup group: groups) { + count += group.getBlockers().size(); + } + for (CombatGroup group : groups) { + group.checkBlockRestrictions(game, count); + } + } + + public void acceptBlockers(Game game) { + for (CombatGroup group : groups) { + group.acceptBlockers(game); + } + } + public void resumeSelectBlockers(Game game) { //TODO: this isn't quite right - but will work fine for two-player games for (UUID defenderId : getPlayerDefenders(game)) { @@ -206,12 +247,6 @@ public class Combat implements Serializable, Copyable { } } - public void checkBlockRestrictions(Game game) { - for (CombatGroup group : groups) { - group.checkBlockRestrictions(game); - } - } - public void setDefenders(Game game) { Set opponents = game.getOpponents(attackerId); PlayerList players; diff --git a/Mage/src/mage/game/combat/CombatGroup.java b/Mage/src/mage/game/combat/CombatGroup.java index d9709e196f..48968964c9 100644 --- a/Mage/src/mage/game/combat/CombatGroup.java +++ b/Mage/src/mage/game/combat/CombatGroup.java @@ -30,10 +30,7 @@ package mage.game.combat; import mage.Constants.Outcome; import mage.abilities.common.DamageAsThoughNotBlockedAbility; -import mage.abilities.keyword.DeathtouchAbility; -import mage.abilities.keyword.DoubleStrikeAbility; -import mage.abilities.keyword.FirstStrikeAbility; -import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -492,10 +489,42 @@ public class CombatGroup implements Serializable, Copyable { } } - public void checkBlockRestrictions(Game game) { + public void acceptBlockers(Game game) { if (attackers.isEmpty()) { return; } + for (UUID blockerId : blockers) { + for (UUID attackerId: attackers) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BLOCKER_DECLARED, attackerId, blockerId, players.get(blockerId))); + } + } + if(!blockers.isEmpty()) { + for (UUID attackerId: attackers) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKED, attackerId, null)); + } + } + } + + public void checkBlockRestrictions(Game game, int blockersCount) { + if (attackers.isEmpty()) { + return; + } + if (blockersCount == 1) { + List toBeRemoved = new ArrayList(); + for (UUID blockerId: getBlockers()) { + Permanent blocker = game.getPermanent(blockerId); + if (blocker != null && blocker.getAbilities().containsKey(CantBlockAloneAbility.getInstance().getId())) { + game.informPlayers(blocker.getName() + " can't block alone. Removing it from combat."); + toBeRemoved.add(blockerId); + } + } + for (UUID blockerId : toBeRemoved) { + remove(blockerId); + } + if (blockers.size() == 0) { + this.blocked = false; + } + } for (UUID uuid : attackers) { Permanent attacker = game.getPermanent(uuid); if (attacker != null && this.blocked && attacker.getMinBlockedBy() > 1 && blockers.size() > 0 && blockers.size() < attacker.getMinBlockedBy()) { @@ -512,16 +541,6 @@ public class CombatGroup implements Serializable, Copyable { return; } } - for (UUID blockerId : blockers) { - for (UUID attackerId: attackers) { - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BLOCKER_DECLARED, attackerId, blockerId, players.get(blockerId))); - } - } - if(!blockers.isEmpty()) { - for (UUID attackerId: attackers) { - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKED, attackerId, null)); - } - } } @Override diff --git a/Mage/src/mage/game/turn/DeclareBlockersStep.java b/Mage/src/mage/game/turn/DeclareBlockersStep.java index 22d28fb05a..5900edb6d2 100644 --- a/Mage/src/mage/game/turn/DeclareBlockersStep.java +++ b/Mage/src/mage/game/turn/DeclareBlockersStep.java @@ -28,11 +28,12 @@ package mage.game.turn; -import java.util.UUID; import mage.Constants.PhaseStep; import mage.game.Game; import mage.game.events.GameEvent.EventType; +import java.util.UUID; + /** * * @author BetaSteward_at_googlemail.com @@ -62,7 +63,7 @@ public class DeclareBlockersStep extends Step { super.beginStep(game, activePlayerId); game.getCombat().selectBlockers(game); if (!game.isPaused()) { - game.getCombat().checkBlockRestrictions(game); + game.getCombat().acceptBlockers(game); game.getCombat().damageAssignmentOrder(game); } } @@ -71,7 +72,7 @@ public class DeclareBlockersStep extends Step { public void resumeBeginStep(Game game, UUID activePlayerId) { super.resumeBeginStep(game, activePlayerId); game.getCombat().resumeSelectBlockers(game); - game.getCombat().checkBlockRestrictions(game); + game.getCombat().acceptBlockers(game); game.getCombat().damageAssignmentOrder(game); }