diff --git a/Mage.Sets/src/mage/cards/a/AncientGreenwarden.java b/Mage.Sets/src/mage/cards/a/AncientGreenwarden.java index 1a66c847528..95d3c0f35fe 100644 --- a/Mage.Sets/src/mage/cards/a/AncientGreenwarden.java +++ b/Mage.Sets/src/mage/cards/a/AncientGreenwarden.java @@ -4,7 +4,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; -import mage.abilities.effects.common.ruleModifying.PlayLandsFromGraveyardEffect; +import mage.abilities.effects.common.ruleModifying.PlayLandsFromGraveyardControllerEffect; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -35,7 +35,7 @@ public final class AncientGreenwarden extends CardImpl { this.addAbility(ReachAbility.getInstance()); // You may play lands from your graveyard. - this.addAbility(new SimpleStaticAbility(new PlayLandsFromGraveyardEffect())); + this.addAbility(new SimpleStaticAbility(new PlayLandsFromGraveyardControllerEffect())); // If a land entering the battlefield causes a triggered ability of a permanent you control to trigger, that ability triggers an additional time. this.addAbility(new SimpleStaticAbility(new AncientGreenwardenEffect())); diff --git a/Mage.Sets/src/mage/cards/c/CrucibleOfWorlds.java b/Mage.Sets/src/mage/cards/c/CrucibleOfWorlds.java index 2faefc1e60b..abd8bb3d1b1 100644 --- a/Mage.Sets/src/mage/cards/c/CrucibleOfWorlds.java +++ b/Mage.Sets/src/mage/cards/c/CrucibleOfWorlds.java @@ -1,25 +1,24 @@ - package mage.cards.c; -import java.util.UUID; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.ruleModifying.PlayLandsFromGraveyardEffect; +import mage.abilities.effects.common.ruleModifying.PlayLandsFromGraveyardControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; +import java.util.UUID; + /** - * * @author Plopman */ public final class CrucibleOfWorlds extends CardImpl { public CrucibleOfWorlds(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}"); + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); // You may play land cards from your graveyard. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayLandsFromGraveyardEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayLandsFromGraveyardControllerEffect())); } public CrucibleOfWorlds(final CrucibleOfWorlds card) { diff --git a/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java b/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java index 64203ad5518..adaecbd2615 100644 --- a/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java +++ b/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java @@ -30,6 +30,7 @@ import java.util.UUID; * @author arcox */ public final class RadhaHeartOfKeld extends CardImpl { + private static final FilterCard filter = new FilterLandCard("play land cards"); public RadhaHeartOfKeld(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/r/RamunapExcavator.java b/Mage.Sets/src/mage/cards/r/RamunapExcavator.java index 3e1eecd526a..e7756a883d5 100644 --- a/Mage.Sets/src/mage/cards/r/RamunapExcavator.java +++ b/Mage.Sets/src/mage/cards/r/RamunapExcavator.java @@ -1,16 +1,16 @@ - package mage.cards.r; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.ruleModifying.PlayLandsFromGraveyardEffect; +import mage.abilities.effects.common.ruleModifying.PlayLandsFromGraveyardControllerEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; +import java.util.UUID; + /** * @author fireshoes */ @@ -25,7 +25,7 @@ public final class RamunapExcavator extends CardImpl { this.toughness = new MageInt(3); // You may play land cards from your graveyard. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayLandsFromGraveyardEffect())); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayLandsFromGraveyardControllerEffect())); } public RamunapExcavator(final RamunapExcavator card) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayLandsFromGraveyardTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayLandsFromGraveyardTest.java new file mode 100644 index 00000000000..9114c1d37ba --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayLandsFromGraveyardTest.java @@ -0,0 +1,48 @@ +package org.mage.test.cards.asthough; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps; + +/** + * @author JayDi85 + */ +public class PlayLandsFromGraveyardTest extends CardTestPlayerBaseWithAIHelps { + + @Test + public void test_CrucibleOfWorlds() { + removeAllCardsFromHand(playerA); + removeAllCardsFromLibrary(playerA); + + // You may play lands from your graveyard. + addCard(Zone.HAND, playerA, "Crucible of Worlds"); // {3} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // + addCard(Zone.GRAVEYARD, playerA, "Island", 1); + // + addCard(Zone.GRAVEYARD, playerA, "Balduvian Bears"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + checkGraveyardCount("graveyard before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears", 1); + checkGraveyardCount("graveyard before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island", 1); + checkPlayableAbility("can't play as creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Balduvian Bears", false); + checkPlayableAbility("can't play as land", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Island", false); + + // play artifact and apply play from graveyard effect + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Crucible of Worlds"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPlayableAbility("can't play as creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Balduvian Bears", false); + checkPlayableAbility("can play as land", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Island", true); + + // play land + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, "Island", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java index 75acf99c1c3..ebbab933f85 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaces/ModalDoubleFacesCardsTest.java @@ -118,8 +118,8 @@ public class ModalDoubleFacesCardsTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel"); checkLibraryCount("library before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 1); - checkPlayableAbility("can play as land", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Akoum Warrior", true); - checkPlayableAbility("can play as creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Akoum Teeth", true); + checkPlayableAbility("can play as creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Akoum Warrior", true); + checkPlayableAbility("can play as land", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Akoum Teeth", true); // play as creature castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior"); @@ -136,6 +136,64 @@ public class ModalDoubleFacesCardsTest extends CardTestPlayerBase { assertLife(playerA, 20 - 6); // creature life pay instead mana } + @Test + public void test_PlayFromNonHand_SecondSideAsLand_ByRadhaHeartOfKeld() { + removeAllCardsFromHand(playerA); + removeAllCardsFromLibrary(playerA); + + // Akoum Warrior {5}{R} - creature + // Akoum Teeth - land + addCard(Zone.LIBRARY, playerA, "Akoum Warrior"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + // + // You may look at the top card of your library any time, and you may play lands from the top of your library. + addCard(Zone.BATTLEFIELD, playerA, "Radha, Heart of Keld"); + + checkLibraryCount("library before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 1); + checkPlayableAbility("can't play as creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Akoum Warrior", false); + checkPlayableAbility("can play as land", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Akoum Teeth", true); + + // play as land + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Teeth"); + checkLibraryCount("library after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 0); + checkPermanentCount("after cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 0); + checkPermanentCount("after cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Teeth", 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_PlayFromNonHand_SecondSideAsLand_CrucibleOfWorlds() { + removeAllCardsFromHand(playerA); + removeAllCardsFromLibrary(playerA); + + // Akoum Warrior {5}{R} - creature + // Akoum Teeth - land + addCard(Zone.GRAVEYARD, playerA, "Akoum Warrior"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); + // + // You may play lands from your graveyard. + addCard(Zone.BATTLEFIELD, playerA, "Crucible of Worlds"); + + checkGraveyardCount("graveyard before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 1); + checkPlayableAbility("can't play as creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Akoum Warrior", false); + checkPlayableAbility("can play as land", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Akoum Teeth", true); + + // play as land + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Teeth"); + checkLibraryCount("library after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 0); + checkPermanentCount("after cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Warrior", 0); + checkPermanentCount("after cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akoum Teeth", 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + @Test public void test_PlayFromNonHand_GraveyardByYawgmothsAgenda() { removeAllCardsFromHand(playerA); diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 2f5ee5fa565..eebdcccb61f 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -515,7 +515,15 @@ public class ContinuousEffects implements Serializable { UUID idToCheck; if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) { idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId(); - } else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof ModalDoubleFacesCardHalf) { + } else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof ModalDoubleFacesCardHalf + && !type.needPlayCardAbility()) { + // each mdf side uses own characteristics to check for playing, all other cases must use main card + // rules: + // "If an effect allows you to play a land or cast a spell from among a group of cards, + // you may play or cast a modal double-faced card with any face that fits the criteria + // of that effect. For example, if Sejiri Shelter / Sejiri Glacier is in your graveyard + // and an effect allows you to play lands from your graveyard, you could play Sejiri Glacier. + // That effect doesn't allow you to cast Sejiri Shelter." idToCheck = ((ModalDoubleFacesCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId(); } else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell && !type.needPlayCardAbility()) { @@ -525,7 +533,9 @@ public class ContinuousEffects implements Serializable { Card card = game.getCard(objectId); if (card instanceof SplitCardHalf) { idToCheck = ((SplitCardHalf) card).getParentCard().getId(); - } else if (card instanceof ModalDoubleFacesCardHalf) { + } else if (card instanceof ModalDoubleFacesCardHalf + && !type.needPlayCardAbility()) { + // each mdf side uses own characteristics to check for playing, all other cases must use main card idToCheck = ((ModalDoubleFacesCardHalf) card).getParentCard().getId(); } else if (card instanceof AdventureCardSpell && !type.needPlayCardAbility()) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/PlayLandsFromGraveyardControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/PlayLandsFromGraveyardControllerEffect.java new file mode 100644 index 00000000000..4a2eb21a7c6 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/PlayLandsFromGraveyardControllerEffect.java @@ -0,0 +1,78 @@ +package mage.abilities.effects.common.ruleModifying; + +import mage.abilities.Ability; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.cards.Card; +import mage.constants.AsThoughEffectType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.FilterCard; +import mage.filter.common.FilterLandCard; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author JayDi85 + */ +public class PlayLandsFromGraveyardControllerEffect extends AsThoughEffectImpl { + + private final FilterCard filter; + + public PlayLandsFromGraveyardControllerEffect() { + this(new FilterLandCard("lands")); + } + + public PlayLandsFromGraveyardControllerEffect(FilterCard filter) { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); + this.filter = filter; + staticText = "You may play " + filter.getMessage() + " from your graveyard"; + } + + public PlayLandsFromGraveyardControllerEffect(final PlayLandsFromGraveyardControllerEffect effect) { + super(effect); + this.filter = effect.filter; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + + @Override + public PlayLandsFromGraveyardControllerEffect copy() { + return new PlayLandsFromGraveyardControllerEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + return applies(objectId, null, source, game, affectedControllerId); + } + + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + Card cardToCheck = game.getCard(objectId); + objectId = CardUtil.getMainCardId(game, objectId); // for split cards + if (cardToCheck == null) { + return false; + } + + Player player = game.getPlayer(cardToCheck.getOwnerId()); + if (player == null) { + return false; + } + + UUID needCardId = objectId; + if (player.getGraveyard().getCards(game).stream().noneMatch(c -> c.getId().equals(needCardId))) { + return false; + } + + return playerId.equals(source.getControllerId()) + && cardToCheck.isOwnedBy(source.getControllerId()) + && (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand()) + && filter.match(cardToCheck, game); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/PlayLandsFromGraveyardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/PlayLandsFromGraveyardEffect.java deleted file mode 100644 index 3fc89c0464b..00000000000 --- a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/PlayLandsFromGraveyardEffect.java +++ /dev/null @@ -1,46 +0,0 @@ -package mage.abilities.effects.common.ruleModifying; - -import mage.abilities.Ability; -import mage.abilities.common.PlayLandFromGraveyardAbility; -import mage.abilities.effects.ContinuousEffectImpl; -import mage.cards.Card; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.game.Game; -import mage.players.Player; - -public class PlayLandsFromGraveyardEffect extends ContinuousEffectImpl { - - public PlayLandsFromGraveyardEffect() { - super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.staticText = "You may play lands from your graveyard"; - } - - public PlayLandsFromGraveyardEffect(final PlayLandsFromGraveyardEffect effect) { - super(effect); - } - - @Override - public PlayLandsFromGraveyardEffect copy() { - return new PlayLandsFromGraveyardEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - for (Card card : player.getGraveyard().getCards(game)) { - if (card != null && card.isLand()) { - PlayLandFromGraveyardAbility ability = new PlayLandFromGraveyardAbility(card.getName()); - ability.setSourceId(card.getId()); - ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card, ability); - } - } - return true; - } - return false; - } -}