implement [MH3] Six

This commit is contained in:
Susucre 2024-05-22 20:24:33 +02:00
parent 725101c026
commit bab302f653
6 changed files with 286 additions and 168 deletions

View file

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

View file

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

View file

@ -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));

View file

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

View file

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

View file

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