From c5718e3f19b8b70c8bb01c6d530c803ea77538f1 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 25 Aug 2015 15:09:44 +0200 Subject: [PATCH] * Phasing - Fixed that idirect phasing (attachments of permanents with phasing) were not phased out with the permanent they attached to. --- .../mage/sets/weatherlight/TolarianDrake.java | 7 +- .../cards/abilities/keywords/PhasingTest.java | 74 ++++++- Mage/src/mage/game/permanent/Battlefield.java | 197 +++++++++--------- 3 files changed, 167 insertions(+), 111 deletions(-) diff --git a/Mage.Sets/src/mage/sets/weatherlight/TolarianDrake.java b/Mage.Sets/src/mage/sets/weatherlight/TolarianDrake.java index d235b884073..36fd5e37428 100644 --- a/Mage.Sets/src/mage/sets/weatherlight/TolarianDrake.java +++ b/Mage.Sets/src/mage/sets/weatherlight/TolarianDrake.java @@ -28,12 +28,12 @@ package mage.sets.weatherlight; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Rarity; import mage.MageInt; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.PhasingAbility; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; /** * @@ -49,7 +49,10 @@ public class TolarianDrake extends CardImpl { this.power = new MageInt(2); this.toughness = new MageInt(4); + // Flying this.addAbility(FlyingAbility.getInstance()); + + // Phasing this.addAbility(PhasingAbility.getInstance()); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PhasingTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PhasingTest.java index 53027c46f22..77bd021e04b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PhasingTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PhasingTest.java @@ -36,27 +36,25 @@ import org.mage.test.serverside.base.CardTestPlayerBase; * * @author LevelX2 */ - public class PhasingTest extends CardTestPlayerBase { - - /** - * Test that abilities of phased out cards do not trigger or apply their effects + * Test that abilities of phased out cards do not trigger or apply their + * effects */ @Test public void TestAbilitiesOfPhasedOutAreNotApplied() { addCard(Zone.BATTLEFIELD, playerA, "Island", 3); - // At the beginning of each player's upkeep, that player chooses artifact, creature, land, or non-Aura enchantment. - // All nontoken permanents of that type phase out. + // At the beginning of each player's upkeep, that player chooses artifact, creature, land, or non-Aura enchantment. + // All nontoken permanents of that type phase out. addCard(Zone.HAND, playerA, "Teferi's Realm", 1); - + addCard(Zone.BATTLEFIELD, playerB, "Crusade", 1); addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Teferi's Realm"); - - setChoice(playerB, "Non-Aura enchantment"); + + setChoice(playerB, "Non-Aura enchantment"); setStopAt(2, PhaseStep.PRECOMBAT_MAIN); execute(); @@ -64,4 +62,60 @@ public class PhasingTest extends CardTestPlayerBase { assertPermanentCount(playerB, "Silvercoat Lion", 1); assertPowerToughness(playerB, "Silvercoat Lion", 2, 2); } -} \ No newline at end of file + + /** + * I had Fireshrieker equipped to Taniwha. When Taniwha phased out, the + * Fireshrieker remained visible on the battlefield, appearing to be + * attached to a Coldsteel Heart. The Fireshrieker should have been phased + * out indirectly. + * + * 502.15i When a permanent phases out, any local enchantments or Equipment + * attached to that permanent phase out at the same time. This alternate way + * of phasing out is known as phasing out "indirectly." An enchantment or + * Equipment that phased out indirectly won't phase in by itself, but + * instead phases in along with the card it's attached to. + */ + @Test + public void TestIndirectPhasing() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + // Flying + // Phasing (This phases in or out before you untap during each of your untap steps. While it's phased out, it's treated as though it doesn't exist.) + // All nontoken permanents of that type phase out. + addCard(Zone.HAND, playerA, "Tolarian Drake", 1); + // Enchant creature + // {R}: Enchanted creature gets +1/+0 until end of turn. + addCard(Zone.HAND, playerA, "Firebreathing", 1); // {R} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tolarian Drake"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Firebreathing", "Tolarian Drake"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Tolarian Drake", 0); + assertPermanentCount(playerA, "Firebreathing", 0); + } + + @Test + public void TestIndirectPhasingAgainPhasedIn() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + // Flying + // Phasing (This phases in or out before you untap during each of your untap steps. While it's phased out, it's treated as though it doesn't exist.) + // All nontoken permanents of that type phase out. + addCard(Zone.HAND, playerA, "Tolarian Drake", 1); + // Enchant creature + // {R}: Enchanted creature gets +1/+0 until end of turn. + addCard(Zone.HAND, playerA, "Firebreathing", 1); // {R} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tolarian Drake"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Firebreathing", "Tolarian Drake"); + + setStopAt(5, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Tolarian Drake", 1); + assertPermanentCount(playerA, "Firebreathing", 1); + } +} diff --git a/Mage/src/mage/game/permanent/Battlefield.java b/Mage/src/mage/game/permanent/Battlefield.java index e44a9c59f74..115e78a3c38 100644 --- a/Mage/src/mage/game/permanent/Battlefield.java +++ b/Mage/src/mage/game/permanent/Battlefield.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.game.permanent; import java.io.Serializable; @@ -51,10 +50,11 @@ public class Battlefield implements Serializable { private final Map field = new LinkedHashMap<>(); - public Battlefield () {} + public Battlefield() { + } public Battlefield(final Battlefield battlefield) { - for (Entry entry: battlefield.field.entrySet()) { + for (Entry entry : battlefield.field.entrySet()) { field.put(entry.getKey(), entry.getValue().copy()); } } @@ -64,7 +64,7 @@ public class Battlefield implements Serializable { } public void reset(Game game) { - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { perm.reset(game); } } @@ -74,11 +74,13 @@ public class Battlefield implements Serializable { } /** - * Returns a count of all {@link Permanent} that match the filter and are controlled by controllerId. - * - * Some filter predicates do not work here (e.g. AnotherPredicate() because filter.match() is called - * without controllerId. To use this predicates you can use count() instead of countAll() - * + * Returns a count of all {@link Permanent} that match the filter and are + * controlled by controllerId. + * + * Some filter predicates do not work here (e.g. AnotherPredicate() because + * filter.match() is called without controllerId. To use this predicates you + * can use count() instead of countAll() + * * @param filter * @param controllerId * @param game @@ -86,7 +88,7 @@ public class Battlefield implements Serializable { */ public int countAll(FilterPermanent filter, UUID controllerId, Game game) { int count = 0; - for (Permanent permanent: field.values()) { + for (Permanent permanent : field.values()) { if (permanent.getControllerId().equals(controllerId) && filter.match(permanent, game) && permanent.isPhasedIn()) { count++; } @@ -95,29 +97,28 @@ public class Battlefield implements Serializable { } /** - * Returns a count of all {@link Permanent} that are within the range of influence of the specified player id - * and that match the supplied filter. + * Returns a count of all {@link Permanent} that are within the range of + * influence of the specified player id and that match the supplied filter. * * @param filter - * @param sourceId - sourceId of the MageObject the calling effect/ability belongs to + * @param sourceId - sourceId of the MageObject the calling effect/ability + * belongs to * @param sourcePlayerId * @param game * @return count */ - public int count(FilterPermanent filter, UUID sourceId, UUID sourcePlayerId, Game game) { int count = 0; if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) { - for (Permanent permanent: field.values()) { - if (filter.match(permanent, sourceId, sourcePlayerId, game) && permanent.isPhasedIn()) { + for (Permanent permanent : field.values()) { + if (filter.match(permanent, sourceId, sourcePlayerId, game) && permanent.isPhasedIn()) { count++; } } - } - else { + } else { Set range = game.getPlayer(sourcePlayerId).getInRange(); - for (Permanent permanent: field.values()) { - if (range.contains(permanent.getControllerId()) && filter.match(permanent, sourceId, sourcePlayerId, game) && permanent.isPhasedIn()) { + for (Permanent permanent : field.values()) { + if (range.contains(permanent.getControllerId()) && filter.match(permanent, sourceId, sourcePlayerId, game) && permanent.isPhasedIn()) { count++; } } @@ -127,8 +128,7 @@ public class Battlefield implements Serializable { /** * Returns true if the battlefield contains at least 1 {@link Permanent} - * that matches the filter. - * This method ignores the range of influence. + * that matches the filter. This method ignores the range of influence. * * @param filter * @param num @@ -137,7 +137,7 @@ public class Battlefield implements Serializable { */ public boolean contains(FilterPermanent filter, int num, Game game) { int count = 0; - for (Permanent permanent: field.values()) { + for (Permanent permanent : field.values()) { if (filter.match(permanent, game) && permanent.isPhasedIn()) { count++; if (num == count) { @@ -150,8 +150,8 @@ public class Battlefield implements Serializable { /** * Returns true if the battlefield contains num or more {@link Permanent} - * that matches the filter and is controlled by controllerId. - * This method ignores the range of influence. + * that matches the filter and is controlled by controllerId. This method + * ignores the range of influence. * * @param filter * @param controllerId @@ -161,7 +161,7 @@ public class Battlefield implements Serializable { */ public boolean contains(FilterPermanent filter, UUID controllerId, int num, Game game) { int count = 0; - for (Permanent permanent: field.values()) { + for (Permanent permanent : field.values()) { if (permanent.getControllerId().equals(controllerId) && filter.match(permanent, game) && permanent.isPhasedIn()) { count++; if (num == count) { @@ -174,8 +174,8 @@ public class Battlefield implements Serializable { /** * Returns true if the battlefield contains num or more {@link Permanent} - * that is within the range of influence of the specified player id - * and that matches the supplied filter. + * that is within the range of influence of the specified player id and that + * matches the supplied filter. * * @param filter * @param sourcePlayerId @@ -186,7 +186,7 @@ public class Battlefield implements Serializable { public boolean contains(FilterPermanent filter, UUID sourcePlayerId, Game game, int num) { int count = 0; if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) { - for (Permanent permanent: field.values()) { + for (Permanent permanent : field.values()) { if (filter.match(permanent, null, sourcePlayerId, game) && permanent.isPhasedIn()) { count++; if (num == count) { @@ -194,10 +194,9 @@ public class Battlefield implements Serializable { } } } - } - else { + } else { Set range = game.getPlayer(sourcePlayerId).getInRange(); - for (Permanent permanent: field.values()) { + for (Permanent permanent : field.values()) { if (range.contains(permanent.getControllerId()) && filter.match(permanent, null, sourcePlayerId, game) && permanent.isPhasedIn()) { count++; if (num == count) { @@ -226,13 +225,13 @@ public class Battlefield implements Serializable { } public void beginningOfTurn(Game game) { - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { perm.beginningOfTurn(game); } } public void endOfTurn(UUID controllerId, Game game) { - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { perm.endOfTurn(game); } } @@ -247,7 +246,7 @@ public class Battlefield implements Serializable { public List getAllActivePermanents() { List active = new ArrayList<>(); - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.isPhasedIn()) { active.add(perm); } @@ -256,16 +255,16 @@ public class Battlefield implements Serializable { } /** - * Returns all {@link Permanent} on the battlefield that are controlled by the specified - * player id. The method ignores the range of influence. - * + * Returns all {@link Permanent} on the battlefield that are controlled by + * the specified player id. The method ignores the range of influence. + * * @param controllerId * @return a list of {@link Permanent} * @see Permanent */ public List getAllActivePermanents(UUID controllerId) { List active = new ArrayList<>(); - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.isPhasedIn() && perm.getControllerId().equals(controllerId)) { active.add(perm); } @@ -274,16 +273,16 @@ public class Battlefield implements Serializable { } /** - * Returns all {@link Permanent} on the battlefield that match the specified {@link CardType}. - * This method ignores the range of influence. - * + * Returns all {@link Permanent} on the battlefield that match the specified + * {@link CardType}. This method ignores the range of influence. + * * @param type * @return a list of {@link Permanent} * @see Permanent */ public List getAllActivePermanents(CardType type) { List active = new ArrayList<>(); - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.isPhasedIn() && perm.getCardType().contains(type)) { active.add(perm); } @@ -292,9 +291,9 @@ public class Battlefield implements Serializable { } /** - * Returns all {@link Permanent} on the battlefield that match the supplied filter. - * This method ignores the range of influence. - * + * Returns all {@link Permanent} on the battlefield that match the supplied + * filter. This method ignores the range of influence. + * * * @param filter * @param game @@ -303,7 +302,7 @@ public class Battlefield implements Serializable { */ public List getAllActivePermanents(FilterPermanent filter, Game game) { List active = new ArrayList<>(); - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.isPhasedIn() && filter.match(perm, game)) { active.add(perm); } @@ -312,8 +311,8 @@ public class Battlefield implements Serializable { } /** - * Returns all {@link Permanent} that match the filter and are controlled by controllerId. - * This method ignores the range of influence. + * Returns all {@link Permanent} that match the filter and are controlled by + * controllerId. This method ignores the range of influence. * * @param filter * @param controllerId @@ -323,7 +322,7 @@ public class Battlefield implements Serializable { */ public List getAllActivePermanents(FilterPermanent filter, UUID controllerId, Game game) { List active = new ArrayList<>(); - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.isPhasedIn() && perm.getControllerId().equals(controllerId) && filter.match(perm, game)) { active.add(perm); } @@ -332,8 +331,8 @@ public class Battlefield implements Serializable { } /** - * Returns all {@link Permanent} that are within the range of influence of the specified player id - * and that match the supplied filter. + * Returns all {@link Permanent} that are within the range of influence of + * the specified player id and that match the supplied filter. * * @param filter * @param sourcePlayerId @@ -346,9 +345,9 @@ public class Battlefield implements Serializable { } /** - * Returns all {@link Permanent} that are within the range of influence of the specified player id - * and that match the supplied filter. - * + * Returns all {@link Permanent} that are within the range of influence of + * the specified player id and that match the supplied filter. + * * @param filter * @param sourcePlayerId * @param sourceId @@ -359,15 +358,14 @@ public class Battlefield implements Serializable { public List getActivePermanents(FilterPermanent filter, UUID sourcePlayerId, UUID sourceId, Game game) { List active = new ArrayList<>(); if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) { - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.isPhasedIn() && filter.match(perm, sourceId, sourcePlayerId, game)) { active.add(perm); } } - } - else { + } else { Set range = game.getPlayer(sourcePlayerId).getInRange(); - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.isPhasedIn() && range.contains(perm.getControllerId()) && filter.match(perm, sourceId, sourcePlayerId, game)) { active.add(perm); } @@ -377,8 +375,9 @@ public class Battlefield implements Serializable { } /** - * Returns all {@link Permanent} that are within the range of influence of the specified player id. - * + * Returns all {@link Permanent} that are within the range of influence of + * the specified player id. + * * @param sourcePlayerId * @param game * @return a list of {@link Permanent} @@ -387,11 +386,10 @@ public class Battlefield implements Serializable { public List getActivePermanents(UUID sourcePlayerId, Game game) { if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) { return getAllActivePermanents(); - } - else { + } else { List active = new ArrayList<>(); Set range = game.getPlayer(sourcePlayerId).getInRange(); - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.isPhasedIn() && range.contains(perm.getControllerId())) { active.add(perm); } @@ -402,7 +400,7 @@ public class Battlefield implements Serializable { public List getPhasedIn(UUID controllerId) { List phasedIn = new ArrayList<>(); - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.getAbilities().containsKey(PhasingAbility.getInstance().getId()) && perm.isPhasedIn() && perm.getControllerId().equals(controllerId)) { phasedIn.add(perm); } @@ -412,7 +410,7 @@ public class Battlefield implements Serializable { public List getPhasedOut(UUID controllerId) { List phasedOut = new ArrayList<>(); - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (!perm.isPhasedIn() && perm.getControllerId().equals(controllerId)) { phasedOut.add(perm); } @@ -421,23 +419,24 @@ public class Battlefield implements Serializable { } public void resetPermanentsControl() { - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.isPhasedIn()) { perm.resetControl(); } } } - + /** - * since control could change several times during applyEvents we only want to fire - * control changed events after all control change effects have been applied - * - * @param game + * since control could change several times during applyEvents we only want + * to fire control changed events after all control change effects have been + * applied + * + * @param game * @return */ public boolean fireControlChangeEvents(Game game) { boolean controlChanged = false; - for (Permanent perm: field.values()) { + for (Permanent perm : field.values()) { if (perm.isPhasedIn()) { controlChanged |= perm.checkControlChanged(game); }