diff --git a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java index 57fada3d7a7..7bf737f1946 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java @@ -149,7 +149,7 @@ public class TestCardRenderDialog extends MageDialog { if (transform) { // need direct transform call to keep other side info (original) - TransformAbility.transformPermanent(permanent, permCard.getSecondCardFace(), game, null); + TransformAbility.transformPermanent(permanent, game, null); } if (damage > 0) permanent.damage(damage, controllerId, null, game); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java new file mode 100644 index 00000000000..d4f56c1aef3 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java @@ -0,0 +1,225 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class IncubateTest extends CardTestPlayerBase { + + private void checkIncubate(String info, int needIncubateTokens, int needPhyrexianTokens, boolean needPlayableTransform) { + checkPermanentCount(info, 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", needIncubateTokens); + checkPermanentCount(info, 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", needPhyrexianTokens); + checkPlayableAbility(info, 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: Transform", needPlayableTransform); + } + + @Test + public void test_Transform_Normal() { + // Incubate 3. (Create an Incubator token with three +1/+1 counters on it and “{2}: Transform this artifact.” + // It transforms into a 0/0 Phyrexian artifact creature.) + // Draw a card. + addCard(Zone.HAND, playerA, "Eyes of Gitaxias", 1); // {2}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + // + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); // for transform + + // prepare incubator 3 + checkIncubate("before", 0, 0, false); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Eyes of Gitaxias"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkIncubate("after prepare", 1, 0, true); + + // transform + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: Transform"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkIncubate("after transform", 0, 1, false); + checkPT("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", 3, 3); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_Transform_Custom() { + // target transform + addCustomEffect_TransformTarget(playerA); + + // Alluring Suitor, 2/3 + // Deadly Dancer, 3/3 + addCard(Zone.BATTLEFIELD, playerA, "Alluring Suitor", 1); + + checkPermanentCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alluring Suitor", 1); + + // transform to back + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target transform", "Alluring Suitor"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after back", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alluring Suitor", 0); + checkPermanentCount("after back", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deadly Dancer", 1); + + // transform to front + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target transform", "Deadly Dancer"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after front", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alluring Suitor", 1); + checkPermanentCount("after front", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deadly Dancer", 0); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_Transform_IncubatorToken() { + // target transform + addCustomEffect_TransformTarget(playerA); + + // Incubate 3. (Create an Incubator token with three +1/+1 counters on it and “{2}: Transform this artifact.” + // It transforms into a 0/0 Phyrexian artifact creature.) + // Draw a card. + addCard(Zone.HAND, playerA, "Eyes of Gitaxias", 1); // {2}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + + // prepare incubator 3 + checkPermanentCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 0); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Eyes of Gitaxias"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 1); + + // transform to back + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target transform", "Incubator Token"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after back", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", 1); + + // transform to front + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target transform", "Phyrexian Token"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after front", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 1); + checkPermanentCount("after front", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", 0); + + // transform to back 2 (counters must be saved on transform, so it will be alive) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target transform", "Incubator Token"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after front 2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 0); + checkPermanentCount("after front 2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_Transform_CopiedByPermanent_FrontSide() { + // use case: copy one side, can't tranform + + // target transform + addCustomEffect_TransformTarget(playerA); + // target destroy + addCustomEffect_DestroyTarget(playerA); + + // Incubate 3. (Create an Incubator token with three +1/+1 counters on it and “{2}: Transform this artifact.” + // It transforms into a 0/0 Phyrexian artifact creature.) + // Draw a card. + addCard(Zone.HAND, playerA, "Eyes of Gitaxias", 1); // {2}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + // + // You may have Copy Artifact enter the battlefield as a copy of any artifact on the battlefield, + // except it’s an enchantment in addition to its other types. + addCard(Zone.HAND, playerA, "Copy Artifact", 1); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + // prepare incubator 3 + checkPermanentCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 0); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Eyes of Gitaxias"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 1); + + // prepare copied front side + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Copy Artifact"); + setChoice(playerA, true); // use copy + setChoice(playerA, "Incubator Token"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 2); + + // kill original token + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Incubator Token[no copy]"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after kill", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 1); + showBattlefield("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // try to transform (nothing happen) + // 701.28c + // If a spell or ability instructs a player to transform a permanent that isn’t represented by a + // transforming token or a transforming double-faced card, nothing happens. + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target transform", "Incubator Token"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after transform", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 1); + checkPermanentCount("after transform", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", 0); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void test_Transform_CopiedByPermanent_BackSide() { + // use case: copy one side, can't tranform + + // target transform + addCustomEffect_TransformTarget(playerA); + // target destroy + addCustomEffect_DestroyTarget(playerA); + + // Incubate 3. (Create an Incubator token with three +1/+1 counters on it and “{2}: Transform this artifact.” + // It transforms into a 0/0 Phyrexian artifact creature.) + // Draw a card. + addCard(Zone.HAND, playerA, "Eyes of Gitaxias", 1); // {2}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + // + // You may have Copy Artifact enter the battlefield as a copy of any artifact on the battlefield, + // except it’s an enchantment in addition to its other types. + addCard(Zone.HAND, playerA, "Copy Artifact", 1); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + // prepare incubator 3 + checkPermanentCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 0); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Eyes of Gitaxias"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 1); + + // transform to back + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target transform", "Incubator Token"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after back", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", 1); + + // prepare copied back side + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Copy Artifact"); + setChoice(playerA, true); // use copy + setChoice(playerA, "Phyrexian Token"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", 2); + + // kill original token + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Phyrexian Token[no copy]"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after kill", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", 1); + showBattlefield("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA); + + // try to transform back side (nothing happen) + // 701.28c + // If a spell or ability instructs a player to transform a permanent that isn’t represented by a + // transforming token or a transforming double-faced card, nothing happens. + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target transform", "Phyrexian Token"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after transform", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Incubator Token", 0); + checkPermanentCount("after transform", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java b/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java index 7ef9808df92..6606464266b 100644 --- a/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java @@ -40,9 +40,13 @@ public class TransformAbility extends SimpleStaticAbility { return ""; } - public static void transformPermanent(Permanent permanent, MageObject sourceCard, Game game, Ability source) { + /** + * Apply transform effect to permanent (copy characteristic and other things) + */ + public static boolean transformPermanent(Permanent permanent, Game game, Ability source) { + MageObject sourceCard = findSourceObjectForTransform(permanent); if (sourceCard == null) { - return; + return false; } permanent.setTransformed(true); @@ -73,6 +77,25 @@ public class TransformAbility extends SimpleStaticAbility { permanent.getToughness().setModifiedBaseValue(sourceCard.getToughness().getValue()); permanent.setStartingLoyalty(sourceCard.getStartingLoyalty()); permanent.setStartingDefense(sourceCard.getStartingDefense()); + + return true; + } + + private static MageObject findSourceObjectForTransform(Permanent permanent) { + if (permanent == null) { + return null; + } + + // copies can't transform + if (permanent.isCopy()) { + return null; + } + + if (permanent instanceof PermanentToken) { + return ((PermanentToken) permanent).getToken().getBackFace(); + } else { + return permanent.getSecondCardFace(); + } } public static Card transformCardSpellStatic(Card mainSide, Card otherSide, Game game) { @@ -130,35 +153,16 @@ class TransformEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent == null) { return false; } - if (permanent.isCopy()) { // copies can't transform - return true; - } - + // only for transformed permanents if (!permanent.isTransformed()) { - // keep original card - return true; - } - - MageObject card; - if (permanent instanceof PermanentToken) { - card = ((PermanentToken) permanent).getToken().getBackFace(); - } else { - card = permanent.getSecondCardFace(); - } - - if (card == null) { return false; } - TransformAbility.transformPermanent(permanent, card, game, source); - - return true; - + return TransformAbility.transformPermanent(permanent, game, source); } @Override diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 8a86ea1f7e8..f8218fe8f26 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -602,6 +602,11 @@ public abstract class CardImpl extends MageObjectImpl implements Card { // mtg rules method: here // GUI related method: search "transformable = true" in CardView // TODO: check and fix method usage in game engine, it's must be mtg rules logic, not GUI + + // 701.28c + // If a spell or ability instructs a player to transform a permanent that + // isn’t represented by a transforming token or a transforming double-faced + // card, nothing happens. return this.secondSideCardClazz != null || this.nightCard; } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 874fc6630d8..59159d7ca93 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1972,9 +1972,12 @@ public abstract class GameImpl implements Game { // if it was no copy of copy take the target itself if (newBluePrint == null) { newBluePrint = copyFromPermanent.copy(); + + // reset to original characteristics newBluePrint.reset(this); - //getState().addCard(permanent); + // workaround to find real copyable characteristics of transformed/facedown/etc permanents + if (copyFromPermanent.isMorphed() || copyFromPermanent.isManifested() || copyFromPermanent.isFaceDown(this)) { @@ -1982,7 +1985,7 @@ public abstract class GameImpl implements Game { } newBluePrint.assignNewId(); if (copyFromPermanent.isTransformed()) { - TransformAbility.transformPermanent(newBluePrint, newBluePrint.getSecondCardFace(), this, source); + TransformAbility.transformPermanent(newBluePrint,this, source); } if (copyFromPermanent.isPrototyped()) { Abilities abilities = copyFromPermanent.getAbilities(); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index b6c3642b0ea..9946f8cdd55 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -72,11 +72,13 @@ public class PermanentCard extends PermanentImpl { if (card instanceof LevelerCard) { maxLevelCounters = ((LevelerCard) card).getMaxLevelCounters(); } + + // if transformed on ETB if (card.isTransformable()) { if (game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId()) != null || NightboundAbility.checkCard(this, game)) { game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId(), null); - TransformAbility.transformPermanent(this, getSecondCardFace(), game, null); + TransformAbility.transformPermanent(this, game, null); } } } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentToken.java b/Mage/src/main/java/mage/game/permanent/PermanentToken.java index 5cab268b6e1..6145172324a 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentToken.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentToken.java @@ -32,8 +32,10 @@ public class PermanentToken extends PermanentImpl { this.power = new MageInt(token.getPower().getModifiedBaseValue()); this.toughness = new MageInt(token.getToughness().getModifiedBaseValue()); this.copyFromToken(this.token, game, false); // needed to have at this time (e.g. for subtypes for entersTheBattlefield replacement effects) + + // if transformed on ETB if (this.token.isEntersTransformed()) { - TransformAbility.transformPermanent(this, this.token.getBackFace(), game, null); + TransformAbility.transformPermanent(this, game, null); } // token's ZCC must be synced with original token to keep abilities settings @@ -146,6 +148,10 @@ public class PermanentToken extends PermanentImpl { @Override public boolean isTransformable() { + // 701.28c + // If a spell or ability instructs a player to transform a permanent that + // isn’t represented by a transforming token or a transforming double-faced card, + // nothing happens. return token.getBackFace() != null; }