From fb965ebdd6e4b74fa40f06b5b618b7250d7763b6 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 29 Aug 2015 18:52:46 +0200 Subject: [PATCH] * Opalescence - Fixed that the dependent effect (613.7) Opalescence was applied before the effect of Enchanted Evening if Opalescence was cast earlier. --- .../mage/sets/futuresight/MagusOfTheMoon.java | 102 +++++----- .../mage/sets/futuresight/YixlidJailer.java | 80 ++++---- .../sets/scarsofmirrodin/NecroticOoze.java | 111 +++++------ .../sets/shadowmoor/EnchantedEvening.java | 56 +++--- .../mage/sets/urzasdestiny/Opalescence.java | 26 ++- .../continuous/DependentEffectsTest.java | 176 ++++++++++++++++++ .../abilities/effects/ContinuousEffect.java | 74 +++++--- .../effects/ContinuousEffectImpl.java | 6 + .../abilities/effects/ContinuousEffects.java | 123 ++++++++---- 9 files changed, 515 insertions(+), 239 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/continuous/DependentEffectsTest.java diff --git a/Mage.Sets/src/mage/sets/futuresight/MagusOfTheMoon.java b/Mage.Sets/src/mage/sets/futuresight/MagusOfTheMoon.java index d6ee678b76..f8add1d6b6 100644 --- a/Mage.Sets/src/mage/sets/futuresight/MagusOfTheMoon.java +++ b/Mage.Sets/src/mage/sets/futuresight/MagusOfTheMoon.java @@ -28,6 +28,12 @@ package mage.sets.futuresight; import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Layer; @@ -35,12 +41,6 @@ import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.SubLayer; import mage.constants.Zone; -import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.mana.RedManaAbility; -import mage.cards.CardImpl; import mage.filter.common.FilterLandPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.SupertypePredicate; @@ -53,6 +53,12 @@ import mage.game.permanent.Permanent; */ public class MagusOfTheMoon extends CardImpl { + private static final FilterLandPermanent filter = new FilterLandPermanent(); + + static { + filter.add(Predicates.not(new SupertypePredicate("Basic"))); + } + public MagusOfTheMoon(UUID ownerId) { super(ownerId, 101, "Magus of the Moon", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{2}{R}"); this.expansionSetCode = "FUT"; @@ -74,53 +80,49 @@ public class MagusOfTheMoon extends CardImpl { public MagusOfTheMoon copy() { return new MagusOfTheMoon(this); } -} -class MagusOfTheMoonEffect extends ContinuousEffectImpl { + class MagusOfTheMoonEffect extends ContinuousEffectImpl { - private static final FilterLandPermanent filter = new FilterLandPermanent(); - static { - filter.add(Predicates.not(new SupertypePredicate("Basic"))); - } - - MagusOfTheMoonEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - this.staticText = "Nonbasic lands are Mountains"; - } - - MagusOfTheMoonEffect(final MagusOfTheMoonEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return false; - } - - @Override - public MagusOfTheMoonEffect copy() { - return new MagusOfTheMoonEffect(this); - } - - @Override - public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { - for (Permanent land: game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) { - switch (layer) { - case AbilityAddingRemovingEffects_6: - land.removeAllAbilities(source.getSourceId(), game); - land.addAbility(new RedManaAbility(), source.getSourceId(), game); - break; - case TypeChangingEffects_4: - land.getSubtype().clear(); - land.getSubtype().add("Mountain"); - break; - } + MagusOfTheMoonEffect() { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + this.staticText = "Nonbasic lands are Mountains"; + } + + MagusOfTheMoonEffect(final MagusOfTheMoonEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public MagusOfTheMoonEffect copy() { + return new MagusOfTheMoonEffect(this); + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + for (Permanent land : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) { + switch (layer) { + case AbilityAddingRemovingEffects_6: + land.removeAllAbilities(source.getSourceId(), game); + land.addAbility(new RedManaAbility(), source.getSourceId(), game); + break; + case TypeChangingEffects_4: + land.getSubtype().clear(); + land.getSubtype().add("Mountain"); + break; + } + } + return true; + } + + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.AbilityAddingRemovingEffects_6 || layer == Layer.TypeChangingEffects_4; } - return true; } - @Override - public boolean hasLayer(Layer layer) { - return layer == Layer.AbilityAddingRemovingEffects_6 || layer == Layer.TypeChangingEffects_4; - } } diff --git a/Mage.Sets/src/mage/sets/futuresight/YixlidJailer.java b/Mage.Sets/src/mage/sets/futuresight/YixlidJailer.java index 4e35a0f736..dd701feb7e 100644 --- a/Mage.Sets/src/mage/sets/futuresight/YixlidJailer.java +++ b/Mage.Sets/src/mage/sets/futuresight/YixlidJailer.java @@ -72,59 +72,59 @@ public class YixlidJailer extends CardImpl { public YixlidJailer copy() { return new YixlidJailer(this); } -} -class YixlidJailerEffect extends ContinuousEffectImpl { + class YixlidJailerEffect extends ContinuousEffectImpl { - YixlidJailerEffect() { - super(Duration.WhileOnBattlefield, Outcome.LoseAbility); - staticText = "Cards in graveyards lose all abilities."; - } + YixlidJailerEffect() { + super(Duration.WhileOnBattlefield, Outcome.LoseAbility); + staticText = "Cards in graveyards lose all abilities."; + } - YixlidJailerEffect(final YixlidJailerEffect effect) { - super(effect); - } + YixlidJailerEffect(final YixlidJailerEffect effect) { + super(effect); + } - @Override - public YixlidJailerEffect copy() { - return new YixlidJailerEffect(this); - } + @Override + public YixlidJailerEffect copy() { + return new YixlidJailerEffect(this); + } - @Override - public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { - if (layer == Layer.AbilityAddingRemovingEffects_6) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - for (UUID playerId : controller.getInRange()) { - Player player = game.getPlayer(playerId); - if (player != null) { - for (Card card : player.getGraveyard().getCards(game)) { - if (card != null) { - card.getAbilities(game).clear(); // Will the abilities ever come back???? - // TODO: Fix that (LevelX2) - // game.getContinuousEffects().removeGainedEffectsForSource(card.getId()); - // game.getState().resetTriggersForSourceId(card.getId()); - Abilities abilities = game.getState().getAllOtherAbilities(card.getId()); - if (abilities != null) { - abilities.clear(); + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + if (layer == Layer.AbilityAddingRemovingEffects_6) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + for (UUID playerId : controller.getInRange()) { + Player player = game.getPlayer(playerId); + if (player != null) { + for (Card card : player.getGraveyard().getCards(game)) { + if (card != null) { + card.getAbilities(game).clear(); // Will the abilities ever come back???? + // TODO: Fix that (LevelX2) + // game.getContinuousEffects().removeGainedEffectsForSource(card.getId()); + // game.getState().resetTriggersForSourceId(card.getId()); + Abilities abilities = game.getState().getAllOtherAbilities(card.getId()); + if (abilities != null) { + abilities.clear(); + } } } } } + return true; } - return true; } + return false; } - return false; - } - @Override - public boolean apply(Game game, Ability source) { - return false; - } + @Override + public boolean apply(Game game, Ability source) { + return false; + } - @Override - public boolean hasLayer(Layer layer) { - return layer == Layer.AbilityAddingRemovingEffects_6; + @Override + public boolean hasLayer(Layer layer) { + return layer == Layer.AbilityAddingRemovingEffects_6; + } } } diff --git a/Mage.Sets/src/mage/sets/scarsofmirrodin/NecroticOoze.java b/Mage.Sets/src/mage/sets/scarsofmirrodin/NecroticOoze.java index 65d7927c47..e49932fcb0 100644 --- a/Mage.Sets/src/mage/sets/scarsofmirrodin/NecroticOoze.java +++ b/Mage.Sets/src/mage/sets/scarsofmirrodin/NecroticOoze.java @@ -1,16 +1,16 @@ /* * Copyright 2011 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 @@ -20,14 +20,25 @@ * 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.sets.scarsofmirrodin; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.cards.Card; +import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Layer; @@ -35,16 +46,10 @@ import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.SubLayer; import mage.constants.Zone; -import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.ActivatedAbility; -import mage.abilities.StaticAbility; -import mage.abilities.effects.ContinuousEffectImpl; -import mage.cards.Card; -import mage.cards.CardImpl; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.sets.futuresight.YixlidJailer; /** * @@ -60,7 +65,8 @@ public class NecroticOoze extends CardImpl { this.power = new MageInt(4); this.toughness = new MageInt(3); - this.addAbility(new NecroticOozeAbility()); + // As long as Necrotic Ooze is on the battlefield, it has all activated abilities of all creature cards in all graveyards + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new NecroticOozeEffect())); } public NecroticOoze(final NecroticOoze card) { @@ -72,48 +78,27 @@ public class NecroticOoze extends CardImpl { return new NecroticOoze(this); } -} + class NecroticOozeEffect extends ContinuousEffectImpl { -class NecroticOozeAbility extends StaticAbility { + public NecroticOozeEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + staticText = "As long as {this} is on the battlefield, it has all activated abilities of all creature cards in all graveyards"; + } - public NecroticOozeAbility() { - super(Zone.BATTLEFIELD, new NecroticOozeEffect()); - } + public NecroticOozeEffect(final NecroticOozeEffect effect) { + super(effect); + } - public NecroticOozeAbility(final NecroticOozeAbility ability) { - super(ability); - } - - @Override - public NecroticOozeAbility copy() { - return new NecroticOozeAbility(this); - } - -} - -class NecroticOozeEffect extends ContinuousEffectImpl { - - public NecroticOozeEffect() { - super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - staticText = "As long as {this} is on the battlefield, it has all activated abilities of all creature cards in all graveyards"; - } - - public NecroticOozeEffect(final NecroticOozeEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { + @Override + public boolean apply(Game game, Ability source) { Permanent perm = game.getPermanent(source.getSourceId()); if (perm != null) { - for (UUID playerId: controller.getInRange()) { + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); if (player != null) { - for (Card card: player.getGraveyard().getCards(game)) { + for (Card card : player.getGraveyard().getCards(game)) { if (card.getCardType().contains(CardType.CREATURE)) { - for (Ability ability: card.getAbilities()) { + for (Ability ability : card.getAbilities()) { if (ability instanceof ActivatedAbility) { perm.addAbility(ability, game); } @@ -122,15 +107,31 @@ class NecroticOozeEffect extends ContinuousEffectImpl { } } } + return true; } - return true; + return false; + } + + @Override + public NecroticOozeEffect copy() { + return new NecroticOozeEffect(this); + } + + @Override + public Set isDependentTo(List allEffectsInLayer) { + // the dependent classes needs to be an enclosed class for dependent check of continuous effects + Set dependentTo = null; + for (ContinuousEffect effect : allEffectsInLayer) { + // http://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/285211-yixlid-jailer-vs-necrotic-ooze + if (YixlidJailer.class.equals(effect.getClass().getEnclosingClass())) { + if (dependentTo == null) { + dependentTo = new HashSet<>(); + } + dependentTo.add(effect.getId()); + } + } + return dependentTo; } - return false; } - @Override - public NecroticOozeEffect copy() { - return new NecroticOozeEffect(this); - } - -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/shadowmoor/EnchantedEvening.java b/Mage.Sets/src/mage/sets/shadowmoor/EnchantedEvening.java index f90471ad70..9590020f57 100644 --- a/Mage.Sets/src/mage/sets/shadowmoor/EnchantedEvening.java +++ b/Mage.Sets/src/mage/sets/shadowmoor/EnchantedEvening.java @@ -54,7 +54,6 @@ public class EnchantedEvening extends CardImpl { super(ownerId, 140, "Enchanted Evening", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{3}{W/U}{W/U}"); this.expansionSetCode = "SHM"; - // All permanents are enchantments in addition to their other types. Effect effect = new EnchangedEveningEffect(CardType.ENCHANTMENT, Duration.WhileOnBattlefield, new FilterPermanent()); effect.setText("All permanents are enchantments in addition to their other types"); @@ -70,37 +69,38 @@ public class EnchantedEvening extends CardImpl { public EnchantedEvening copy() { return new EnchantedEvening(this); } -} -class EnchangedEveningEffect extends ContinuousEffectImpl { + // need to be enclosed class for dependent check of continuous effects + class EnchangedEveningEffect extends ContinuousEffectImpl { - private final CardType addedCardType; - private final FilterPermanent filter; + private final CardType addedCardType; + private final FilterPermanent filter; - public EnchangedEveningEffect(CardType addedCardType, Duration duration, FilterPermanent filter) { - super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); - this.addedCardType = addedCardType; - this.filter = filter; - } - - public EnchangedEveningEffect(final EnchangedEveningEffect effect) { - super(effect); - this.addedCardType = effect.addedCardType; - this.filter = effect.filter; - } - - @Override - public boolean apply(Game game, Ability source) { - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, game)) { - if (permanent != null && !permanent.getCardType().contains(addedCardType)) { - permanent.getCardType().add(addedCardType); - } + public EnchangedEveningEffect(CardType addedCardType, Duration duration, FilterPermanent filter) { + super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); + this.addedCardType = addedCardType; + this.filter = filter; } - return true; - } - @Override - public EnchangedEveningEffect copy() { - return new EnchangedEveningEffect(this); + public EnchangedEveningEffect(final EnchangedEveningEffect effect) { + super(effect); + this.addedCardType = effect.addedCardType; + this.filter = effect.filter; + } + + @Override + public boolean apply(Game game, Ability source) { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, game)) { + if (permanent != null && !permanent.getCardType().contains(addedCardType)) { + permanent.getCardType().add(addedCardType); + } + } + return true; + } + + @Override + public EnchangedEveningEffect copy() { + return new EnchangedEveningEffect(this); + } } } diff --git a/Mage.Sets/src/mage/sets/urzasdestiny/Opalescence.java b/Mage.Sets/src/mage/sets/urzasdestiny/Opalescence.java index 977547a474..cd11bb8344 100644 --- a/Mage.Sets/src/mage/sets/urzasdestiny/Opalescence.java +++ b/Mage.Sets/src/mage/sets/urzasdestiny/Opalescence.java @@ -27,9 +27,13 @@ */ package mage.sets.urzasdestiny; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.CardImpl; import mage.constants.CardType; @@ -47,6 +51,7 @@ import mage.filter.predicate.mageobject.SubtypePredicate; import mage.filter.predicate.permanent.AnotherPredicate; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.sets.shadowmoor.EnchantedEvening; /** * @@ -58,7 +63,6 @@ public class Opalescence extends CardImpl { super(ownerId, 13, "Opalescence", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}{W}"); this.expansionSetCode = "UDS"; - // Each other non-Aura enchantment is a creature with power and toughness each equal to its converted mana cost. It's still an enchantment. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new OpalescenceEffect())); @@ -72,19 +76,21 @@ public class Opalescence extends CardImpl { public Opalescence copy() { return new Opalescence(this); } + } class OpalescenceEffect extends ContinuousEffectImpl { private static final FilterEnchantmentPermanent filter = new FilterEnchantmentPermanent("Each other non-Aura enchantment"); + static { filter.add(Predicates.not(new SubtypePredicate("Aura"))); filter.add(new AnotherPredicate()); } - + public OpalescenceEffect() { super(Duration.WhileOnBattlefield, Outcome.BecomeCreature); - staticText = "Each other non-Aura enchantment is a creature with power and toughness each equal to its converted mana cost"; + staticText = "Each other non-Aura enchantment is a creature in addition to its other types and has base power and base toughness each equal to its converted mana cost"; } public OpalescenceEffect(final OpalescenceEffect effect) { @@ -125,10 +131,22 @@ class OpalescenceEffect extends ContinuousEffectImpl { return false; } - @Override public boolean hasLayer(Layer layer) { return layer == Layer.PTChangingEffects_7 || layer == Layer.TypeChangingEffects_4; } + @Override + public Set isDependentTo(List allEffectsInLayer) { + Set dependentTo = null; + for (ContinuousEffect effect : allEffectsInLayer) { + if (EnchantedEvening.class.equals(effect.getClass().getEnclosingClass())) { + if (dependentTo == null) { + dependentTo = new HashSet<>(); + } + dependentTo.add(effect.getId()); + } + } + return dependentTo; + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/DependentEffectsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/DependentEffectsTest.java new file mode 100644 index 0000000000..e844aacf1f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/DependentEffectsTest.java @@ -0,0 +1,176 @@ +/* + * 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 org.mage.test.cards.continuous; + +import mage.abilities.Ability; +import mage.constants.AbilityType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class DependentEffectsTest extends CardTestPlayerBase { + + /** + * Opalescence plus Enchanted Evening are still not wiping any lands. + */ + @Test + public void testLandsAreDestroyed() { + // Each other non-Aura enchantment is a creature in addition to its other types and has base power and base toughness each equal to its converted mana cost. + addCard(Zone.HAND, playerA, "Opalescence", 1); // {2}{W}{W} + + addCard(Zone.BATTLEFIELD, playerA, "Plains", 9); + addCard(Zone.BATTLEFIELD, playerA, "War Horn", 1); + + // All permanents are enchantments in addition to their other types. + addCard(Zone.HAND, playerA, "Enchanted Evening"); // {3}{W/U}{W/U} + + addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Opalescence"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Enchanted Evening"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPowerToughness(playerA, "Enchanted Evening", 5, 5); + assertPowerToughness(playerA, "War Horn", 3, 3); + + assertPermanentCount(playerA, "Island", 0); + assertPermanentCount(playerB, "Plains", 0); + } + + /** + * Opalescense is dependent on Enchanted Evening, so it will be applied + * after it regardless of timestamp. + * + * Tokens can also have mana costs, and as a consequence of that, converted + * mana costs. A token created with Rite of Replication would have the mana + * cost of the creature it targeted. Most tokens do not have mana costs + * though. + * + * Tokens with no mana costs would be 0/0, as you said, and would indeed be + * put into owner's graveyard next time State Based Actionas are performed. + * Tokens with mana costs would naturally have whatever power and toughness + * their CMC indicated. + */ + @Test + public void testTokensAreDestroyed() { + // Each other non-Aura enchantment is a creature in addition to its other types and has base power and base toughness each equal to its converted mana cost. + addCard(Zone.BATTLEFIELD, playerA, "Opalescence", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 9); + // Kicker {5} + // Put a token that's a copy of target creature onto the battlefield. If Rite of Replication was kicked, put five of those tokens onto the battlefield instead. + addCard(Zone.HAND, playerA, "Rite of Replication", 1); // This token can have a cmc + // All permanents are enchantments in addition to their other types. + addCard(Zone.HAND, playerA, "Enchanted Evening"); // {3}{W/U}{W/U} + + addCard(Zone.BATTLEFIELD, playerB, "Cobblebrute", 1); // 5/2 cmc = 4 + addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); + // Put two 1/1 white Soldier creature tokens onto the battlefield. + addCard(Zone.HAND, playerB, "Raise the Alarm"); // Instant {1}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rite of Replication", "Cobblebrute"); + setChoice(playerA, "No"); // no kicker + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Raise the Alarm"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Enchanted Evening"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Rite of Replication", 1); + assertGraveyardCount(playerB, "Raise the Alarm", 1); + + assertPowerToughness(playerA, "Enchanted Evening", 5, 5); + + assertPowerToughness(playerB, "Cobblebrute", 4, 4); + assertPowerToughness(playerA, "Cobblebrute", 4, 4); + + assertPermanentCount(playerB, "Soldier", 0); + assertPermanentCount(playerA, "Island", 0); + assertPermanentCount(playerB, "Plains", 0); + } + + @Test + public void testYixlidJailerAndNecroticOozeBasic() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // As long as Necrotic Ooze is on the battlefield, it has all activated abilities of all creature cards in all graveyards + addCard(Zone.BATTLEFIELD, playerA, "Necrotic Ooze", 1); + // {2}{1},{T}: Tap target creature. + addCard(Zone.GRAVEYARD, playerA, "Akroan Jailer", 1); + // {T}: Target attacking creature gets +1/+1 until end of turn. + addCard(Zone.GRAVEYARD, playerB, "Anointer of Champions", 1); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + Permanent necroticOoze = getPermanent("Necrotic Ooze", playerA); + int numberOfActivatedAbilities = 0; + for (Ability ability : necroticOoze.getAbilities(currentGame)) { + if (ability.getAbilityType().equals(AbilityType.ACTIVATED)) { + numberOfActivatedAbilities++; + } + } + Assert.assertEquals("Two abilities for Necrotic Ooze", 2, numberOfActivatedAbilities); + } + + @Test + public void testYixlidJailerAndNecroticOozeLooseAll() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // As long as Necrotic Ooze is on the battlefield, it has all activated abilities of all creature cards in all graveyards + addCard(Zone.BATTLEFIELD, playerA, "Necrotic Ooze", 1); + // {2}{1},{T}: Tap target creature. + addCard(Zone.GRAVEYARD, playerA, "Akroan Jailer", 1); + // {T}: Target attacking creature gets +1/+1 until end of turn. + addCard(Zone.GRAVEYARD, playerB, "Anointer of Champions", 1); + + // Cards in graveyards lose all abilities. + addCard(Zone.HAND, playerA, "Yixlid Jailer", 1); // Creature - {1}{B} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Yixlid Jailer"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Yixlid Jailer", 1); + + Permanent necroticOoze = getPermanent("Necrotic Ooze", playerA); + int numberOfActivatedAbilities = 0; + for (Ability ability : necroticOoze.getAbilities(currentGame)) { + if (ability.getAbilityType().equals(AbilityType.ACTIVATED)) { + numberOfActivatedAbilities++; + } + } + Assert.assertEquals("All abilities from cards in graveyard are removed - so no abilities for Necrotic Ooze", 0, numberOfActivatedAbilities); + } + +} diff --git a/Mage/src/mage/abilities/effects/ContinuousEffect.java b/Mage/src/mage/abilities/effects/ContinuousEffect.java index 5f6987ab47..355ff4347a 100644 --- a/Mage/src/mage/abilities/effects/ContinuousEffect.java +++ b/Mage/src/mage/abilities/effects/ContinuousEffect.java @@ -1,34 +1,35 @@ /* -* 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.effects; import java.util.List; +import java.util.Set; +import java.util.UUID; import mage.MageObjectReference; import mage.abilities.Ability; import mage.constants.Duration; @@ -43,25 +44,42 @@ import mage.game.Game; public interface ContinuousEffect extends Effect { boolean isUsed(); + boolean isDiscarded(); + void discard(); + Duration getDuration(); + long getOrder(); + void setOrder(long order); + boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game); + boolean hasLayer(Layer layer); + boolean isInactive(Ability source, Game game); + void init(Ability source, Game game); + Layer getLayer(); + SubLayer getSublayer(); + void overrideRuleText(String text); + List getAffectedObjects(); + Set isDependentTo(List allEffectsInLayer); + @Override void newId(); + @Override ContinuousEffect copy(); - + boolean isTemporary(); + void setTemporary(boolean temporary); } diff --git a/Mage/src/mage/abilities/effects/ContinuousEffectImpl.java b/Mage/src/mage/abilities/effects/ContinuousEffectImpl.java index e793058a81..48bd0bf8a2 100644 --- a/Mage/src/mage/abilities/effects/ContinuousEffectImpl.java +++ b/Mage/src/mage/abilities/effects/ContinuousEffectImpl.java @@ -29,6 +29,7 @@ package mage.abilities.effects; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.UUID; import mage.MageObjectReference; import mage.abilities.Ability; @@ -250,4 +251,9 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu this.temporary = temporary; } + @Override + public Set isDependentTo(List allEffectsInLayer) { + return null; + } + } diff --git a/Mage/src/mage/abilities/effects/ContinuousEffects.java b/Mage/src/mage/abilities/effects/ContinuousEffects.java index a6030dc051..8871ea4658 100644 --- a/Mage/src/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/mage/abilities/effects/ContinuousEffects.java @@ -38,6 +38,7 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import mage.MageObject; @@ -851,8 +852,9 @@ public class ContinuousEffects implements Serializable { //20091005 - 613 public void apply(Game game) { removeInactiveEffects(game); - List layerEffects = getLayeredEffects(game); - List layer = filterLayeredEffects(layerEffects, Layer.CopyEffects_1); + List activeLayerEffects = getLayeredEffects(game); + + List layer = filterLayeredEffects(activeLayerEffects, Layer.CopyEffects_1); for (ContinuousEffect effect : layer) { HashSet abilities = layeredEffects.getAbility(effect.getId()); for (Ability ability : abilities) { @@ -861,10 +863,10 @@ public class ContinuousEffects implements Serializable { } //Reload layerEffect if copy effects were applied if (layer.size() > 0) { - layerEffects = getLayeredEffects(game); + activeLayerEffects = getLayeredEffects(game); } - layer = filterLayeredEffects(layerEffects, Layer.ControlChangingEffects_2); + layer = filterLayeredEffects(activeLayerEffects, Layer.ControlChangingEffects_2); // apply control changing effects multiple times if it's needed // for cases when control over permanents with change control abilities is changed // e.g. Mind Control is controlled by Steal Enchantment @@ -882,55 +884,72 @@ public class ContinuousEffects implements Serializable { // reset control before reapplying control changing effects game.getBattlefield().resetPermanentsControl(); } - layer = filterLayeredEffects(layerEffects, Layer.TextChangingEffects_3); - for (ContinuousEffect effect : layer) { - HashSet abilities = layeredEffects.getAbility(effect.getId()); - for (Ability ability : abilities) { - effect.apply(Layer.TextChangingEffects_3, SubLayer.NA, ability, game); - } - } - layer = filterLayeredEffects(layerEffects, Layer.TypeChangingEffects_4); - for (ContinuousEffect effect : layer) { - HashSet abilities = layeredEffects.getAbility(effect.getId()); - for (Ability ability : abilities) { - effect.apply(Layer.TypeChangingEffects_4, SubLayer.NA, ability, game); - } - } - layer = filterLayeredEffects(layerEffects, Layer.ColorChangingEffects_5); - for (ContinuousEffect effect : layer) { - HashSet abilities = layeredEffects.getAbility(effect.getId()); - for (Ability ability : abilities) { - effect.apply(Layer.ColorChangingEffects_5, SubLayer.NA, ability, game); - } - } - Map> appliedEffects = new HashMap<>(); + applyLayer(activeLayerEffects, Layer.TextChangingEffects_3, game); + applyLayer(activeLayerEffects, Layer.TypeChangingEffects_4, game); + applyLayer(activeLayerEffects, Layer.ColorChangingEffects_5, game); + + Map> appliedEffectAbilities = new HashMap<>(); boolean done = false; + Map> waitingEffects = new LinkedHashMap<>(); + Set appliedEffects = new HashSet<>(); while (!done) { // loop needed if a added effect adds again an effect (e.g. Level 5- of Joraga Treespeaker) done = true; - layer = filterLayeredEffects(layerEffects, Layer.AbilityAddingRemovingEffects_6); + layer = filterLayeredEffects(activeLayerEffects, Layer.AbilityAddingRemovingEffects_6); for (ContinuousEffect effect : layer) { - if (layerEffects.contains(effect)) { - List appliedAbilities = appliedEffects.get(effect); + if (activeLayerEffects.contains(effect) && !appliedEffects.contains(effect.getId())) { // Effect does still exist and was not applied yet + Set dependentTo = effect.isDependentTo(layer); + if (dependentTo != null && !appliedEffects.containsAll(dependentTo)) { + waitingEffects.put(effect, dependentTo); + continue; + } + List appliedAbilities = appliedEffectAbilities.get(effect); HashSet abilities = layeredEffects.getAbility(effect.getId()); for (Ability ability : abilities) { if (appliedAbilities == null || !appliedAbilities.contains(ability)) { if (appliedAbilities == null) { appliedAbilities = new ArrayList<>(); - appliedEffects.put(effect, appliedAbilities); + appliedEffectAbilities.put(effect, appliedAbilities); } appliedAbilities.add(ability); effect.apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, ability, game); done = false; // list must be updated after each applied effect (eg. if "Turn to Frog" removes abilities) - layerEffects = getLayeredEffects(game); + activeLayerEffects = getLayeredEffects(game); + } + } + appliedEffects.add(effect.getId()); + + if (!waitingEffects.isEmpty()) { + // check if waiting effects can be applied now + for (Iterator>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) { + Map.Entry> entry = iterator.next(); + if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself + appliedAbilities = appliedEffectAbilities.get(entry.getKey()); + abilities = layeredEffects.getAbility(entry.getKey().getId()); + for (Ability ability : abilities) { + if (appliedAbilities == null || !appliedAbilities.contains(ability)) { + if (appliedAbilities == null) { + appliedAbilities = new ArrayList<>(); + appliedEffectAbilities.put(entry.getKey(), appliedAbilities); + } + appliedAbilities.add(ability); + entry.getKey().apply(Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, ability, game); + done = false; + // list must be updated after each applied effect (eg. if "Turn to Frog" removes abilities) + activeLayerEffects = getLayeredEffects(game); + } + } + appliedEffects.add(entry.getKey().getId()); + iterator.remove(); + } } } } } } - layer = filterLayeredEffects(layerEffects, Layer.PTChangingEffects_7); + layer = filterLayeredEffects(activeLayerEffects, Layer.PTChangingEffects_7); for (ContinuousEffect effect : layer) { HashSet abilities = layeredEffects.getAbility(effect.getId()); for (Ability ability : abilities) { @@ -952,14 +971,14 @@ public class ContinuousEffects implements Serializable { effect.apply(Layer.PTChangingEffects_7, SubLayer.SwitchPT_e, ability, game); } } - layer = filterLayeredEffects(layerEffects, Layer.PlayerEffects); + layer = filterLayeredEffects(activeLayerEffects, Layer.PlayerEffects); for (ContinuousEffect effect : layer) { HashSet abilities = layeredEffects.getAbility(effect.getId()); for (Ability ability : abilities) { effect.apply(Layer.PlayerEffects, SubLayer.NA, ability, game); } } - layer = filterLayeredEffects(layerEffects, Layer.RulesEffects); + layer = filterLayeredEffects(activeLayerEffects, Layer.RulesEffects); for (ContinuousEffect effect : layer) { HashSet abilities = layeredEffects.getAbility(effect.getId()); for (Ability ability : abilities) { @@ -968,6 +987,42 @@ public class ContinuousEffects implements Serializable { } } + private void applyLayer(List activeLayerEffects, Layer currentLayer, Game game) { + List layer = filterLayeredEffects(activeLayerEffects, currentLayer); + if (!layer.isEmpty()) { + int numberOfEffects = layer.size(); + Set appliedEffects = new HashSet<>(); + Map> waitingEffects = new LinkedHashMap<>(); + for (ContinuousEffect effect : layer) { + if (numberOfEffects > 1) { // If an effect is dependent to not applied effects yet of this layer, so wait to apply this effect + Set dependentTo = effect.isDependentTo(layer); + if (dependentTo != null && !appliedEffects.containsAll(dependentTo)) { + waitingEffects.put(effect, dependentTo); + continue; + } + } + applyContinuousEffect(effect, currentLayer, game); + appliedEffects.add(effect.getId()); + if (!waitingEffects.isEmpty()) { + // check if waiting effects can be applied now + for (Entry> entry : waitingEffects.entrySet()) { + if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself + applyContinuousEffect(entry.getKey(), currentLayer, game); + appliedEffects.add(entry.getKey().getId()); + } + } + } + } + } + } + + private void applyContinuousEffect(ContinuousEffect effect, Layer currentLayer, Game game) { + HashSet abilities = layeredEffects.getAbility(effect.getId()); + for (Ability ability : abilities) { + effect.apply(currentLayer, SubLayer.NA, ability, game); + } + } + /** * Adds a continuous ability with a reference to a sourceId. It's used for * effects that cease to exist again So this effects were removed again