From 4d01eb143a85ec9cf57e218a089fc7e748ab795b Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 8 Dec 2015 12:20:37 +0100 Subject: [PATCH] Redesigned handling of attack allowed check related to the complete attack. --- .../mage/sets/bornofthegods/LoyalPegasus.java | 2 +- .../sets/dragonsmaze/MasterOfCruelties.java | 10 +- .../src/mage/sets/gatecrash/EmberBeast.java | 6 +- .../sets/journeyintonyx/SightlessBrawler.java | 13 +- .../mage/sets/magic2010/JackalFamiliar.java | 9 +- .../src/mage/sets/magic2013/MoggFlunkies.java | 9 +- .../sets/magicorigins/BondedConstruct.java | 2 +- .../mage/sets/masterseditionii/Errantry.java | 10 +- .../combat/CantAttackOrBlockAloneTest.java | 17 +++ .../abilities/effects/RestrictionEffect.java | 4 + .../combat/CanAttackOnlyAloneEffect.java | 43 +++++++ .../common/combat/CantAttackAloneEffect.java | 43 +++++++ .../keyword/CanAttackOnlyAloneAbility.java | 81 ++++++------- .../keyword/CantAttackAloneAbility.java | 82 ++++++------- .../main/java/mage/game/combat/Combat.java | 112 ++++++++++-------- 15 files changed, 267 insertions(+), 176 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/effects/common/combat/CanAttackOnlyAloneEffect.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackAloneEffect.java diff --git a/Mage.Sets/src/mage/sets/bornofthegods/LoyalPegasus.java b/Mage.Sets/src/mage/sets/bornofthegods/LoyalPegasus.java index 0a01871d90..bd2ab37453 100644 --- a/Mage.Sets/src/mage/sets/bornofthegods/LoyalPegasus.java +++ b/Mage.Sets/src/mage/sets/bornofthegods/LoyalPegasus.java @@ -53,7 +53,7 @@ public class LoyalPegasus extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // Loyal Pegasus can't attack or block alone. - this.addAbility(CantAttackAloneAbility.getInstance()); + this.addAbility(new CantAttackAloneAbility()); this.addAbility(CantBlockAloneAbility.getInstance()); } diff --git a/Mage.Sets/src/mage/sets/dragonsmaze/MasterOfCruelties.java b/Mage.Sets/src/mage/sets/dragonsmaze/MasterOfCruelties.java index 776570e5a0..50a8dd16d8 100644 --- a/Mage.Sets/src/mage/sets/dragonsmaze/MasterOfCruelties.java +++ b/Mage.Sets/src/mage/sets/dragonsmaze/MasterOfCruelties.java @@ -69,7 +69,7 @@ public class MasterOfCruelties extends CardImpl { // Deathtouch this.addAbility(DeathtouchAbility.getInstance()); // Master of Cruelties can only attack alone. - this.addAbility(CanAttackOnlyAloneAbility.getInstance()); + this.addAbility(new CanAttackOnlyAloneAbility()); // Whenever Master of Cruelties attacks a player and isn't blocked, that player's life total becomes 1. Master of Cruelties assigns no combat damage this combat. this.addAbility(new MasterOfCrueltiesTriggeredAbility()); @@ -111,7 +111,7 @@ class MasterOfCrueltiesTriggeredAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { Permanent sourcePermanent = game.getPermanent(getSourceId()); if (sourcePermanent.isAttacking()) { - for (CombatGroup combatGroup: game.getCombat().getGroups()) { + for (CombatGroup combatGroup : game.getCombat().getGroups()) { if (combatGroup.getBlockers().isEmpty() && combatGroup.getAttackers().contains(getSourceId())) { // check if a player is attacked (instead of a planeswalker) Player defendingPlayer = game.getPlayer(combatGroup.getDefenderId()); @@ -184,11 +184,11 @@ class MasterOfCrueltiesNoDamageEffect extends ContinuousRuleModifyingEffectImpl return false; } } - + @Override public boolean applies(GameEvent event, Ability source, Game game) { DamageEvent damageEvent = (DamageEvent) event; - return event.getSourceId().equals(source.getSourceId()) && damageEvent.isCombatDamage(); - + return event.getSourceId().equals(source.getSourceId()) && damageEvent.isCombatDamage(); + } } diff --git a/Mage.Sets/src/mage/sets/gatecrash/EmberBeast.java b/Mage.Sets/src/mage/sets/gatecrash/EmberBeast.java index 77ebea7de2..3451bdc4a5 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/EmberBeast.java +++ b/Mage.Sets/src/mage/sets/gatecrash/EmberBeast.java @@ -28,12 +28,12 @@ package mage.sets.gatecrash; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Rarity; import mage.MageInt; import mage.abilities.keyword.CantAttackAloneAbility; import mage.abilities.keyword.CantBlockAloneAbility; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; /** * @@ -50,7 +50,7 @@ public class EmberBeast extends CardImpl { this.toughness = new MageInt(4); // Ember Beast can't attack or block alone. - this.addAbility(CantAttackAloneAbility.getInstance()); + this.addAbility(new CantAttackAloneAbility()); this.addAbility(CantBlockAloneAbility.getInstance()); } diff --git a/Mage.Sets/src/mage/sets/journeyintonyx/SightlessBrawler.java b/Mage.Sets/src/mage/sets/journeyintonyx/SightlessBrawler.java index c12742e364..8ef66a7f06 100644 --- a/Mage.Sets/src/mage/sets/journeyintonyx/SightlessBrawler.java +++ b/Mage.Sets/src/mage/sets/journeyintonyx/SightlessBrawler.java @@ -32,12 +32,11 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.Effect; +import mage.abilities.effects.common.combat.CanAttackOnlyAloneEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; -import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.keyword.BestowAbility; import mage.abilities.keyword.CantAttackAloneAbility; import mage.cards.CardImpl; -import mage.constants.AttachmentType; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Rarity; @@ -61,16 +60,16 @@ public class SightlessBrawler extends CardImpl { // Bestow 4W (If you cast this card for its bestow cost, it's an Aura spell with enchant creature. It becomes a creature again if it's not attached to a creature.) this.addAbility(new BestowAbility(this, "{4}{W}")); // Sightless Brawler can't attack alone. - this.addAbility(CantAttackAloneAbility.getInstance()); + this.addAbility(new CantAttackAloneAbility()); // Enchanted creature gets +3/+2 and can't attack alone. - Effect effect = new BoostEnchantedEffect(3,2, Duration.WhileOnBattlefield); + Effect effect = new BoostEnchantedEffect(3, 2, Duration.WhileOnBattlefield); effect.setText("Enchanted creature gets +3/+2"); Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, effect); - effect = new GainAbilityAttachedEffect(CantAttackAloneAbility.getInstance(), AttachmentType.AURA, Duration.WhileOnBattlefield); + effect = new CanAttackOnlyAloneEffect(); effect.setText("and can't attack alone"); ability.addEffect(effect); - this.addAbility(ability); - + this.addAbility(ability); + } public SightlessBrawler(final SightlessBrawler card) { diff --git a/Mage.Sets/src/mage/sets/magic2010/JackalFamiliar.java b/Mage.Sets/src/mage/sets/magic2010/JackalFamiliar.java index 3b37baf1bd..67059c5f0f 100644 --- a/Mage.Sets/src/mage/sets/magic2010/JackalFamiliar.java +++ b/Mage.Sets/src/mage/sets/magic2010/JackalFamiliar.java @@ -27,14 +27,13 @@ */ package mage.sets.magic2010; -import mage.constants.CardType; -import mage.constants.Rarity; +import java.util.UUID; import mage.MageInt; import mage.abilities.keyword.CantAttackAloneAbility; import mage.abilities.keyword.CantBlockAloneAbility; import mage.cards.CardImpl; - -import java.util.UUID; +import mage.constants.CardType; +import mage.constants.Rarity; /** * @author magenoxx_at_gmail.com @@ -50,7 +49,7 @@ public class JackalFamiliar extends CardImpl { this.toughness = new MageInt(2); // Jackal Familiar can't attack or block alone. - this.addAbility(CantAttackAloneAbility.getInstance()); + this.addAbility(new CantAttackAloneAbility()); this.addAbility(CantBlockAloneAbility.getInstance()); } diff --git a/Mage.Sets/src/mage/sets/magic2013/MoggFlunkies.java b/Mage.Sets/src/mage/sets/magic2013/MoggFlunkies.java index 79bd878552..8f101217fc 100644 --- a/Mage.Sets/src/mage/sets/magic2013/MoggFlunkies.java +++ b/Mage.Sets/src/mage/sets/magic2013/MoggFlunkies.java @@ -27,14 +27,13 @@ */ package mage.sets.magic2013; -import mage.constants.CardType; -import mage.constants.Rarity; +import java.util.UUID; import mage.MageInt; import mage.abilities.keyword.CantAttackAloneAbility; import mage.abilities.keyword.CantBlockAloneAbility; import mage.cards.CardImpl; - -import java.util.UUID; +import mage.constants.CardType; +import mage.constants.Rarity; /** * @author magenoxx_at_gmail.com @@ -50,7 +49,7 @@ public class MoggFlunkies extends CardImpl { this.toughness = new MageInt(3); // Mogg Flunkies can't attack or block alone. - this.addAbility(CantAttackAloneAbility.getInstance()); + this.addAbility(new CantAttackAloneAbility()); this.addAbility(CantBlockAloneAbility.getInstance()); } diff --git a/Mage.Sets/src/mage/sets/magicorigins/BondedConstruct.java b/Mage.Sets/src/mage/sets/magicorigins/BondedConstruct.java index 5f464c632a..95f00778a4 100644 --- a/Mage.Sets/src/mage/sets/magicorigins/BondedConstruct.java +++ b/Mage.Sets/src/mage/sets/magicorigins/BondedConstruct.java @@ -48,7 +48,7 @@ public class BondedConstruct extends CardImpl { this.toughness = new MageInt(1); // Bonded Construct can't attack alone. - this.addAbility(CantAttackAloneAbility.getInstance()); + this.addAbility(new CantAttackAloneAbility()); } public BondedConstruct(final BondedConstruct card) { diff --git a/Mage.Sets/src/mage/sets/masterseditionii/Errantry.java b/Mage.Sets/src/mage/sets/masterseditionii/Errantry.java index b9214d5aa2..2685f464a8 100644 --- a/Mage.Sets/src/mage/sets/masterseditionii/Errantry.java +++ b/Mage.Sets/src/mage/sets/masterseditionii/Errantry.java @@ -32,12 +32,10 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.combat.CanAttackOnlyAloneEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; -import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; -import mage.abilities.keyword.CanAttackOnlyAloneAbility; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; -import mage.constants.AttachmentType; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; @@ -49,7 +47,7 @@ import mage.target.common.TargetCreaturePermanent; /** * * @author LoneFox - + * */ public class Errantry extends CardImpl { @@ -66,8 +64,8 @@ public class Errantry extends CardImpl { this.addAbility(ability); // Enchanted creature gets +3/+0 and can only attack alone. ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(3, 0, Duration.WhileOnBattlefield)); - Effect effect = new GainAbilityAttachedEffect(CanAttackOnlyAloneAbility.getInstance(), AttachmentType.AURA); - effect.setText("and can only attack alone."); + Effect effect = new CanAttackOnlyAloneEffect(); + effect.setText("and can only attack alone"); ability.addEffect(effect); this.addAbility(ability); } diff --git a/Mage.Tests/src/test/java/org/mage/test/combat/CantAttackOrBlockAloneTest.java b/Mage.Tests/src/test/java/org/mage/test/combat/CantAttackOrBlockAloneTest.java index a668c46beb..0a24dab7a2 100644 --- a/Mage.Tests/src/test/java/org/mage/test/combat/CantAttackOrBlockAloneTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/combat/CantAttackOrBlockAloneTest.java @@ -48,6 +48,23 @@ public class CantAttackOrBlockAloneTest extends CardTestPlayerBase { assertLife(playerA, 15); } + /** + * Try to attack with two Flunkies + */ + @Test + public void testCanAttackWithTwo() { + addCard(Zone.BATTLEFIELD, playerB, "Mogg Flunkies", 2); // 3/3 + + attack(2, playerB, "Mogg Flunkies"); + attack(2, playerB, "Mogg Flunkies"); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 14); + + } + /** * Try to block alone */ diff --git a/Mage/src/main/java/mage/abilities/effects/RestrictionEffect.java b/Mage/src/main/java/mage/abilities/effects/RestrictionEffect.java index 47f89ec602..12bf04ef22 100644 --- a/Mage/src/main/java/mage/abilities/effects/RestrictionEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/RestrictionEffect.java @@ -69,6 +69,10 @@ public abstract class RestrictionEffect extends ContinuousEffectImpl { return true; } + public boolean canAttackCheckAfter(int numberOfAttackers, Ability source, Game game) { + return true; + } + public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game) { return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CanAttackOnlyAloneEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CanAttackOnlyAloneEffect.java new file mode 100644 index 0000000000..2df2b790ad --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CanAttackOnlyAloneEffect.java @@ -0,0 +1,43 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.abilities.effects.common.combat; + +import mage.abilities.Ability; +import mage.abilities.effects.RestrictionEffect; +import mage.constants.Duration; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author LevelX2 + */ +public class CanAttackOnlyAloneEffect extends RestrictionEffect { + + public CanAttackOnlyAloneEffect() { + super(Duration.WhileOnBattlefield); + staticText = "{this} can only attack alone"; + } + + public CanAttackOnlyAloneEffect(final CanAttackOnlyAloneEffect effect) { + super(effect); + } + + @Override + public CanAttackOnlyAloneEffect copy() { + return new CanAttackOnlyAloneEffect(this); + } + + @Override + public boolean canAttackCheckAfter(int numberOfAttackers, Ability source, Game game) { + return numberOfAttackers == 1; + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return source.getSourceId().equals(permanent.getId()); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackAloneEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackAloneEffect.java new file mode 100644 index 0000000000..6d8cd6d610 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackAloneEffect.java @@ -0,0 +1,43 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.abilities.effects.common.combat; + +import mage.abilities.Ability; +import mage.abilities.effects.RestrictionEffect; +import mage.constants.Duration; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author LevelX2 + */ +public class CantAttackAloneEffect extends RestrictionEffect { + + public CantAttackAloneEffect() { + super(Duration.WhileOnBattlefield); + staticText = "{this} can't attack alone"; + } + + public CantAttackAloneEffect(final CantAttackAloneEffect effect) { + super(effect); + } + + @Override + public CantAttackAloneEffect copy() { + return new CantAttackAloneEffect(this); + } + + @Override + public boolean canAttackCheckAfter(int numberOfAttackers, Ability source, Game game) { + return numberOfAttackers > 1; + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return source.getSourceId().equals(permanent.getId()); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/CanAttackOnlyAloneAbility.java b/Mage/src/main/java/mage/abilities/keyword/CanAttackOnlyAloneAbility.java index 55aa82e390..f37cb247fe 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CanAttackOnlyAloneAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CanAttackOnlyAloneAbility.java @@ -1,65 +1,52 @@ /* -* 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. -*/ - + * 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 java.io.ObjectStreamException; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.combat.CanAttackOnlyAloneEffect; import mage.constants.Zone; -import mage.abilities.MageSingleton; -import mage.abilities.StaticAbility; /** * @author LevelX2 */ -public class CanAttackOnlyAloneAbility extends StaticAbility implements MageSingleton { +public class CanAttackOnlyAloneAbility extends SimpleStaticAbility { - private static final CanAttackOnlyAloneAbility fINSTANCE = new CanAttackOnlyAloneAbility(); - - private Object readResolve() throws ObjectStreamException { - return fINSTANCE; + public CanAttackOnlyAloneAbility() { + super(Zone.BATTLEFIELD, new CanAttackOnlyAloneEffect()); } - public static CanAttackOnlyAloneAbility getInstance() { - return fINSTANCE; - } - - private CanAttackOnlyAloneAbility() { - super(Zone.BATTLEFIELD, null); - } - - @Override - public String getRule() { - return "{this} can only attack alone."; + private CanAttackOnlyAloneAbility(CanAttackOnlyAloneAbility ability) { + super(ability); } @Override public CanAttackOnlyAloneAbility copy() { - return fINSTANCE; + return new CanAttackOnlyAloneAbility(this); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/CantAttackAloneAbility.java b/Mage/src/main/java/mage/abilities/keyword/CantAttackAloneAbility.java index a7b0d7ef72..fecbd9dcdd 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CantAttackAloneAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CantAttackAloneAbility.java @@ -1,66 +1,52 @@ /* -* 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. -*/ - + * 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.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.combat.CantAttackAloneEffect; import mage.constants.Zone; -import mage.abilities.MageSingleton; -import mage.abilities.StaticAbility; - -import java.io.ObjectStreamException; /** * @author magenoxx_at_googlemail.com */ -public class CantAttackAloneAbility extends StaticAbility implements MageSingleton { +public class CantAttackAloneAbility extends SimpleStaticAbility { - private static final CantAttackAloneAbility fINSTANCE = new CantAttackAloneAbility(); - - private Object readResolve() throws ObjectStreamException { - return fINSTANCE; + public CantAttackAloneAbility() { + super(Zone.BATTLEFIELD, new CantAttackAloneEffect()); } - public static CantAttackAloneAbility getInstance() { - return fINSTANCE; - } - - private CantAttackAloneAbility() { - super(Zone.BATTLEFIELD, null); - } - - @Override - public String getRule() { - return "{this} can't attack alone."; + private CantAttackAloneAbility(CantAttackAloneAbility ability) { + super(ability); } @Override public CantAttackAloneAbility copy() { - return fINSTANCE; + return new CantAttackAloneAbility(this); } } diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 7b041bdef5..aa6c20c20f 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -36,11 +36,10 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.RestrictionEffect; -import mage.abilities.keyword.CanAttackOnlyAloneAbility; -import mage.abilities.keyword.CantAttackAloneAbility; import mage.abilities.keyword.VigilanceAbility; import mage.constants.CardType; import mage.constants.Outcome; @@ -58,12 +57,15 @@ import mage.target.common.TargetDefender; import mage.util.CardUtil; import mage.util.Copyable; import mage.util.trace.TraceUtil; +import org.apache.log4j.Logger; /** * @author BetaSteward_at_googlemail.com */ public class Combat implements Serializable, Copyable { + private static final Logger logger = Logger.getLogger(Combat.class); + private static FilterPlaneswalkerPermanent filterPlaneswalker = new FilterPlaneswalkerPermanent(); private static FilterCreatureForCombatBlock filterBlockers = new FilterCreatureForCombatBlock(); // There are effects that let creatures assigns combat damage equal to its toughness rather than its power @@ -252,14 +254,18 @@ public class Combat implements Serializable, Copyable { Player player = game.getPlayer(attackerId); //20101001 - 508.1d game.getCombat().checkAttackRequirements(player, game); - if (!game.getPlayer(game.getActivePlayerId()).getAvailableAttackers(game).isEmpty()) { - player.selectAttackers(game, attackerId); - } - if (game.isPaused() || game.gameOver(null) || game.executingRollback()) { - return; - } - // because of possible undo during declare attackers it's neccassary to call here the methods with "game.getCombat()." to get the valid combat object!!! - game.getCombat().checkAttackRestrictions(player, game); + boolean firstTime = true; + do { + if (!firstTime || !game.getPlayer(game.getActivePlayerId()).getAvailableAttackers(game).isEmpty()) { + player.selectAttackers(game, attackerId); + } + firstTime = false; + if (game.isPaused() || game.gameOver(null) || game.executingRollback()) { + return; + } + // because of possible undo during declare attackers it's neccassary to call here the methods with "game.getCombat()." to get the current combat object!!! + // I don't like it too - it has to be redesigned + } while (!game.getCombat().checkAttackRestrictions(player, game)); game.getCombat().resumeSelectAttackers(game); } } @@ -337,50 +343,60 @@ 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) { - List tobeRemoved = new ArrayList<>(); + /** + * + * @param player + * @param game + * @return true if the attack with that set of creatures and attacked + * players/planeswalkers is possible + */ + protected boolean checkAttackRestrictions(Player player, Game game) { + boolean check = true; + int numberOfChecks = 0; + UUID attackerToRemove = null; + Check: + while (check) { + check = false; + numberOfChecks++; + int numberAttackers = 0; for (CombatGroup group : groups) { - for (UUID attackingCreatureId : group.getAttackers()) { - Permanent attacker = game.getPermanent(attackingCreatureId); - if (count > 1 && attacker != null && attacker.getAbilities().containsKey(CanAttackOnlyAloneAbility.getInstance().getId())) { - if (!game.isSimulation()) { - game.informPlayers(attacker.getLogName() + " can only attack alone. Removing it from combat."); + numberAttackers += group.getAttackers().size(); + } + Player attackingPlayer = game.getPlayer(attackerId); + if (attackerToRemove != null) { + removeAttacker(attackerToRemove, game); + } + for (UUID attackingCreatureId : this.getAttackers()) { + Permanent attackingCreature = game.getPermanent(attackingCreatureId); + for (Map.Entry> entry : game.getContinuousEffects().getApplicableRestrictionEffects(attackingCreature, game).entrySet()) { + RestrictionEffect effect = entry.getKey(); + for (Ability ability : entry.getValue()) { + if (!effect.canAttackCheckAfter(numberAttackers, ability, game)) { + MageObject sourceObject = ability.getSourceObject(game); + if (attackingPlayer.isHuman()) { + game.informPlayer(attackingPlayer, attackingCreature.getIdName() + " can't attack this way (" + (sourceObject == null ? "null" : sourceObject.getIdName()) + ")"); + return false; + } else { + // remove attacking creatures for AI that are not allowed to attack + // can create possible not allowed attack scenarios, but not sure how to solve this + for (CombatGroup combatGroup : this.getGroups()) { + if (combatGroup.getAttackers().contains(attackingCreatureId)) { + attackerToRemove = attackingCreatureId; + } + } + check = true; // do the check again + if (numberOfChecks > 50) { + logger.error("Seems to be an AI declare attacker lock (reached 50 check iterations) " + (sourceObject == null ? "null" : sourceObject.getIdName())); + return true; // break the check + } + continue Check; + } } - tobeRemoved.add(attackingCreatureId); - count--; } } } - for (UUID attackingCreatureId : tobeRemoved) { - this.removeAttacker(attackingCreatureId, game); - } } - - if (count == 1) { - List tobeRemoved = new ArrayList<>(); - for (CombatGroup group : groups) { - for (UUID attackingCreatureId : group.getAttackers()) { - Permanent attacker = game.getPermanent(attackingCreatureId); - if (attacker != null && attacker.getAbilities().containsKey(CantAttackAloneAbility.getInstance().getId())) { - if (!game.isSimulation()) { - game.informPlayers(attacker.getLogName() + " can't attack alone. Removing it from combat."); - } - tobeRemoved.add(attackingCreatureId); - } - } - } - for (UUID attackingCreatureId : tobeRemoved) { - this.removeAttacker(attackingCreatureId, game); - } - - } - + return true; } public void selectBlockers(Game game) {