diff --git a/Mage.Sets/src/mage/cards/d/DeeprootHistorian.java b/Mage.Sets/src/mage/cards/d/DeeprootHistorian.java index 83e5f276514..fee49d05f70 100644 --- a/Mage.Sets/src/mage/cards/d/DeeprootHistorian.java +++ b/Mage.Sets/src/mage/cards/d/DeeprootHistorian.java @@ -1,32 +1,41 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.keyword.RetraceAbility; -import mage.cards.*; -import mage.constants.*; -import mage.game.Game; -import mage.players.Player; +import mage.abilities.effects.common.continuous.GainRetraceYourGraveyardEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; + +import java.util.UUID; /** - * - * @author Grath + * @author Susucr */ public final class DeeprootHistorian extends CardImpl { + private static final FilterCard filter = new FilterCard("Merfolk and Druid cards"); + + static { + filter.add(Predicates.or(SubType.MERFOLK.getPredicate(), SubType.DRUID.getPredicate())); + } + public DeeprootHistorian(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); - + this.subtype.add(SubType.MERFOLK); this.subtype.add(SubType.DRUID); this.power = new MageInt(3); this.toughness = new MageInt(3); // Merfolk and Druid cards in your graveyard have retrace. - this.addAbility(new SimpleStaticAbility(new DeeprootHistorianEffect())); + this.addAbility(new SimpleStaticAbility( + new GainRetraceYourGraveyardEffect(StaticFilters.FILTER_CARD_INSTANT_AND_SORCERY) + )); } private DeeprootHistorian(final DeeprootHistorian card) { @@ -37,75 +46,4 @@ public final class DeeprootHistorian extends CardImpl { public DeeprootHistorian copy() { return new DeeprootHistorian(this); } -} - -class DeeprootHistorianEffect extends ContinuousEffectImpl { - - DeeprootHistorianEffect() { - super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - staticText = "Merfolk and Druid cards in your graveyard have retrace."; - } - - private DeeprootHistorianEffect(final DeeprootHistorianEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - for (UUID cardId : controller.getGraveyard()) { - Card card = game.getCard(cardId); - if (card == null) { - continue; - } - if (card instanceof SplitCard) { - SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard(); - SplitCardHalf rightHalfCard = ((SplitCard) card).getRightHalfCard(); - if (leftHalfCard.hasSubtype(SubType.MERFOLK, game) || leftHalfCard.hasSubtype(SubType.DRUID, game)) { - Ability ability = new RetraceAbility(leftHalfCard); - ability.setSourceId(cardId); - ability.setControllerId(leftHalfCard.getOwnerId()); - game.getState().addOtherAbility(leftHalfCard, ability); - } - if (rightHalfCard.hasSubtype(SubType.MERFOLK, game) || rightHalfCard.hasSubtype(SubType.DRUID, game)) { - Ability ability = new RetraceAbility(rightHalfCard); - ability.setSourceId(cardId); - ability.setControllerId(rightHalfCard.getOwnerId()); - game.getState().addOtherAbility(rightHalfCard, ability); - } - } - if (card instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); - if (leftHalfCard.hasSubtype(SubType.MERFOLK, game) || leftHalfCard.hasSubtype(SubType.DRUID, game)) { - Ability ability = new RetraceAbility(leftHalfCard); - ability.setSourceId(cardId); - ability.setControllerId(leftHalfCard.getOwnerId()); - game.getState().addOtherAbility(leftHalfCard, ability); - } - if (rightHalfCard.hasSubtype(SubType.MERFOLK, game) || rightHalfCard.hasSubtype(SubType.DRUID, game)) { - Ability ability = new RetraceAbility(rightHalfCard); - ability.setSourceId(cardId); - ability.setControllerId(rightHalfCard.getOwnerId()); - game.getState().addOtherAbility(rightHalfCard, ability); - } - } - if (!card.hasSubtype(SubType.MERFOLK, game) && !card.hasSubtype(SubType.DRUID, game)) { - continue; - } - Ability ability = new RetraceAbility(card); - ability.setSourceId(cardId); - ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card, ability); - } - return true; - } - - @Override - public DeeprootHistorianEffect copy() { - return new DeeprootHistorianEffect(this); - } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/Six.java b/Mage.Sets/src/mage/cards/s/Six.java new file mode 100644 index 00000000000..6d36b54e42e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Six.java @@ -0,0 +1,70 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.MillThenPutInHandEffect; +import mage.abilities.effects.common.continuous.GainRetraceYourGraveyardEffect; +import mage.abilities.hint.common.MyTurnHint; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class Six extends CardImpl { + + private static final FilterCard filter = new FilterPermanentCard("nonland permanent card"); + + static { + filter.add(Predicates.not(CardType.LAND.getPredicate())); + } + + public Six(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.TREEFOLK); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // Whenever Six attacks, mill three cards. You may put a land card from among them into your hand. + this.addAbility(new AttacksTriggeredAbility( + new MillThenPutInHandEffect(3, StaticFilters.FILTER_CARD_LAND_A) + .withTextOptions("them") + )); + + // As long as it's your turn, nonland permanent cards in your graveyard have retrace. + this.addAbility(new SimpleStaticAbility( + new ConditionalContinuousEffect( + new GainRetraceYourGraveyardEffect(filter), + MyTurnCondition.instance, + "As long as it's your turn, nonland permanent cards in your graveyard have retrace." + ) + ).addHint(MyTurnHint.instance)); + } + + private Six(final Six card) { + super(card); + } + + @Override + public Six copy() { + return new Six(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3.java b/Mage.Sets/src/mage/sets/ModernHorizons3.java index 79ebbcd490e..231c2aa2fc5 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons3.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons3.java @@ -73,6 +73,7 @@ public final class ModernHorizons3 extends ExpansionSet { cards.add(new SetCardInfo("Scurrilous Sentry", 108, Rarity.COMMON, mage.cards.s.ScurrilousSentry.class)); cards.add(new SetCardInfo("Scurry of Gremlins", 203, Rarity.UNCOMMON, mage.cards.s.ScurryOfGremlins.class)); cards.add(new SetCardInfo("Serum Visionary", 69, Rarity.COMMON, mage.cards.s.SerumVisionary.class)); + cards.add(new SetCardInfo("Six", 169, Rarity.RARE, mage.cards.s.Six.class)); cards.add(new SetCardInfo("Snow-Covered Wastes", 229, Rarity.UNCOMMON, mage.cards.s.SnowCoveredWastes.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spawn-Gang Commander", 140, Rarity.UNCOMMON, mage.cards.s.SpawnGangCommander.class)); cards.add(new SetCardInfo("Swamp", 306, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SixTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SixTest.java new file mode 100644 index 00000000000..7ba3dd45cf0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/SixTest.java @@ -0,0 +1,120 @@ +package org.mage.test.cards.single.mh3; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SixTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.Six Six} {2}{G} + * Legendary Creature — Treefolk + * Reach + * Whenever Six attacks, mill three cards. You may put a land card from among them into your hand. + * As long as it's your turn, nonland permanent cards in your graveyard have retrace. + * 2/4 + */ + private static final String six = "Six"; + + @Test + public void test_Retrace_Simple() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, six); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.GRAVEYARD, playerA, "Grizzly Bears"); + addCard(Zone.HAND, playerA, "Taiga"); + + checkPlayableAbility("has retrace", 1, PhaseStep.PRECOMBAT_MAIN, playerA, + "Cast Grizzly Bears with retrace", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears with retrace"); + setChoice(playerA, "Taiga"); // discard to cast with Retrace + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertTappedCount("Forest", true, 2); + assertPermanentCount(playerA, "Grizzly Bears", 1); + assertGraveyardCount(playerA, "Taiga", 1); + } + + @Test + public void test_Retrace_Flash_YourTurnOnly() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, six); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.GRAVEYARD, playerA, "Ambush Viper"); + addCard(Zone.HAND, playerA, "Taiga"); + + checkPlayableAbility("1: has retrace your turn", 1, PhaseStep.UPKEEP, playerA, + "Cast Ambush Viper with retrace", true); + checkPlayableAbility("2: hasn't retrace not your turn", 2, PhaseStep.UPKEEP, playerA, + "Cast Ambush Viper with retrace", false); + checkPlayableAbility("3: has retrace your turn", 3, PhaseStep.UPKEEP, playerA, + "Cast Ambush Viper with retrace", true); + + castSpell(3, PhaseStep.UPKEEP, playerA, "Ambush Viper with retrace"); + setChoice(playerA, "Taiga"); // discard to cast with Retrace + + setStopAt(3, PhaseStep.DRAW); + execute(); + + assertTappedCount("Forest", true, 2); + assertPermanentCount(playerA, "Ambush Viper", 1); + assertGraveyardCount(playerA, "Taiga", 1); + } + + @Test + public void test_Retrace_Adventure() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, six); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.GRAVEYARD, playerA, "Bonecrusher Giant"); + addCard(Zone.HAND, playerA, "Taiga"); + + checkPlayableAbility("Giant has retrace", 1, PhaseStep.PRECOMBAT_MAIN, playerA, + "Cast Bonecrusher Giant with retrace", true); + checkPlayableAbility("Stomp doesn't have retrace (not permanent)", 1, PhaseStep.PRECOMBAT_MAIN, playerA, + "Cast Stomp with retrace", false); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bonecrusher Giant with retrace"); + setChoice(playerA, "Taiga"); // discard to cast with Retrace + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertTappedCount("Mountain", true, 3); + assertPermanentCount(playerA, "Bonecrusher Giant", 1); + assertGraveyardCount(playerA, "Taiga", 1); + } + + @Test + public void test_Retrace_MDFC() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, six); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.GRAVEYARD, playerA, "Egon, God of Death"); // MDFC creature/artifact + addCard(Zone.HAND, playerA, "Taiga"); + + checkPlayableAbility("Egon, God of Death has retrace", 1, PhaseStep.PRECOMBAT_MAIN, playerA, + "Cast Egon, God of Death with retrace", true); + checkPlayableAbility("Throne of Death has retrace", 1, PhaseStep.PRECOMBAT_MAIN, playerA, + "Cast Throne of Death with retrace", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Throne of Death with retrace"); + setChoice(playerA, "Taiga"); // discard to cast with Retrace + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertTappedCount("Swamp", true, 1); + assertPermanentCount(playerA, "Throne of Death", 1); + assertGraveyardCount(playerA, "Taiga", 1); + } + +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainRetraceYourGraveyardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainRetraceYourGraveyardEffect.java new file mode 100644 index 00000000000..3fa301b9676 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainRetraceYourGraveyardEffect.java @@ -0,0 +1,67 @@ +package mage.abilities.effects.common.continuous; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.keyword.RetraceAbility; +import mage.cards.*; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * "[filter] cards in your graveyard have retrace." + * + * @author Susucr + */ +public class GainRetraceYourGraveyardEffect extends ContinuousEffectImpl { + + private final FilterCard filter; + + public GainRetraceYourGraveyardEffect(FilterCard filter) { + this(Duration.WhileOnBattlefield, filter); + } + + public GainRetraceYourGraveyardEffect(Duration duration, FilterCard filter) { + super(duration, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.filter = filter; + staticText = filter.getMessage() + " in your graveyard have retrace."; + } + + private GainRetraceYourGraveyardEffect(final GainRetraceYourGraveyardEffect effect) { + super(effect); + this.filter = effect.filter; + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + for (UUID cardId : controller.getGraveyard()) { + Card card = game.getCard(cardId); + if (card == null) { + continue; + } + for (Card faceCard : CardUtil.getCastableComponents(card, filter, source, controller, game, null, false)) { + Ability ability = new RetraceAbility(faceCard); + ability.setSourceId(cardId); + ability.setControllerId(faceCard.getOwnerId()); + game.getState().addOtherAbility(faceCard, ability); + } + } + return true; + } + + @Override + public GainRetraceYourGraveyardEffect copy() { + return new GainRetraceYourGraveyardEffect(this); + } +} diff --git a/Mage/src/main/java/mage/game/command/emblems/WrennAndSixEmblem.java b/Mage/src/main/java/mage/game/command/emblems/WrennAndSixEmblem.java index 186fcb963b0..3dd12d53e54 100644 --- a/Mage/src/main/java/mage/game/command/emblems/WrennAndSixEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/WrennAndSixEmblem.java @@ -1,16 +1,10 @@ package mage.game.command.emblems; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.keyword.RetraceAbility; -import mage.cards.*; -import mage.constants.*; -import mage.game.Game; +import mage.abilities.effects.common.continuous.GainRetraceYourGraveyardEffect; +import mage.constants.Zone; +import mage.filter.StaticFilters; import mage.game.command.Emblem; -import mage.players.Player; - -import java.util.UUID; /** * @author TheElk801 @@ -19,7 +13,10 @@ public final class WrennAndSixEmblem extends Emblem { public WrennAndSixEmblem() { super("Emblem Wrenn"); - this.getAbilities().add(new SimpleStaticAbility(Zone.COMMAND, new WrennAndSixEmblemEffect())); + // Instant and sorcery cards in your graveyard have retrace. + this.getAbilities().add(new SimpleStaticAbility( + Zone.COMMAND, new GainRetraceYourGraveyardEffect(StaticFilters.FILTER_CARD_INSTANT_AND_SORCERY) + )); } private WrennAndSixEmblem(final WrennAndSixEmblem card) { @@ -31,78 +28,3 @@ public final class WrennAndSixEmblem extends Emblem { return new WrennAndSixEmblem(this); } } - -class WrennAndSixEmblemEffect extends ContinuousEffectImpl { - - WrennAndSixEmblemEffect() { - super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - staticText = "Instant and sorcery cards in your graveyard have retrace."; - } - - private WrennAndSixEmblemEffect(final WrennAndSixEmblemEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - for (UUID cardId : controller.getGraveyard()) { - Card card = game.getCard(cardId); - if (card == null) { - continue; - } - if (card instanceof SplitCard) { - SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard(); - SplitCardHalf rightHalfCard = ((SplitCard) card).getRightHalfCard(); - if (leftHalfCard.isInstantOrSorcery(game)) { - Ability ability = new RetraceAbility(leftHalfCard); - ability.setSourceId(cardId); - ability.setControllerId(leftHalfCard.getOwnerId()); - game.getState().addOtherAbility(leftHalfCard, ability); - } - if (rightHalfCard.isInstantOrSorcery(game)) { - Ability ability = new RetraceAbility(rightHalfCard); - ability.setSourceId(cardId); - ability.setControllerId(rightHalfCard.getOwnerId()); - game.getState().addOtherAbility(rightHalfCard, ability); - } - } - if (card instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); - if (leftHalfCard.isInstantOrSorcery(game)) { - Ability ability = new RetraceAbility(leftHalfCard); - ability.setSourceId(cardId); - ability.setControllerId(leftHalfCard.getOwnerId()); - game.getState().addOtherAbility(leftHalfCard, ability); - } - if (rightHalfCard.isInstantOrSorcery(game)) { - Ability ability = new RetraceAbility(rightHalfCard); - ability.setSourceId(cardId); - ability.setControllerId(rightHalfCard.getOwnerId()); - game.getState().addOtherAbility(rightHalfCard, ability); - } - } - if (card instanceof AdventureCard) { - // Adventure cards are castable per https://twitter.com/elishffrn/status/1179047911729946624 - card = ((AdventureCard) card).getSpellCard(); - } - if (!card.isInstantOrSorcery(game)) { - continue; - } - Ability ability = new RetraceAbility(card); - ability.setSourceId(cardId); - ability.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card, ability); - } - return true; - } - - @Override - public WrennAndSixEmblemEffect copy() { - return new WrennAndSixEmblemEffect(this); - } -}