From e431cd90abe416e5b351a466ddad54ef5abe647c Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 26 Jan 2024 19:47:23 -0500 Subject: [PATCH] Rework face down effect to layer 1b (#11689) * add test case for face-down permanent losing abilities * rework BecomesFaceDownCreatureEffect to layer 1b * add test for becoming Treasure * small refactor: Minimus Containment * add mycosynth lattice test --- .../src/mage/cards/m/MinimusContainment.java | 14 +-- .../cards/abilities/keywords/MorphTest.java | 113 ++++++++++++++++- .../BecomesFaceDownCreatureEffect.java | 115 ++++++++---------- 3 files changed, 161 insertions(+), 81 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MinimusContainment.java b/Mage.Sets/src/mage/cards/m/MinimusContainment.java index e210cd75d65..3c177fc6dd9 100644 --- a/Mage.Sets/src/mage/cards/m/MinimusContainment.java +++ b/Mage.Sets/src/mage/cards/m/MinimusContainment.java @@ -2,12 +2,10 @@ package mage.cards.m; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.AttachEffect; import mage.abilities.keyword.EnchantAbility; -import mage.abilities.mana.AnyColorManaAbility; +import mage.abilities.token.TreasureAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -51,16 +49,10 @@ public final class MinimusContainment extends CardImpl { class MinimusContainmentEffect extends ContinuousEffectImpl { - private static final Ability ability = new AnyColorManaAbility(); - - static { - Cost cost = new SacrificeSourceCost(); - cost.setText("sacrifice this artifact"); - ability.addCost(cost); - } + private static final Ability ability = new TreasureAbility(false); MinimusContainmentEffect() { - super(Duration.WhileOnBattlefield, Outcome.Benefit); + super(Duration.WhileOnBattlefield, Outcome.LoseAbility); staticText = "enchanted permanent is a Treasure artifact with " + "\"{T}, Sacrifice this artifact: Add one mana of any color,\" and it loses all other abilities"; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java index 06b5ab69390..752ec7844cf 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java @@ -418,7 +418,7 @@ public class MorphTest extends CardTestPlayerBase { for (Card card : currentGame.getExile().getAllCards(currentGame)) { if (card.getName().equals("Birchlore Rangers")) { - Assert.assertEquals("Birchlore Rangers has to be face up in exile", false, card.isFaceDown(currentGame)); + Assert.assertFalse("Birchlore Rangers has to be face up in exile", card.isFaceDown(currentGame)); break; } } @@ -457,7 +457,7 @@ public class MorphTest extends CardTestPlayerBase { for (Card card : playerA.getGraveyard().getCards(currentGame)) { if (card.getName().equals("Ashcloud Phoenix")) { - Assert.assertEquals("Ashcloud Phoenix has to be face up in graveyard", false, card.isFaceDown(currentGame)); + Assert.assertFalse("Ashcloud Phoenix has to be face up in graveyard", card.isFaceDown(currentGame)); break; } } @@ -493,7 +493,7 @@ public class MorphTest extends CardTestPlayerBase { for (Card card : playerA.getGraveyard().getCards(currentGame)) { if (card.getName().equals("Ashcloud Phoenix")) { - Assert.assertEquals("Ashcloud Phoenix has to be face up in graveyard", false, card.isFaceDown(currentGame)); + Assert.assertFalse("Ashcloud Phoenix has to be face up in graveyard", card.isFaceDown(currentGame)); break; } } @@ -772,7 +772,7 @@ public class MorphTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Brine Elemental", 1); assertPermanentCount(playerB, "Brine Elemental", 1); - Assert.assertTrue("Skip next turn has to be added to TurnMods", currentGame.getState().getTurnMods().size() == 1); + Assert.assertEquals("Skip next turn has to be added to TurnMods", 1, currentGame.getState().getTurnMods().size()); } /** @@ -963,7 +963,6 @@ public class MorphTest extends CardTestPlayerBase { @Test public void test_LandWithMorph_MorphAfterLand() { - removeAllCardsFromHand(playerA); // Morph {2} addCard(Zone.HAND, playerA, "Zoetic Cavern"); @@ -1118,4 +1117,108 @@ public class MorphTest extends CardTestPlayerBase { assertPermanentCount(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2); } + @Test + public void testLoseAbilities() { + addCard(Zone.HAND, playerA, "Monastery Flock"); + addCard(Zone.HAND, playerA, "Tamiyo's Compleation"); + addCard(Zone.BATTLEFIELD, playerA, "Secret Plans"); // face-down creatures get +0/+1 + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Monastery Flock using Morph"); + + checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + checkPlayableAbility("unmorph", 1, PhaseStep.BEGIN_COMBAT, playerA, "{U}: Turn this", true); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Tamiyo's Compleation", EmptyNames.FACE_DOWN_CREATURE.toString()); + + checkPlayableAbility("unmorph", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{U}: Turn this", false); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTapped(EmptyNames.FACE_DOWN_CREATURE.toString(), true); + assertAttachedTo(playerA, "Tamiyo's Compleation", EmptyNames.FACE_DOWN_CREATURE.toString(), true); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + } + + @Test + public void testBecomeTreasure() { + addCard(Zone.HAND, playerA, "Sage-Eye Harrier"); // 1/5 Flying, Morph 3W + addCard(Zone.HAND, playerA, "Minimus Containment"); // 2W Aura + // Enchant nonland permanent + // Enchanted permanent is a Treasure artifact with “{T},Sacrifice this artifact: Add one mana of any color,” + // and it loses all other abilities. (If it was a creature, it’s no longer a creature.) + + addCard(Zone.BATTLEFIELD, playerA, "Plains", 7); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sage-Eye Harrier using Morph"); + + checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 2); + checkPlayableAbility("unmorph", 1, PhaseStep.BEGIN_COMBAT, playerA, "{3}{W}: Turn this", true); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Minimus Containment", EmptyNames.FACE_DOWN_CREATURE.toString()); + + checkPlayableAbility("unmorph", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{3}{W}: Turn this", false); + + checkPlayableAbility("treasure", 1, PhaseStep.END_TURN, playerA, "{T}, Sacrifice ", true); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.UPKEEP); + execute(); + + assertSubtype(EmptyNames.FACE_DOWN_CREATURE.toString(), SubType.TREASURE); + assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.ARTIFACT, true); + assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.CREATURE, false); + assertAttachedTo(playerA, "Minimus Containment", EmptyNames.FACE_DOWN_CREATURE.toString(), true); + } + + @Test + public void testMycosynthAfter() { + addCard(Zone.HAND, playerA, "Monastery Flock"); + addCard(Zone.HAND, playerA, "Mycosynth Lattice"); + addCard(Zone.BATTLEFIELD, playerA, "Secret Plans"); // face-down creatures get +0/+1 + addCard(Zone.BATTLEFIELD, playerA, "Island", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Monastery Flock using Morph"); + + checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + checkPlayableAbility("unmorph", 1, PhaseStep.BEGIN_COMBAT, playerA, "{U}: Turn this", true); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Mycosynth Lattice"); + + checkPlayableAbility("unmorph", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{U}: Turn this", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.ARTIFACT, true); + assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.CREATURE, true); + assertNotSubtype(EmptyNames.FACE_DOWN_CREATURE.toString(), SubType.BIRD); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + } + + @Test + public void testMycosynthBefore() { + addCard(Zone.HAND, playerA, "Monastery Flock"); + addCard(Zone.BATTLEFIELD, playerA, "Mycosynth Lattice"); + addCard(Zone.BATTLEFIELD, playerA, "Secret Plans"); // face-down creatures get +0/+1 + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Monastery Flock using Morph"); + + checkPT("face down", 1, PhaseStep.BEGIN_COMBAT, playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + checkPlayableAbility("unmorph", 1, PhaseStep.BEGIN_COMBAT, playerA, "{U}: Turn this", true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.ARTIFACT, true); + assertType(EmptyNames.FACE_DOWN_CREATURE.toString(), CardType.CREATURE, true); + assertNotSubtype(EmptyNames.FACE_DOWN_CREATURE.toString(), SubType.BIRD); + assertPowerToughness(playerA, EmptyNames.FACE_DOWN_CREATURE.toString(), 2, 3); + } + } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java index e733e4aa38c..c4a5c4859bd 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java @@ -29,7 +29,9 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { MANIFESTED, MANUAL, MEGAMORPHED, - MORPHED + MORPHED, + DISGUISED, + CLOAKED } protected int zoneChangeCounter; @@ -55,7 +57,7 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { } public BecomesFaceDownCreatureEffect(Costs turnFaceUpCosts, MageObjectReference objectReference, Duration duration, FaceDownType faceDownType) { - super(duration, Outcome.BecomeCreature); + super(duration, Layer.CopyEffects_1, SubLayer.FaceDownEffects_1b, Outcome.BecomeCreature); this.objectReference = objectReference; this.zoneChangeCounter = Integer.MIN_VALUE; if (turnFaceUpCosts != null) { @@ -77,20 +79,20 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { this.faceDownType = effect.faceDownType; } - @Override - public BecomesFaceDownCreatureEffect copy() { - return new BecomesFaceDownCreatureEffect(this); - } - private static Costs createCosts(Cost cost) { if (cost == null) { - return null; + return null; // ignore warning, null is used specifically } Costs costs = new CostsImpl<>(); costs.add(cost); return costs; } + @Override + public BecomesFaceDownCreatureEffect copy() { + return new BecomesFaceDownCreatureEffect(this); + } + @Override public void init(Ability source, Game game) { super.init(source, game); @@ -108,7 +110,7 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { } @Override - public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + public boolean apply(Game game, Ability source) { Permanent permanent; if (objectReference != null) { permanent = objectReference.getPermanent(game); @@ -128,72 +130,55 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { case MEGAMORPHED: permanent.setMorphed(true); break; + default: + throw new UnsupportedOperationException("FaceDownType not yet supported: " + faceDownType); } } - switch (layer) { - case TypeChangingEffects_4: - permanent.setName(""); - permanent.removeAllSuperTypes(game); - permanent.removeAllCardTypes(game); - permanent.addCardType(game, CardType.CREATURE); - permanent.removeAllSubTypes(game); - break; - case ColorChangingEffects_5: - permanent.getColor(game).setColor(new ObjectColor()); - break; - case AbilityAddingRemovingEffects_6: - Card card = game.getCard(permanent.getId()); // - List abilitiesToRemove = new ArrayList<>(); - for (Ability ability : permanent.getAbilities()) { - // keep gained abilities from other sources, removes only own (card text) - if (card != null && !card.getAbilities().contains(ability)) { - continue; - } + permanent.setName(EmptyNames.FACE_DOWN_CREATURE.toString()); + permanent.removeAllSuperTypes(game); + permanent.removeAllCardTypes(game); + permanent.addCardType(game, CardType.CREATURE); + permanent.removeAllSubTypes(game); + permanent.getColor(game).setColor(ObjectColor.COLORLESS); + Card card = game.getCard(permanent.getId()); - // 701.33c - // If a card with morph is manifested, its controller may turn that card face up using - // either the procedure described in rule 702.36e to turn a face-down permanent with morph face up - // or the procedure described above to turn a manifested permanent face up. - // - // so keep all tune face up abilities and other face down compatible - if (ability.getWorksFaceDown()) { - ability.setRuleVisible(false); - continue; - } + List abilitiesToRemove = new ArrayList<>(); + for (Ability ability : permanent.getAbilities()) { - if (!ability.getRuleVisible() && !ability.getEffects().isEmpty()) { - if (ability.getEffects().get(0) instanceof BecomesFaceDownCreatureEffect) { - continue; - } - } - abilitiesToRemove.add(ability); - } - permanent.removeAbilities(abilitiesToRemove, source.getSourceId(), game); - if (turnFaceUpAbility != null) { - permanent.addAbility(turnFaceUpAbility, source.getSourceId(), game); - } - break; - case PTChangingEffects_7: - if (sublayer == SubLayer.SetPT_7b) { - permanent.getPower().setModifiedBaseValue(2); - permanent.getToughness().setModifiedBaseValue(2); + // keep gained abilities from other sources, removes only own (card text) + if (card != null && !card.getAbilities().contains(ability)) { + continue; + } + + // 701.33c + // If a card with morph is manifested, its controller may turn that card face up using + // either the procedure described in rule 702.36e to turn a face-down permanent with morph face up + // or the procedure described above to turn a manifested permanent face up. + // + // so keep all tune face up abilities and other face down compatible + if (ability.getWorksFaceDown()) { + ability.setRuleVisible(false); + continue; + } + + if (!ability.getRuleVisible() && !ability.getEffects().isEmpty()) { + if (ability.getEffects().get(0) instanceof BecomesFaceDownCreatureEffect) { + continue; } + } + abilitiesToRemove.add(ability); } - } else if (duration == Duration.Custom && foundPermanent == true) { + permanent.removeAbilities(abilitiesToRemove, source.getSourceId(), game); + if (turnFaceUpAbility != null) { // TODO: shouldn't be added by this effect, but separately + permanent.addAbility(turnFaceUpAbility, source.getSourceId(), game); + } + permanent.getPower().setModifiedBaseValue(2); + permanent.getToughness().setModifiedBaseValue(2); + } else if (duration == Duration.Custom && foundPermanent) { discard(); } return true; } - @Override - public boolean apply(Game game, Ability source) { - return false; - } - - @Override - public boolean hasLayer(Layer layer) { - return layer == Layer.PTChangingEffects_7 || layer == Layer.AbilityAddingRemovingEffects_6 || layer == Layer.ColorChangingEffects_5 || layer == Layer.TypeChangingEffects_4; - } - }