From 15fe85c5dabd8b96cc2558bbde52e62cc8bf2c76 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 1 Jul 2015 02:00:07 +0200 Subject: [PATCH] Fixed that creatures forced to attack that have to pay a cost to attack lock the UI (not completed for all existing cards yet). --- .../championsofkamigawa/GhostlyPrison.java | 70 ++------------- .../src/mage/sets/exodus/ExaltedDragon.java | 55 +++--------- .../sets/saviorsofkamigawa/CowedByWisdom.java | 11 ++- .../src/mage/sets/tempest/Propaganda.java | 64 +------------ .../mage/sets/tenthedition/WindbornMuse.java | 33 +++---- .../cards/restriction/CantAttackTest.java | 22 +++++ .../mage/abilities/costs/mana/ManaCosts.java | 62 +++++++------ .../abilities/costs/mana/ManaCostsImpl.java | 76 ++++++++-------- .../abilities/effects/ContinuousEffects.java | 24 +++++ .../effects/PayCostToAttackBlockEffect.java | 5 +- .../PayCostToAttackBlockEffectImpl.java | 75 +++++++++++++--- .../CantAttackYouUnlessPayManaAllEffect.java | 41 +++++++++ Mage/src/mage/game/combat/Combat.java | 89 ++++++++++++------- 13 files changed, 326 insertions(+), 301 deletions(-) create mode 100644 Mage/src/mage/abilities/effects/common/replacement/CantAttackYouUnlessPayManaAllEffect.java diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/GhostlyPrison.java b/Mage.Sets/src/mage/sets/championsofkamigawa/GhostlyPrison.java index a59826a420..120e33b61d 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/GhostlyPrison.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/GhostlyPrison.java @@ -25,20 +25,15 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.sets.championsofkamigawa; import java.util.UUID; import mage.constants.*; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.replacement.CantAttackYouUnlessPayManaAllEffect; import mage.cards.CardImpl; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.players.Player; /** * @@ -46,16 +41,16 @@ import mage.players.Player; */ public class GhostlyPrison extends CardImpl { - public GhostlyPrison (UUID ownerId) { + public GhostlyPrison(UUID ownerId) { super(ownerId, 10, "Ghostly Prison", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); this.expansionSetCode = "CHK"; - // Creatures can't attack you unless their controller pays {2} for each creature he or she controls that's attacking you - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GhostlyPrisonReplacementEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantAttackYouUnlessPayManaAllEffect(new ManaCostsImpl("{2}")))); + } - public GhostlyPrison (final GhostlyPrison card) { + public GhostlyPrison(final GhostlyPrison card) { super(card); } @@ -65,58 +60,3 @@ public class GhostlyPrison extends CardImpl { } } - -class GhostlyPrisonReplacementEffect extends ReplacementEffectImpl { - - private static final String effectText = "Creatures can't attack you unless their controller pays {2} for each creature he or she controls that's attacking you"; - - GhostlyPrisonReplacementEffect ( ) { - super(Duration.WhileOnBattlefield, Outcome.Neutral); - staticText = effectText; - } - - GhostlyPrisonReplacementEffect ( GhostlyPrisonReplacementEffect effect ) { - super(effect); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARE_ATTACKER; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getTargetId().equals(source.getControllerId()) ) { - Player attackedPlayer = game.getPlayer(event.getTargetId()); - if (attackedPlayer != null) { - // only if a player is attacked. Attacking a planeswalker is free - return true; - } - } - return false; - } - - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player player = game.getPlayer(event.getPlayerId()); - if ( player != null && event.getTargetId().equals(source.getControllerId())) { - ManaCostsImpl attackTax = new ManaCostsImpl("{2}"); - if (attackTax.canPay(source, source.getSourceId(), event.getPlayerId(), game) && - player.chooseUse(Outcome.Benefit, "Pay {2} to attack player?", source, game) ) { - if (attackTax.payOrRollback(source, game, this.getId(), event.getPlayerId())) { - return false; - } - } - return true; - } - return false; - } - - @Override - public GhostlyPrisonReplacementEffect copy() { - return new GhostlyPrisonReplacementEffect(this); - } - -} - diff --git a/Mage.Sets/src/mage/sets/exodus/ExaltedDragon.java b/Mage.Sets/src/mage/sets/exodus/ExaltedDragon.java index f0aa723125..8c637213d2 100644 --- a/Mage.Sets/src/mage/sets/exodus/ExaltedDragon.java +++ b/Mage.Sets/src/mage/sets/exodus/ExaltedDragon.java @@ -31,9 +31,8 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.common.ReturnToHandTargetPermanentCost; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.PayCostToAttackBlockEffectImpl; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.constants.CardType; @@ -42,11 +41,8 @@ import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; import mage.filter.common.FilterControlledLandPermanent; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.mageobject.CardTypePredicate; import mage.game.Game; import mage.game.events.GameEvent; -import mage.players.Player; import mage.target.common.TargetControlledPermanent; /** @@ -64,9 +60,9 @@ public class ExaltedDragon extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - + // Exalted Dragon can't attack unless you sacrifice a land. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ExaltedDragonReplacementEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ExaltedDragonCostToAttackBlockEffect())); } public ExaltedDragon(final ExaltedDragon card) { @@ -79,55 +75,26 @@ public class ExaltedDragon extends CardImpl { } } -class ExaltedDragonReplacementEffect extends ReplacementEffectImpl { +class ExaltedDragonCostToAttackBlockEffect extends PayCostToAttackBlockEffectImpl { - private static final FilterControlledPermanent filter = new FilterControlledLandPermanent(); - - ExaltedDragonReplacementEffect ( ) { - super(Duration.WhileOnBattlefield, Outcome.Neutral); + ExaltedDragonCostToAttackBlockEffect() { + super(Duration.WhileOnBattlefield, Outcome.Detriment, RestrictType.ATTACK, + new SacrificeTargetCost(new TargetControlledPermanent(new FilterControlledLandPermanent("a land")))); staticText = "{this} can't attack unless you sacrifice a land (This cost is paid as attackers are declared.)"; } - ExaltedDragonReplacementEffect ( ExaltedDragonReplacementEffect effect ) { + ExaltedDragonCostToAttackBlockEffect(ExaltedDragonCostToAttackBlockEffect effect) { super(effect); } - @Override - public boolean apply(Game game, Ability source) { - throw new UnsupportedOperationException("Not supported."); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player player = game.getPlayer(event.getPlayerId()); - if ( player != null ) { - SacrificeTargetCost attackCost = new SacrificeTargetCost(new TargetControlledPermanent(filter)); - if ( attackCost.canPay(source, source.getSourceId(), event.getPlayerId(), game) && - player.chooseUse(Outcome.Neutral, "Sacrifice a land?", source, game) ) - { - if (attackCost.pay(source, game, source.getSourceId(), event.getPlayerId(), false) ) { - return false; - } - } - return true; - } - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARE_ATTACKER; - } - - @Override public boolean applies(GameEvent event, Ability source, Game game) { - return event.getSourceId().equals(source.getSourceId()); + return source.getSourceId().equals(event.getSourceId()); } @Override - public ExaltedDragonReplacementEffect copy() { - return new ExaltedDragonReplacementEffect(this); + public ExaltedDragonCostToAttackBlockEffect copy() { + return new ExaltedDragonCostToAttackBlockEffect(this); } } diff --git a/Mage.Sets/src/mage/sets/saviorsofkamigawa/CowedByWisdom.java b/Mage.Sets/src/mage/sets/saviorsofkamigawa/CowedByWisdom.java index 9ad1c7827b..f112c128dc 100644 --- a/Mage.Sets/src/mage/sets/saviorsofkamigawa/CowedByWisdom.java +++ b/Mage.Sets/src/mage/sets/saviorsofkamigawa/CowedByWisdom.java @@ -30,8 +30,9 @@ package mage.sets.saviorsofkamigawa; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.Cost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCosts; +import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.PayCostToAttackBlockEffectImpl; import mage.abilities.effects.common.AttachEffect; import mage.abilities.keyword.EnchantAbility; @@ -83,7 +84,7 @@ public class CowedByWisdom extends CardImpl { class CowedByWisdomayCostToAttackBlockEffect extends PayCostToAttackBlockEffectImpl { CowedByWisdomayCostToAttackBlockEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment, RestrictType.ATTACK_AND_BLOCK, null); + super(Duration.WhileOnBattlefield, Outcome.Detriment, RestrictType.ATTACK_AND_BLOCK); staticText = "Enchanted creature can't attack or block unless its controller pays {1} for each card in your hand"; } @@ -92,10 +93,12 @@ class CowedByWisdomayCostToAttackBlockEffect extends PayCostToAttackBlockEffectI } @Override - public Cost getCostToPay(GameEvent event, Ability source, Game game) { + public ManaCosts getManaCostToPay(GameEvent event, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null && !controller.getHand().isEmpty()) { - return new GenericManaCost(controller.getHand().size()); + ManaCosts manaCosts = new ManaCostsImpl(); + manaCosts.add(new GenericManaCost(controller.getHand().size())); + return manaCosts; } return null; } diff --git a/Mage.Sets/src/mage/sets/tempest/Propaganda.java b/Mage.Sets/src/mage/sets/tempest/Propaganda.java index e4f36fd168..9b5f60aa26 100644 --- a/Mage.Sets/src/mage/sets/tempest/Propaganda.java +++ b/Mage.Sets/src/mage/sets/tempest/Propaganda.java @@ -28,19 +28,13 @@ package mage.sets.tempest; import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.replacement.CantAttackYouUnlessPayManaAllEffect; import mage.cards.CardImpl; import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.players.Player; /** * @@ -52,7 +46,8 @@ public class Propaganda extends CardImpl { super(ownerId, 80, "Propaganda", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); this.expansionSetCode = "TMP"; - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PropagandaReplacementEffect())); + // Creatures can't attack you unless their controller pays {2} for each creature he or she controls that's attacking you. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantAttackYouUnlessPayManaAllEffect(new ManaCostsImpl("{2}")))); } public Propaganda(final Propaganda card) { @@ -64,56 +59,3 @@ public class Propaganda extends CardImpl { return new Propaganda(this); } } - -class PropagandaReplacementEffect extends ReplacementEffectImpl { - - private static final String effectText = "Creatures can't attack you unless their controller pays {2} for each creature he or she controls that's attacking you"; - - PropagandaReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Neutral); - staticText = effectText; - } - - PropagandaReplacementEffect(PropagandaReplacementEffect effect) { - super(effect); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DECLARE_ATTACKER; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getTargetId().equals(source.getControllerId())) { - Player attackedPlayer = game.getPlayer(event.getTargetId()); - if (attackedPlayer != null) { - // only if a player is attacked. Attacking a planeswalker is free - return true; - } - } - return false; - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player player = game.getPlayer(event.getPlayerId()); - if (player != null) { - ManaCostsImpl attackTax = new ManaCostsImpl("{2}"); - if (attackTax.canPay(source, source.getSourceId(), event.getPlayerId(), game) - && player.chooseUse(Outcome.Neutral, "Pay {2} to attack player?", source, game)) { - if (attackTax.payOrRollback(source, game, source.getSourceId(), event.getPlayerId())) { - return false; - } - } - return true; - } - return false; - } - - @Override - public PropagandaReplacementEffect copy() { - return new PropagandaReplacementEffect(this); - } - -} diff --git a/Mage.Sets/src/mage/sets/tenthedition/WindbornMuse.java b/Mage.Sets/src/mage/sets/tenthedition/WindbornMuse.java index e1d70ddfef..b16714da35 100644 --- a/Mage.Sets/src/mage/sets/tenthedition/WindbornMuse.java +++ b/Mage.Sets/src/mage/sets/tenthedition/WindbornMuse.java @@ -28,15 +28,19 @@ package mage.sets.tenthedition; import java.util.UUID; - -import mage.constants.*; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.replacement.CantAttackYouUnlessPayManaAllEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; @@ -53,12 +57,13 @@ public class WindbornMuse extends CardImpl { this.subtype.add("Spirit"); this.power = new MageInt(2); this.toughness = new MageInt(3); - + // Flying this.addAbility(FlyingAbility.getInstance()); + // Creatures can't attack you unless their controller pays {2} for each creature he or she controls that's attacking you. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new WindbornMuseReplacementEffect())); - + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantAttackYouUnlessPayManaAllEffect(new ManaCostsImpl("{2}")))); + } public WindbornMuse(final WindbornMuse card) { @@ -75,12 +80,12 @@ class WindbornMuseReplacementEffect extends ReplacementEffectImpl { private static final String effectText = "Creatures can't attack you unless their controller pays {2} for each creature he or she controls that's attacking you"; - WindbornMuseReplacementEffect ( ) { + WindbornMuseReplacementEffect() { super(Duration.WhileOnBattlefield, Outcome.Benefit); staticText = effectText; } - WindbornMuseReplacementEffect ( WindbornMuseReplacementEffect effect ) { + WindbornMuseReplacementEffect(WindbornMuseReplacementEffect effect) { super(effect); } @@ -88,10 +93,10 @@ class WindbornMuseReplacementEffect extends ReplacementEffectImpl { public boolean checksEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.DECLARE_ATTACKER; } - + @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getTargetId().equals(source.getControllerId()) ) { + if (event.getTargetId().equals(source.getControllerId())) { Player attackedPlayer = game.getPlayer(event.getTargetId()); if (attackedPlayer != null) { // only if a player is attacked. Attacking a planeswalker is free @@ -100,15 +105,14 @@ class WindbornMuseReplacementEffect extends ReplacementEffectImpl { } return false; } - + @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { Player player = game.getPlayer(event.getPlayerId()); - if ( player != null && event.getTargetId().equals(source.getControllerId())) { + if (player != null && event.getTargetId().equals(source.getControllerId())) { ManaCostsImpl attackTax = new ManaCostsImpl("{2}"); - if ( attackTax.canPay(source, source.getSourceId(), event.getPlayerId(), game) && - player.chooseUse(Outcome.Benefit, "Pay {2} to attack player?", source, game) ) - { + if (attackTax.canPay(source, source.getSourceId(), event.getPlayerId(), game) + && player.chooseUse(Outcome.Benefit, "Pay {2} to attack player?", source, game)) { if (attackTax.payOrRollback(source, game, this.getId(), event.getPlayerId())) { return false; } @@ -124,4 +128,3 @@ class WindbornMuseReplacementEffect extends ReplacementEffectImpl { } } - diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/restriction/CantAttackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/restriction/CantAttackTest.java index 6eee906395..f88a2a0c42 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/restriction/CantAttackTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/restriction/CantAttackTest.java @@ -118,4 +118,26 @@ public class CantAttackTest extends CardTestPlayerBase { assertCounterCount("Ajani Goldmane", CounterType.LOYALTY, 2); } + @Test + public void testCowedByWisdom() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + // Enchant creature + // Enchanted creature can't attack or block unless its controller pays {1} for each card in your hand. + addCard(Zone.HAND, playerA, "Cowed by Wisdom"); // Planeswalker 4 loyality counter + + // Bushido 2 (When this blocks or becomes blocked, it gets +2/+2 until end of turn.) + // Battle-Mad Ronin attacks each turn if able. + addCard(Zone.BATTLEFIELD, playerB, "Battle-Mad Ronin"); + addCard(Zone.HAND, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cowed by Wisdom", "Battle-Mad Ronin"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + + assertTapped("Battle-Mad Ronin", false); + } } diff --git a/Mage/src/mage/abilities/costs/mana/ManaCosts.java b/Mage/src/mage/abilities/costs/mana/ManaCosts.java index 49e4989b36..e21c8fa2ac 100644 --- a/Mage/src/mage/abilities/costs/mana/ManaCosts.java +++ b/Mage/src/mage/abilities/costs/mana/ManaCosts.java @@ -1,36 +1,38 @@ /* -* 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.costs.mana; import java.util.List; +import java.util.UUID; import mage.Mana; +import mage.abilities.Ability; import mage.abilities.costs.VariableCost; +import mage.game.Game; /** * @@ -40,12 +42,18 @@ import mage.abilities.costs.VariableCost; public interface ManaCosts extends List, ManaCost { ManaCosts getUnpaidVariableCosts(); + List getVariableCosts(); + int getX(); + void setX(int x); + void load(String mana); + List getSymbols(); + boolean payOrRollback(Ability ability, Game game, UUID sourceId, UUID payingPlayerId); @Override Mana getMana(); diff --git a/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java b/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java index aca1bd6b37..4b610c2af3 100644 --- a/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java +++ b/Mage/src/mage/abilities/costs/mana/ManaCostsImpl.java @@ -1,31 +1,30 @@ /* -* 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.costs.mana; import java.util.ArrayList; @@ -134,14 +133,16 @@ public class ManaCostsImpl extends ArrayList implements M } /** - * bookmarks the current state and restores it if player doesn't pay the mana cost - * + * bookmarks the current state and restores it if player doesn't pay the + * mana cost + * * @param ability * @param game * @param sourceId * @param payingPlayerId * @return true if the cost was paid */ + @Override public boolean payOrRollback(Ability ability, Game game, UUID sourceId, UUID payingPlayerId) { int bookmark = game.bookmarkState(); if (pay(ability, game, sourceId, payingPlayerId, false)) { @@ -174,7 +175,6 @@ public class ManaCostsImpl extends ArrayList implements M return unpaid; } - @Override public List getVariableCosts() { List variableCosts = new ArrayList<>(); @@ -216,7 +216,6 @@ public class ManaCostsImpl extends ArrayList implements M } //attempt to pay colored costs first - for (ManaCost cost : this) { if (!cost.isPaid() && cost instanceof ColoredManaCost) { cost.assignPayment(game, ability, pool); @@ -234,15 +233,13 @@ public class ManaCostsImpl extends ArrayList implements M cost.assignPayment(game, ability, pool); } } - - + for (ManaCost cost : this) { if (!cost.isPaid() && cost instanceof SnowManaCost) { cost.assignPayment(game, ability, pool); } } - for (ManaCost cost : this) { if (!cost.isPaid() && cost instanceof GenericManaCost) { cost.assignPayment(game, ability, pool); @@ -280,8 +277,7 @@ public class ManaCostsImpl extends ArrayList implements M } else { if (!symbol.equals("X")) { this.add((T) new ColoredManaCost(ColoredManaSymbol.lookup(symbol.charAt(0)))); - } - else { + } else { // check X wasn't added before if (modifierForX == 0) { // count X occurence @@ -296,11 +292,9 @@ public class ManaCostsImpl extends ArrayList implements M //TODO: handle multiple {X} and/or {Y} symbols } } else { - if(symbol.equals("snow")) - { + if (symbol.equals("snow")) { this.add((T) new SnowManaCost()); - } - else if (Character.isDigit(symbol.charAt(0))) { + } else if (Character.isDigit(symbol.charAt(0))) { this.add((T) new MonoHybridManaCost(ColoredManaSymbol.lookup(symbol.charAt(2)))); } else if (symbol.contains("P")) { this.add((T) new PhyrexianManaCost(ColoredManaSymbol.lookup(symbol.charAt(0)))); @@ -443,7 +437,7 @@ public class ManaCostsImpl extends ArrayList implements M @Override public boolean containsColor(ColoredManaSymbol coloredManaSymbol) { - for(ManaCost manaCost: this) { + for (ManaCost manaCost : this) { if (manaCost.containsColor(coloredManaSymbol)) { return true; } diff --git a/Mage/src/mage/abilities/effects/ContinuousEffects.java b/Mage/src/mage/abilities/effects/ContinuousEffects.java index b19c442092..12ae10a9fc 100644 --- a/Mage/src/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/mage/abilities/effects/ContinuousEffects.java @@ -324,6 +324,30 @@ public class ContinuousEffects implements Serializable { return effects; } + public boolean checkIfThereArePayCostToAttackBlockEffects(GameEvent event, Game game) { + for (ReplacementEffect effect : replacementEffects) { + if (!effect.checksEventType(event, game)) { + continue; + } + if (effect instanceof PayCostToAttackBlockEffect) { + HashSet abilities = replacementEffects.getAbility(effect.getId()); + for (Ability ability : abilities) { + // for replacment effects of static abilities do not use LKI to check if to apply + if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, event)) { + if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) { + if (!game.getScopeRelevant() || effect.hasSelfScope() || !event.getTargetId().equals(ability.getSourceId())) { + if (effect.applies(event, ability, game)) { + return true; + } + } + } + } + } + } + } + return false; + } + /** * * @param event diff --git a/Mage/src/mage/abilities/effects/PayCostToAttackBlockEffect.java b/Mage/src/mage/abilities/effects/PayCostToAttackBlockEffect.java index 63920ccddd..f73af6074f 100644 --- a/Mage/src/mage/abilities/effects/PayCostToAttackBlockEffect.java +++ b/Mage/src/mage/abilities/effects/PayCostToAttackBlockEffect.java @@ -29,6 +29,7 @@ package mage.abilities.effects; import mage.abilities.Ability; import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.ManaCosts; import mage.game.Game; import mage.game.events.GameEvent; @@ -38,5 +39,7 @@ import mage.game.events.GameEvent; */ public interface PayCostToAttackBlockEffect extends ReplacementEffect { - Cost getCostToPay(GameEvent event, Ability source, Game game); + ManaCosts getManaCostToPay(GameEvent event, Ability source, Game game); + + Cost getOtherCostToPay(GameEvent event, Ability source, Game game); } diff --git a/Mage/src/mage/abilities/effects/PayCostToAttackBlockEffectImpl.java b/Mage/src/mage/abilities/effects/PayCostToAttackBlockEffectImpl.java index 917665ca82..9473fbcd9c 100644 --- a/Mage/src/mage/abilities/effects/PayCostToAttackBlockEffectImpl.java +++ b/Mage/src/mage/abilities/effects/PayCostToAttackBlockEffectImpl.java @@ -29,12 +29,13 @@ package mage.abilities.effects; import mage.abilities.Ability; import mage.abilities.costs.Cost; -import mage.abilities.costs.mana.ManaCostImpl; +import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCostsImpl; 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.players.Player; /** @@ -49,12 +50,29 @@ public abstract class PayCostToAttackBlockEffectImpl extends ReplacementEffectIm } private final Cost cost; + private final ManaCosts manaCosts; + private final RestrictType restrictType; + public PayCostToAttackBlockEffectImpl(Duration duration, Outcome outcome, RestrictType restrictType) { + super(duration, outcome, false); + this.restrictType = restrictType; + this.cost = null; + this.manaCosts = null; + } + public PayCostToAttackBlockEffectImpl(Duration duration, Outcome outcome, RestrictType restrictType, Cost cost) { super(duration, outcome, false); this.restrictType = restrictType; this.cost = cost; + this.manaCosts = null; + } + + public PayCostToAttackBlockEffectImpl(Duration duration, Outcome outcome, RestrictType restrictType, ManaCosts manaCosts) { + super(duration, outcome, false); + this.restrictType = restrictType; + this.cost = null; + this.manaCosts = manaCosts; } public PayCostToAttackBlockEffectImpl(PayCostToAttackBlockEffectImpl effect) { @@ -64,6 +82,11 @@ public abstract class PayCostToAttackBlockEffectImpl extends ReplacementEffectIm } else { this.cost = null; } + if (effect.manaCosts != null) { + this.manaCosts = effect.manaCosts.copy(); + } else { + this.manaCosts = null; + } this.restrictType = effect.restrictType; } @@ -82,20 +105,31 @@ public abstract class PayCostToAttackBlockEffectImpl extends ReplacementEffectIm @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { + ManaCosts attackBlockManaTax = getManaCostToPay(event, source, game); + if (attackBlockManaTax != null) { + return handleManaCosts(attackBlockManaTax, event, source, game); + } + Cost attackBlockOtherTax = getOtherCostToPay(event, source, game); + if (attackBlockOtherTax != null) { + return handleOtherCosts(attackBlockOtherTax, event, source, game); + } + return false; + } + + private boolean handleManaCosts(ManaCosts attackBlockManaTax, GameEvent event, Ability source, Game game) { Player player = game.getPlayer(event.getPlayerId()); - Cost attackBlockTax = getCostToPay(event, source, game); - if (player != null && attackBlockTax != null) { + if (player != null) { String chooseText; if (event.getType().equals(GameEvent.EventType.DECLARE_ATTACKER)) { - chooseText = "Pay " + attackBlockTax.getText() + " to attack?"; + chooseText = "Pay " + attackBlockManaTax.getText() + " to attack?"; } else { - chooseText = "Pay " + attackBlockTax.getText() + " to block?"; + chooseText = "Pay " + attackBlockManaTax.getText() + " to block?"; } - if (attackBlockTax.canPay(source, source.getSourceId(), player.getId(), game) + attackBlockManaTax.clearPaid(); + if (attackBlockManaTax.canPay(source, source.getSourceId(), player.getId(), game) && player.chooseUse(Outcome.Neutral, chooseText, source, game)) { - if (attackBlockTax instanceof ManaCostImpl) { - ManaCostsImpl manaCosts = new ManaCostsImpl(attackBlockTax.getText()); - if (manaCosts.payOrRollback(source, game, source.getSourceId(), event.getPlayerId())) { + if (attackBlockManaTax instanceof ManaCostsImpl) { + if (attackBlockManaTax.payOrRollback(source, game, source.getSourceId(), event.getPlayerId())) { return false; } } @@ -105,9 +139,30 @@ public abstract class PayCostToAttackBlockEffectImpl extends ReplacementEffectIm return false; } + private boolean handleOtherCosts(Cost attackBlockOtherTax, GameEvent event, Ability source, Game game) { + Player player = game.getPlayer(event.getPlayerId()); + if (player != null) { + attackBlockOtherTax.clearPaid(); + if (attackBlockOtherTax.canPay(source, source.getSourceId(), event.getPlayerId(), game) + && player.chooseUse(Outcome.Neutral, + attackBlockOtherTax.getText() + " to " + (event.getType().equals(EventType.DECLARE_ATTACKER) ? "attack?" : "block?"), source, game)) { + if (attackBlockOtherTax.pay(source, game, source.getSourceId(), event.getPlayerId(), false)) { + return false; + } + } + return true; + } + return false; + } + @Override - public Cost getCostToPay(GameEvent event, Ability source, Game game) { + public Cost getOtherCostToPay(GameEvent event, Ability source, Game game) { return cost; } + @Override + public ManaCosts getManaCostToPay(GameEvent event, Ability source, Game game) { + return manaCosts; + } + } diff --git a/Mage/src/mage/abilities/effects/common/replacement/CantAttackYouUnlessPayManaAllEffect.java b/Mage/src/mage/abilities/effects/common/replacement/CantAttackYouUnlessPayManaAllEffect.java new file mode 100644 index 0000000000..36f7be673a --- /dev/null +++ b/Mage/src/mage/abilities/effects/common/replacement/CantAttackYouUnlessPayManaAllEffect.java @@ -0,0 +1,41 @@ +/* + * 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.replacement; + +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCosts; +import mage.abilities.effects.PayCostToAttackBlockEffectImpl; +import mage.abilities.effects.PayCostToAttackBlockEffectImpl.RestrictType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * + * @author LevelX2 + */ +public class CantAttackYouUnlessPayManaAllEffect extends PayCostToAttackBlockEffectImpl { + + public CantAttackYouUnlessPayManaAllEffect(ManaCosts manaCosts) { + super(Duration.WhileOnBattlefield, Outcome.Detriment, RestrictType.ATTACK, manaCosts); + staticText = "Creatures can't attack you unless their controller pays " + manaCosts.getText() + " for each creature he or she controls that's attacking you"; + } + + CantAttackYouUnlessPayManaAllEffect(CantAttackYouUnlessPayManaAllEffect effect) { + super(effect); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.getControllerId().equals(event.getTargetId()); + } + + @Override + public CantAttackYouUnlessPayManaAllEffect copy() { + return new CantAttackYouUnlessPayManaAllEffect(this); + } +} diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index e5d281763c..80caa16dba 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -27,6 +27,15 @@ */ package mage.game.combat; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.RestrictionEffect; @@ -37,6 +46,7 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreatureForCombatBlock; +import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterPlaneswalkerPermanent; import mage.game.Game; import mage.game.events.GameEvent; @@ -48,10 +58,6 @@ import mage.util.CardUtil; import mage.util.Copyable; import mage.util.trace.TraceUtil; -import java.io.Serializable; -import java.util.*; -import mage.filter.common.FilterCreaturePermanent; - /** * @author BetaSteward_at_googlemail.com */ @@ -92,8 +98,8 @@ public class Combat implements Serializable, Copyable { this.useToughnessForDamage = combat.useToughnessForDamage; for (Map.Entry> group : combat.numberCreaturesDefenderAttackedBy.entrySet()) { this.numberCreaturesDefenderAttackedBy.put(group.getKey(), group.getValue()); - } - + } + for (Map.Entry> group : combat.creatureMustBlockAttackers.entrySet()) { this.creatureMustBlockAttackers.put(group.getKey(), group.getValue()); } @@ -133,7 +139,7 @@ public class Combat implements Serializable, Copyable { public boolean useToughnessForDamage(Permanent permanent, Game game) { if (useToughnessForDamage) { - for(FilterCreaturePermanent filter: useToughnessForDamageFilters) { + for (FilterCreaturePermanent filter : useToughnessForDamageFilters) { if (filter.match(permanent, game)) { return true; } @@ -231,7 +237,7 @@ public class Combat implements Serializable, Copyable { } game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId)); if (!game.isSimulation()) { - game.informPlayers(new StringBuilder(player.getLogName()).append(" attacks with ").append(groups.size()).append(groups.size() == 1 ? " creature":" creatures").toString()); + game.informPlayers(new StringBuilder(player.getLogName()).append(" attacks with ").append(groups.size()).append(groups.size() == 1 ? " creature" : " creatures").toString()); } } @@ -256,20 +262,38 @@ public class Combat implements Serializable, Copyable { } } if (mustAttack) { - creaturesForcedToAttack.put(creature.getId(), defendersForcedToAttack); - if (defendersForcedToAttack.isEmpty()) { - if (defenders.size() == 1) { - player.declareAttacker(creature.getId(), defenders.iterator().next(), game, false); - } else { - TargetDefender target = new TargetDefender(defenders, creature.getId()); - target.setRequired(true); - if (player.chooseTarget(Outcome.Damage, target, null, game)) { - player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false); - } + // check which defenders the forced to attck creature can attack without paying a cost + HashSet defendersCostlessAttackable = new HashSet<>(); + defendersCostlessAttackable.addAll(defenders); + for (UUID defenderId : defenders) { + if (game.getContinuousEffects().checkIfThereArePayCostToAttackBlockEffects( + GameEvent.getEvent(GameEvent.EventType.DECLARE_ATTACKER, + defenderId, creature.getId(), creature.getControllerId()), game)) { + defendersCostlessAttackable.remove(defenderId); + defendersForcedToAttack.remove(defenderId); } - } else { - player.declareAttacker(creature.getId(), defendersForcedToAttack.iterator().next(), game, false); } + // force attack only if a defender can be attacked without paying a cost + if (!defendersCostlessAttackable.isEmpty()) { + // No need to attack a special defender + if (defendersForcedToAttack.isEmpty()) { + creaturesForcedToAttack.put(creature.getId(), defendersForcedToAttack); + if (defendersForcedToAttack.isEmpty()) { + if (defendersCostlessAttackable.size() == 1) { + player.declareAttacker(creature.getId(), defenders.iterator().next(), game, false); + } + } else { + TargetDefender target = new TargetDefender(defendersCostlessAttackable, creature.getId()); + target.setRequired(true); + if (player.chooseTarget(Outcome.Damage, target, null, game)) { + player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false); + } + } + } else { + player.declareAttacker(creature.getId(), defendersForcedToAttack.iterator().next(), game, false); + } + } + } } @@ -457,17 +481,18 @@ public class Combat implements Serializable, Copyable { } /** - * Retrieves all requirements that apply and creates a Map with blockers and attackers - * it contains only records if attackers can be retrieved - * // Map> - * + * Retrieves all requirements that apply and creates a Map with blockers and + * attackers it contains only records if attackers can be retrieved // + * Map> + * * @param attackingPlayer - attacker - * @param game + * @param game */ private void retrieveMustBlockAttackerRequirements(Player attackingPlayer, Game game) { if (!game.getContinuousEffects().existRequirementEffects()) { return; - } + } for (Permanent possibleBlocker : game.getBattlefield().getActivePermanents(filterBlockers, attackingPlayer.getId(), game)) { for (Map.Entry> requirementEntry : game.getContinuousEffects().getApplicableRequirementEffects(possibleBlocker, game).entrySet()) { if (requirementEntry.getKey().mustBlock(game)) { @@ -582,7 +607,7 @@ public class Combat implements Serializable, Copyable { // check if the attacker is already blocked by a max of blockers, so blocker can't block it also if (attackingCreature.getMaxBlockedBy() != 0) { // 0 = no restriction about the number of possible blockers int alreadyBlockingCreatures = 0; - for(CombatGroup group :getGroups()) { + for (CombatGroup group : getGroups()) { if (group.getAttackers().contains(attackingCreatureId)) { alreadyBlockingCreatures = group.getBlockers().size(); break; @@ -874,7 +899,7 @@ public class Combat implements Serializable, Copyable { Permanent attacker = game.getPermanent(attackerId); attacker.setAttacking(true); groups.add(newGroup); - return true; + return true; } public boolean canDefenderBeAttacked(UUID attackerId, UUID defenderId, Game game) { @@ -999,7 +1024,6 @@ public class Combat implements Serializable, Copyable { // } // return total; // } - public boolean attacksAlone() { return (groups.size() == 1 && groups.get(0).getAttackers().size() == 1); } @@ -1106,10 +1130,9 @@ public class Combat implements Serializable, Copyable { } } - public void removeBlockerGromGroup(UUID blockerId, CombatGroup groupToUnblock, Game game) { Permanent creature = game.getPermanent(blockerId); - if (creature != null) { + if (creature != null) { for (CombatGroup group : groups) { if (group.equals(groupToUnblock) && group.blockers.contains(blockerId)) { group.blockers.remove(blockerId); @@ -1124,9 +1147,9 @@ public class Combat implements Serializable, Copyable { } } } - } + } } - + public void removeBlocker(UUID blockerId, Game game) { for (CombatGroup group : groups) { if (group.blockers.contains(blockerId)) {