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
This commit is contained in:
xenohedron 2024-01-26 19:47:23 -05:00 committed by GitHub
parent ba0c3baf6c
commit e431cd90ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 161 additions and 81 deletions

View file

@ -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";
}

View file

@ -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, its 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);
}
}

View file

@ -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<Cost> 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<Cost> createCosts(Cost cost) {
if (cost == null) {
return null;
return null; // ignore warning, null is used specifically
}
Costs<Cost> 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<Ability> 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<Ability> 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;
}
}