diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index 230c4e91ab7..2a26bbd9380 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -940,8 +940,13 @@ public abstract class CardPanel extends MagePermanent implements ComponentListen private void setGameCardSides(CardView gameCard) { if (this.cardSideMain == null) { // new card - this.cardSideMain = gameCard; - this.cardSideOther = gameCard.getSecondCardFace(); + if (gameCard instanceof PermanentView) { + this.cardSideMain = gameCard; + this.cardSideOther = gameCard.isTransformed() ? ((PermanentView) gameCard).getOriginal() : gameCard.getSecondCardFace(); + } else { + this.cardSideMain = gameCard; + this.cardSideOther = gameCard.getSecondCardFace(); + } } else { // updated card if (this.cardSideMain.getName().equals(gameCard.getName())) { diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java index 704395e0378..d81d5e797ae 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java @@ -558,12 +558,12 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements ); allCardsUrls.add(url); } - if (card.isModalDoubleFacedCard()) { - if (card.getModalDoubleFacedSecondSideName() == null || card.getModalDoubleFacedSecondSideName().trim().isEmpty()) { + if (card.isDoubleFacedCard()) { + if (card.getDoubleFacedSecondSideName() == null || card.getDoubleFacedSecondSideName().trim().isEmpty()) { throw new IllegalStateException("MDF card can't have empty name."); } CardDownloadData cardDownloadData = new CardDownloadData( - card.getModalDoubleFacedSecondSideName(), + card.getDoubleFacedSecondSideName(), card.getSetCode(), card.getCardNumber(), card.usesVariousArt(), diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index a1a41e0806a..4892830edbb 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -33,6 +33,7 @@ import mage.game.command.Dungeon; import mage.game.command.Emblem; import mage.game.command.Plane; import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; import mage.game.permanent.PermanentToken; import mage.game.permanent.token.Token; import mage.game.stack.Spell; @@ -116,7 +117,7 @@ public class CardView extends SimpleCardView { protected List rightSplitRules; protected String rightSplitTypeLine; - protected boolean isModalDoubleFacedCard; + protected boolean isDoubleFacedCard; protected ArtRect artRect = ArtRect.NORMAL; @@ -238,7 +239,7 @@ public class CardView extends SimpleCardView { this.rightSplitRules = cardView.rightSplitRules == null ? null : new ArrayList<>(cardView.rightSplitRules); this.rightSplitTypeLine = cardView.rightSplitTypeLine; - this.isModalDoubleFacedCard = cardView.isModalDoubleFacedCard; + this.isDoubleFacedCard = cardView.isDoubleFacedCard; this.artRect = cardView.artRect; this.targets = cardView.targets == null ? null : new ArrayList<>(cardView.targets); @@ -428,12 +429,18 @@ public class CardView extends SimpleCardView { this.manaCostLeftStr = splitCard.getLeftHalfCard().getManaCostSymbols(); this.manaCostRightStr = splitCard.getRightHalfCard().getManaCostSymbols(); } else if (card instanceof ModalDoubleFacedCard) { - this.isModalDoubleFacedCard = true; - ModalDoubleFacedCard mainCard = ((ModalDoubleFacedCard) card); + this.isDoubleFacedCard = true; + DoubleFacedCard mainCard = ((DoubleFacedCard) card); fullCardName = mainCard.getLeftHalfCard().getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + mainCard.getRightHalfCard().getName(); this.manaCostLeftStr = mainCard.getLeftHalfCard().getManaCostSymbols(); this.manaCostRightStr = mainCard.getRightHalfCard().getManaCostSymbols(); - } else if (card instanceof CardWithSpellOption) { + } else if (card instanceof TransformingDoubleFacedCard) { + this.isDoubleFacedCard = true; + DoubleFacedCard mainCard = ((DoubleFacedCard) card); + fullCardName = mainCard.getLeftHalfCard().getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + mainCard.getRightHalfCard().getName(); + this.manaCostLeftStr = mainCard.getLeftHalfCard().getManaCostSymbols(); + this.manaCostRightStr = new ArrayList<>(); + } else if (card instanceof CardWithSpellOption) { this.isSplitCard = true; CardWithSpellOption mainCard = ((CardWithSpellOption) card); leftSplitName = mainCard.getName(); @@ -531,13 +538,21 @@ public class CardView extends SimpleCardView { this.extraDeckCard = card.isExtraDeckCard(); + // TODO: can probably remove this after tdfc rework // transformable, double faces cards - this.transformable = card.isTransformable(); + if (!(sourceCard.getMainCard() instanceof DoubleFacedCard)) { + this.transformable = card.isTransformable(); - Card secondSideCard = card.getSecondCardFace(); - if (secondSideCard != null) { + Card secondSideCard = card.getSecondCardFace(); + if (secondSideCard != null) { + this.secondCardFace = new CardView(secondSideCard, game); + this.alternateName = secondCardFace.getName(); + } + } else if (card instanceof PermanentCard && card.isTransformable()) { + this.transformable = card.isTransformable(); + Card secondSideCard = (Card) ((PermanentCard) card).getOtherFace(); this.secondCardFace = new CardView(secondSideCard, game); - this.alternateName = secondCardFace.getName(); + this.alternateName = secondSideCard.getName(); } this.flipCard = card.isFlipCard(); @@ -545,11 +560,11 @@ public class CardView extends SimpleCardView { this.alternateName = card.getFlipCardName(); } - if (card instanceof ModalDoubleFacedCard) { + if (card instanceof DoubleFacedCard) { this.transformable = true; // enable GUI day/night button - ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card; - this.secondCardFace = new CardView(mdfCard.getRightHalfCard(), game); - this.alternateName = mdfCard.getRightHalfCard().getName(); + DoubleFacedCard doubleFacedCard = (DoubleFacedCard) card; + this.secondCardFace = new CardView(doubleFacedCard.getRightHalfCard(), game); + this.alternateName = doubleFacedCard.getRightHalfCard().getName(); } Card meldsToCard = card.getMeldsToCard(); diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/TinyLeaders.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/TinyLeaders.java index aaaa45006e8..c2c364d9cb5 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/TinyLeaders.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/TinyLeaders.java @@ -206,9 +206,9 @@ public class TinyLeaders extends Constructed { if (card instanceof SplitCard) { costs.add(((SplitCard) card).getLeftHalfCard().getManaValue()); costs.add(((SplitCard) card).getRightHalfCard().getManaValue()); - } else if (card instanceof ModalDoubleFacedCard) { - costs.add(((ModalDoubleFacedCard) card).getLeftHalfCard().getManaValue()); - costs.add(((ModalDoubleFacedCard) card).getRightHalfCard().getManaValue()); + } else if (card instanceof DoubleFacedCard) { + costs.add(((DoubleFacedCard) card).getLeftHalfCard().getManaValue()); + costs.add(((DoubleFacedCard) card).getRightHalfCard().getManaValue()); } else { costs.add(card.getManaValue()); } diff --git a/Mage.Sets/src/mage/cards/a/ArchangelAvacyn.java b/Mage.Sets/src/mage/cards/a/ArchangelAvacyn.java index 6f4b2eb2aa9..67f36d8526b 100644 --- a/Mage.Sets/src/mage/cards/a/ArchangelAvacyn.java +++ b/Mage.Sets/src/mage/cards/a/ArchangelAvacyn.java @@ -1,66 +1,83 @@ package mage.cards.a; -import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.DiesCreatureTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.TransformIntoSourceTriggeredAbility; import mage.abilities.common.delayed.AtTheBeginOfNextUpkeepDelayedTriggeredAbility; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DamageAllEffect; +import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.effects.common.TransformSourceEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; import mage.abilities.keyword.*; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; import mage.constants.*; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; import java.util.UUID; /** * @author fireshoes */ -public final class ArchangelAvacyn extends CardImpl { +public final class ArchangelAvacyn extends TransformingDoubleFacedCard { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a non-Angel creature you control"); + private static final FilterCreaturePermanent nonAngelFilter = new FilterCreaturePermanent("a non-Angel creature you control"); + private static final FilterPermanent otherCreatureFilter = new FilterCreaturePermanent("other creature"); static { - filter.add(Predicates.not(SubType.ANGEL.getPredicate())); - filter.add(TargetController.YOU.getControllerPredicate()); + otherCreatureFilter.add(AnotherPredicate.instance); + nonAngelFilter.add(Predicates.not(SubType.ANGEL.getPredicate())); + nonAngelFilter.add(TargetController.YOU.getControllerPredicate()); } public ArchangelAvacyn(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); + super(ownerId, setInfo, + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.ANGEL}, "{3}{W}{W}", + "Avacyn, the Purifier", + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.ANGEL}, "R"); - this.supertype.add(SuperType.LEGENDARY); - this.subtype.add(SubType.ANGEL); - this.power = new MageInt(4); - this.toughness = new MageInt(4); - - this.secondSideCardClazz = mage.cards.a.AvacynThePurifier.class; + this.getLeftHalfCard().setPT(4, 4); + this.getRightHalfCard().setPT(6, 5); // Flash this.addAbility(FlashAbility.getInstance()); // Flying - this.addAbility(FlyingAbility.getInstance()); + this.getLeftHalfCard().addAbility(FlyingAbility.getInstance()); // Vigilance - this.addAbility(VigilanceAbility.getInstance()); + this.getLeftHalfCard().addAbility(VigilanceAbility.getInstance()); // When Archangel Avacyn enters the battlefield, creatures you control gain indestructible until end of turn. - this.addAbility(new EntersBattlefieldTriggeredAbility(new GainAbilityControlledEffect( + this.getLeftHalfCard().addAbility(new EntersBattlefieldTriggeredAbility(new GainAbilityControlledEffect( IndestructibleAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES ), false)); // When a non-Angel creature you control dies, transform Archangel Avacyn at the beginning of the next upkeep. - this.addAbility(new TransformAbility()); - this.addAbility(new DiesCreatureTriggeredAbility( + this.getLeftHalfCard().addAbility(new DiesCreatureTriggeredAbility( new CreateDelayedTriggeredAbilityEffect( new AtTheBeginOfNextUpkeepDelayedTriggeredAbility(new TransformSourceEffect()) - ).setText("transform {this} at the beginning of the next upkeep"), false, filter + ).setText("transform {this} at the beginning of the next upkeep"), false, nonAngelFilter ).setTriggerPhrase("When a non-Angel creature you control dies, ")); + + // Avacyn, the Purifier + + // Flying + this.getRightHalfCard().addAbility(FlyingAbility.getInstance()); + + // When this creature transforms into Avacyn, the Purifier, it deals 3 damage to each other creature and each opponent. + Ability ability = new TransformIntoSourceTriggeredAbility( + new DamageAllEffect(3, "it", otherCreatureFilter) + ); + ability.addEffect(new DamagePlayersEffect(3, TargetController.OPPONENT).setText("and each opponent")); + this.getRightHalfCard().addAbility(ability); } private ArchangelAvacyn(final ArchangelAvacyn card) { diff --git a/Mage.Sets/src/mage/cards/a/AvacynThePurifier.java b/Mage.Sets/src/mage/cards/a/AvacynThePurifier.java deleted file mode 100644 index fc2d87749c9..00000000000 --- a/Mage.Sets/src/mage/cards/a/AvacynThePurifier.java +++ /dev/null @@ -1,62 +0,0 @@ -package mage.cards.a; - -import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.TransformIntoSourceTriggeredAbility; -import mage.abilities.effects.common.DamageAllEffect; -import mage.abilities.effects.common.DamagePlayersEffect; -import mage.abilities.keyword.FlyingAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.constants.TargetController; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.mageobject.AnotherPredicate; - -import java.util.UUID; - -/** - * @author fireshoes - */ -public final class AvacynThePurifier extends CardImpl { - - private static final FilterPermanent filter = new FilterCreaturePermanent("other creature"); - - static { - filter.add(AnotherPredicate.instance); - } - - public AvacynThePurifier(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); - this.supertype.add(SuperType.LEGENDARY); - this.subtype.add(SubType.ANGEL); - this.power = new MageInt(6); - this.toughness = new MageInt(5); - this.color.setRed(true); - - // this card is the second face of double-faced card - this.nightCard = true; - - // Flying - this.addAbility(FlyingAbility.getInstance()); - - // When this creature transforms into Avacyn, the Purifier, it deals 3 damage to each other creature and each opponent. - Ability ability = new TransformIntoSourceTriggeredAbility( - new DamageAllEffect(3, "it", filter) - ); - ability.addEffect(new DamagePlayersEffect(3, TargetController.OPPONENT).setText("and each opponent")); - this.addAbility(ability); - } - - private AvacynThePurifier(final AvacynThePurifier card) { - super(card); - } - - @Override - public AvacynThePurifier copy() { - return new AvacynThePurifier(this); - } -} diff --git a/Mage.Sets/src/mage/cards/a/AzusasManyJourneys.java b/Mage.Sets/src/mage/cards/a/AzusasManyJourneys.java index b62d3daf6f5..1f240eb0840 100644 --- a/Mage.Sets/src/mage/cards/a/AzusasManyJourneys.java +++ b/Mage.Sets/src/mage/cards/a/AzusasManyJourneys.java @@ -1,45 +1,51 @@ package mage.cards.a; -import java.util.UUID; - +import mage.abilities.common.BecomesBlockedSourceTriggeredAbility; import mage.abilities.common.SagaAbility; import mage.abilities.effects.common.ExileSagaAndReturnTransformedEffect; import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.UntapLandsEffect; import mage.abilities.effects.common.continuous.PlayAdditionalLandsControllerEffect; -import mage.abilities.keyword.TransformAbility; +import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; +import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SagaChapter; import mage.constants.SubType; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; + +import java.util.UUID; /** * * @author weirddan455 */ -public final class AzusasManyJourneys extends CardImpl { +public final class AzusasManyJourneys extends TransformingDoubleFacedCard { public AzusasManyJourneys(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}"); + super(ownerId, setInfo, + new CardType[]{CardType.ENCHANTMENT}, new SubType[]{SubType.SAGA}, "{1}{G}", + "Likeness of the Seeker", + new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, new SubType[]{SubType.HUMAN, SubType.MONK}, "G"); - this.subtype.add(SubType.SAGA); - this.secondSideCardClazz = mage.cards.l.LikenessOfTheSeeker.class; + this.getRightHalfCard().setPT(3, 3); // (As this Saga enters and after your draw step, add a lore counter.) - SagaAbility sagaAbility = new SagaAbility(this); + SagaAbility sagaAbility = new SagaAbility(this.getLeftHalfCard()); // I — You may play an additional land this turn. - sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, new PlayAdditionalLandsControllerEffect(1, Duration.EndOfTurn)); + sagaAbility.addChapterEffect(this.getLeftHalfCard(), SagaChapter.CHAPTER_I, new PlayAdditionalLandsControllerEffect(1, Duration.EndOfTurn)); // II — You gain 3 life. - sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, new GainLifeEffect(3)); + sagaAbility.addChapterEffect(this.getLeftHalfCard(), SagaChapter.CHAPTER_II, new GainLifeEffect(3)); // III — Exile this Saga, then return it to the battlefield transformed under your control. - this.addAbility(new TransformAbility()); - sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new ExileSagaAndReturnTransformedEffect()); + sagaAbility.addChapterEffect(this.getLeftHalfCard(), SagaChapter.CHAPTER_III, new ExileSagaAndReturnTransformedEffect()); - this.addAbility(sagaAbility); + this.getLeftHalfCard().addAbility(sagaAbility); + + // Likeness of the Seeker + // Whenever Likeness of the Seeker becomes blocked, untap up to three lands you control. + this.getRightHalfCard().addAbility(new BecomesBlockedSourceTriggeredAbility(new UntapLandsEffect(3, true, true), false)); } private AzusasManyJourneys(final AzusasManyJourneys card) { diff --git a/Mage.Sets/src/mage/cards/b/BaithookAngler.java b/Mage.Sets/src/mage/cards/b/BaithookAngler.java index 1c3126ea98f..792fa6b9075 100644 --- a/Mage.Sets/src/mage/cards/b/BaithookAngler.java +++ b/Mage.Sets/src/mage/cards/b/BaithookAngler.java @@ -1,10 +1,9 @@ package mage.cards.b; -import mage.MageInt; -import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.keyword.DisturbAbility; -import mage.cards.CardImpl; +import mage.abilities.keyword.FlyingAbility; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; import mage.constants.CardType; import mage.constants.SubType; @@ -13,19 +12,25 @@ import java.util.UUID; /** * @author TheElk801 */ -public final class BaithookAngler extends CardImpl { +public final class BaithookAngler extends TransformingDoubleFacedCard { public BaithookAngler(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); - - this.subtype.add(SubType.HUMAN); - this.subtype.add(SubType.PEASANT); - this.power = new MageInt(2); - this.toughness = new MageInt(1); - this.secondSideCardClazz = mage.cards.h.HookHauntDrifter.class; + super(ownerId, setInfo, + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HUMAN, SubType.PEASANT}, "{1}{U}", + "Hook-Haunt Drifter", + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.SPIRIT}, "U" + ); + this.getLeftHalfCard().setPT(2, 1); + this.getRightHalfCard().setPT(1, 2); // Disturb {1}{U} - this.addAbility(new DisturbAbility(this, "{1}{U}")); + this.getLeftHalfCard().addAbility(new DisturbAbility(this, "{1}{U}")); + + // Flying + this.getRightHalfCard().addAbility(FlyingAbility.getInstance()); + + // If Hook-Haunt Drifter would be put into a graveyard from anywhere, exile it instead. + this.getRightHalfCard().addAbility(DisturbAbility.makeBackAbility()); } private BaithookAngler(final BaithookAngler card) { diff --git a/Mage.Sets/src/mage/cards/b/BelenonWarAnthem.java b/Mage.Sets/src/mage/cards/b/BelenonWarAnthem.java deleted file mode 100644 index 432533a985b..00000000000 --- a/Mage.Sets/src/mage/cards/b/BelenonWarAnthem.java +++ /dev/null @@ -1,35 +0,0 @@ -package mage.cards.b; - -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.continuous.BoostControlledEffect; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; - -import java.util.UUID; - -/** - * @author TheElk801 - */ -public final class BelenonWarAnthem extends CardImpl { - - public BelenonWarAnthem(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, ""); - - this.color.setWhite(true); - this.nightCard = true; - - // Creatures you control get +1/+1. - this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield))); - } - - private BelenonWarAnthem(final BelenonWarAnthem card) { - super(card); - } - - @Override - public BelenonWarAnthem copy() { - return new BelenonWarAnthem(this); - } -} diff --git a/Mage.Sets/src/mage/cards/b/BladewheelChariot.java b/Mage.Sets/src/mage/cards/b/BladewheelChariot.java deleted file mode 100644 index aa6b22cf8aa..00000000000 --- a/Mage.Sets/src/mage/cards/b/BladewheelChariot.java +++ /dev/null @@ -1,60 +0,0 @@ -package mage.cards.b; - -import mage.MageInt; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.common.TapTargetCost; -import mage.abilities.effects.common.continuous.AddCardTypeSourceEffect; -import mage.abilities.keyword.CrewAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.SubType; -import mage.filter.common.FilterControlledArtifactPermanent; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.mageobject.AnotherPredicate; -import mage.filter.predicate.permanent.TappedPredicate; -import mage.target.common.TargetControlledPermanent; - -import java.util.UUID; - -/** - * @author TheElk801 - */ -public final class BladewheelChariot extends CardImpl { - - private static final FilterControlledPermanent filter - = new FilterControlledArtifactPermanent("other untapped artifacts you control"); - - static { - filter.add(AnotherPredicate.instance); - filter.add(TappedPredicate.UNTAPPED); - } - - public BladewheelChariot(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, ""); - - this.subtype.add(SubType.VEHICLE); - this.power = new MageInt(5); - this.toughness = new MageInt(5); - this.nightCard = true; - this.color.setWhite(true); - - // Tap two other untapped artifacts you control: Bladewheel Chariot becomes an artifact creature until end of turn. - this.addAbility(new SimpleActivatedAbility(new AddCardTypeSourceEffect( - Duration.EndOfTurn, CardType.ARTIFACT, CardType.CREATURE - ).setText("{this} becomes an artifact creature until end of turn"), new TapTargetCost(new TargetControlledPermanent(2, filter)))); - - // Crew 1 - this.addAbility(new CrewAbility(1)); - } - - private BladewheelChariot(final BladewheelChariot card) { - super(card); - } - - @Override - public BladewheelChariot copy() { - return new BladewheelChariot(this); - } -} diff --git a/Mage.Sets/src/mage/cards/b/BottomlessPoolLockerRoom.java b/Mage.Sets/src/mage/cards/b/BottomlessPoolLockerRoom.java index 7ad36a46cf3..24bbd2026fa 100644 --- a/Mage.Sets/src/mage/cards/b/BottomlessPoolLockerRoom.java +++ b/Mage.Sets/src/mage/cards/b/BottomlessPoolLockerRoom.java @@ -1,21 +1,17 @@ package mage.cards.b; -import java.util.UUID; - import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; import mage.abilities.common.UnlockThisDoorTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.cards.CardSetInfo; import mage.cards.RoomCard; -import mage.constants.CardType; -import mage.constants.SetTargetPointer; -import mage.constants.SpellAbilityType; -import mage.constants.SubType; -import mage.constants.TargetController; +import mage.constants.*; import mage.filter.StaticFilters; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * @author oscscull */ @@ -38,14 +34,14 @@ public final class BottomlessPoolLockerRoom extends RoomCard { UnlockThisDoorTriggeredAbility left = new UnlockThisDoorTriggeredAbility( new ReturnToHandTargetEffect(), false, true); left.addTarget(new TargetCreaturePermanent(0, 1)); + this.getLeftHalfCard().addAbility(left); // Right half ability - "Whenever one or more creatures you control deal combat damage to a player, draw a card." DealsDamageToAPlayerAllTriggeredAbility right = new DealsDamageToAPlayerAllTriggeredAbility( new DrawCardSourceControllerEffect(1), StaticFilters.FILTER_CONTROLLED_A_CREATURE, false, SetTargetPointer.PLAYER, true, true, TargetController.OPPONENT); - - this.addRoomAbilities(left, right); + this.getRightHalfCard().addAbility(right); } private BottomlessPoolLockerRoom(final BottomlessPoolLockerRoom card) { diff --git a/Mage.Sets/src/mage/cards/d/DazzlingTheaterPropRoom.java b/Mage.Sets/src/mage/cards/d/DazzlingTheaterPropRoom.java index c7d407fd916..46d4dc6c385 100644 --- a/Mage.Sets/src/mage/cards/d/DazzlingTheaterPropRoom.java +++ b/Mage.Sets/src/mage/cards/d/DazzlingTheaterPropRoom.java @@ -35,11 +35,11 @@ public final class DazzlingTheaterPropRoom extends RoomCard { // Dazzling Theater: Creature spells you cast have convoke. Ability left = new SimpleStaticAbility(new GainAbilityControlledSpellsEffect(new ConvokeAbility(), filter)); + this.getLeftHalfCard().addAbility(left); // Prop Room: Untap each creature you control during each other player's untap step. Ability right = new SimpleStaticAbility(new UntapAllDuringEachOtherPlayersUntapStepEffect(StaticFilters.FILTER_CONTROLLED_CREATURES)); - - this.addRoomAbilities(left, right); + this.getRightHalfCard().addAbility(right); } private DazzlingTheaterPropRoom(final DazzlingTheaterPropRoom card) { diff --git a/Mage.Sets/src/mage/cards/d/DollmakersShopPorcelainGallery.java b/Mage.Sets/src/mage/cards/d/DollmakersShopPorcelainGallery.java index 2ca7b73a5cc..1566406118a 100644 --- a/Mage.Sets/src/mage/cards/d/DollmakersShopPorcelainGallery.java +++ b/Mage.Sets/src/mage/cards/d/DollmakersShopPorcelainGallery.java @@ -34,13 +34,14 @@ public final class DollmakersShopPorcelainGallery extends RoomCard { // Dollmaker's Shop: Whenever one or more non-Toy creatures you control attack a player, create a 1/1 white Toy artifact creature token. Ability left = new AttacksPlayerWithCreaturesTriggeredAbility(new CreateTokenEffect(new ToyToken()), filter, SetTargetPointer.NONE); + this.getLeftHalfCard().addAbility(left); // Porcelain Gallery: Creatures you control have base power and toughness each equal to the number of creatures you control. Ability right = new SimpleStaticAbility(new SetBasePowerToughnessAllEffect( CreaturesYouControlCount.PLURAL, Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURES ).setText("Creatures you control have base power and toughness each equal to the number of creatures you control")); - - this.addRoomAbilities(left, right.addHint(new ValueHint("Creatures you control", CreaturesYouControlCount.PLURAL))); + right.addHint(new ValueHint("Creatures you control", CreaturesYouControlCount.PLURAL)); + this.getRightHalfCard().addAbility(right); } private DollmakersShopPorcelainGallery (final DollmakersShopPorcelainGallery card) { diff --git a/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java b/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java index 59db469c383..4d30803171c 100644 --- a/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java +++ b/Mage.Sets/src/mage/cards/d/DraugrNecromancer.java @@ -182,8 +182,8 @@ class DraugrNecromancerSpendAnyManaEffect extends AsThoughEffectImpl implements cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); } else if (card instanceof CardWithSpellOption) { cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); - } else if (card instanceof ModalDoubleFacedCard) { - cardState = game.getLastKnownInformationCard(((ModalDoubleFacedCard) card).getLeftHalfCard().getId(), Zone.EXILED); + } else if (card instanceof DoubleFacedCard) { + cardState = game.getLastKnownInformationCard(((DoubleFacedCard) card).getLeftHalfCard().getId(), Zone.EXILED); } else { cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); } diff --git a/Mage.Sets/src/mage/cards/e/EddieBrock.java b/Mage.Sets/src/mage/cards/e/EddieBrock.java index cc84afc2c29..feb1f69ff93 100644 --- a/Mage.Sets/src/mage/cards/e/EddieBrock.java +++ b/Mage.Sets/src/mage/cards/e/EddieBrock.java @@ -47,7 +47,7 @@ public final class EddieBrock extends ModalDoubleFacedCard { "Venom, Lethal Protector", new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.SYMBIOTE, SubType.HERO, SubType.VILLAIN}, "{3}{B}{R}{G}" ); - this.getLeftHalfCard().setPT(5, 5); + this.getLeftHalfCard().setPT(3, 3); this.getRightHalfCard().setPT(5, 5); // When Eddie Brock enters, return target creature card with mana value 1 or less from your graveyard to the battlefield. diff --git a/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java b/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java index 5f098d88de5..bb54bf9003e 100644 --- a/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java +++ b/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java @@ -174,8 +174,8 @@ class EvelynTheCovetousManaEffect extends AsThoughEffectImpl implements AsThough && EvelynTheCovetousWatcher.checkExile(affectedControllerId, card, game, 0); } CardState cardState; - if (card instanceof ModalDoubleFacedCard) { - cardState = game.getLastKnownInformationCard(((ModalDoubleFacedCard) card).getLeftHalfCard().getId(), Zone.EXILED); + if (card instanceof DoubleFacedCard) { + cardState = game.getLastKnownInformationCard(((DoubleFacedCard) card).getLeftHalfCard().getId(), Zone.EXILED); } else { cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); } diff --git a/Mage.Sets/src/mage/cards/f/FaithboundJudge.java b/Mage.Sets/src/mage/cards/f/FaithboundJudge.java index 0098ae14971..1519ccd30ca 100644 --- a/Mage.Sets/src/mage/cards/f/FaithboundJudge.java +++ b/Mage.Sets/src/mage/cards/f/FaithboundJudge.java @@ -1,67 +1,83 @@ package mage.cards.f; -import mage.MageInt; +import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.combat.CanAttackAsThoughItDidntHaveDefenderSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; -import mage.abilities.keyword.DefenderAbility; -import mage.abilities.keyword.DisturbAbility; -import mage.abilities.keyword.FlyingAbility; -import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.keyword.*; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.Duration; -import mage.constants.SubType; +import mage.cards.TransformingDoubleFacedCard; +import mage.constants.*; import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPlayer; +import java.util.Optional; import java.util.UUID; /** * @author TheElk801 */ -public final class FaithboundJudge extends CardImpl { +public final class FaithboundJudge extends TransformingDoubleFacedCard { private static final Condition condition1 = new SourceHasCounterCondition(CounterType.JUDGMENT, ComparisonType.OR_LESS, 2); private static final Condition condition2 = new SourceHasCounterCondition(CounterType.JUDGMENT, 3); public FaithboundJudge(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{W}"); + super(ownerId, setInfo, + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.SPIRIT, SubType.SOLDIER}, "{1}{W}{W}", + "Sinner's Judgment", + new CardType[]{CardType.ENCHANTMENT}, new SubType[]{SubType.AURA, SubType.CURSE}, "W"); - this.subtype.add(SubType.SPIRIT); - this.subtype.add(SubType.SOLDIER); - this.power = new MageInt(4); - this.toughness = new MageInt(4); - this.secondSideCardClazz = mage.cards.s.SinnersJudgment.class; + this.getLeftHalfCard().setPT(4, 4); // Defender - this.addAbility(DefenderAbility.getInstance()); + this.getLeftHalfCard().addAbility(DefenderAbility.getInstance()); // Flying - this.addAbility(FlyingAbility.getInstance()); + this.getLeftHalfCard().addAbility(FlyingAbility.getInstance()); // Vigilance - this.addAbility(VigilanceAbility.getInstance()); + this.getLeftHalfCard().addAbility(VigilanceAbility.getInstance()); // At the beginning of your upkeep, if Faithbound Judge has two or fewer judgment counters on it, put a judgment counter on it. - this.addAbility(new BeginningOfUpkeepTriggeredAbility( + this.getLeftHalfCard().addAbility(new BeginningOfUpkeepTriggeredAbility( new AddCountersSourceEffect(CounterType.JUDGMENT.createInstance()) .setText("put a judgment counter on it"), false ).withInterveningIf(condition1)); // As long as Faithbound Judge has three or more judgment counters on it, it can attack as though it didn't have defender. - this.addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect( + this.getLeftHalfCard().addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect( new CanAttackAsThoughItDidntHaveDefenderSourceEffect(Duration.WhileOnBattlefield), condition2 ).setText("as long as {this} has three or more judgment counters on it, " + "it can attack as though it didn't have defender"))); - // Disturb {5}{W}{W} - this.addAbility(new DisturbAbility(this, "{5}{W}{W}")); + // Sinner's Judgement + + // Enchant player + TargetPlayer auraTarget = new TargetPlayer(); + this.getRightHalfCard().getSpellAbility().addTarget(auraTarget); + this.getRightHalfCard().getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.getRightHalfCard().addAbility(new EnchantAbility(auraTarget)); + + // Faithbound Judge - Disturb {5}{W}{W} + // needs target from right half spell ability + this.getLeftHalfCard().addAbility(new DisturbAbility(this, "{5}{W}{W}")); + + // At the beginning of your upkeep, put a judgment counter on Sinner's Judgment. Then if there are three or more judgment counters on it, enchanted player loses the game. + Ability ability = new BeginningOfUpkeepTriggeredAbility(new AddCountersSourceEffect(CounterType.JUDGMENT.createInstance())); + ability.addEffect(new SinnersJudgmentEffect()); + this.getRightHalfCard().addAbility(ability); + + // If Sinner's Judgment would be put into a graveyard from anywhere, exile it instead. + this.getRightHalfCard().addAbility(DisturbAbility.makeBackAbility()); } private FaithboundJudge(final FaithboundJudge card) { @@ -73,3 +89,34 @@ public final class FaithboundJudge extends CardImpl { return new FaithboundJudge(this); } } + +class SinnersJudgmentEffect extends OneShotEffect { + + SinnersJudgmentEffect() { + super(Outcome.Benefit); + staticText = "Then if there are three or more judgment counters on it, enchanted player loses the game"; + } + + private SinnersJudgmentEffect(final SinnersJudgmentEffect effect) { + super(effect); + } + + @Override + public SinnersJudgmentEffect copy() { + return new SinnersJudgmentEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .filter(permanent -> permanent.getCounters(game).getCount(CounterType.JUDGMENT) >= 3) + .map(Permanent::getAttachedTo) + .map(game::getPlayer) + .filter(player -> { + player.lost(game); + return true; + }) + .isPresent(); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FuneralRoomAwakeningHall.java b/Mage.Sets/src/mage/cards/f/FuneralRoomAwakeningHall.java index b85c3d4b205..a10d8b8f31e 100644 --- a/Mage.Sets/src/mage/cards/f/FuneralRoomAwakeningHall.java +++ b/Mage.Sets/src/mage/cards/f/FuneralRoomAwakeningHall.java @@ -30,13 +30,13 @@ public final class FuneralRoomAwakeningHall extends RoomCard { StaticFilters.FILTER_CONTROLLED_A_CREATURE ); left.addEffect(new GainLifeEffect(1).concatBy("and")); + this.getLeftHalfCard().addAbility(left); // Awakening Hall: When you unlock this door, return all creature cards from your graveyard to the battlefield. Ability right = new UnlockThisDoorTriggeredAbility( new ReturnFromYourGraveyardToBattlefieldAllEffect(StaticFilters.FILTER_CARD_CREATURES), false, false ); - - this.addRoomAbilities(left, right); + this.getRightHalfCard().addAbility(right); } private FuneralRoomAwakeningHall(final FuneralRoomAwakeningHall card) { diff --git a/Mage.Sets/src/mage/cards/g/Galedrifter.java b/Mage.Sets/src/mage/cards/g/Galedrifter.java index c76b1198447..e8ac03ad82b 100644 --- a/Mage.Sets/src/mage/cards/g/Galedrifter.java +++ b/Mage.Sets/src/mage/cards/g/Galedrifter.java @@ -1,11 +1,9 @@ package mage.cards.g; -import mage.MageInt; -import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.keyword.DisturbAbility; import mage.abilities.keyword.FlyingAbility; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; import mage.constants.CardType; import mage.constants.SubType; @@ -14,21 +12,29 @@ import java.util.UUID; /** * @author TheElk801 */ -public final class Galedrifter extends CardImpl { +public final class Galedrifter extends TransformingDoubleFacedCard { public Galedrifter(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + super(ownerId, setInfo, + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HIPPOGRIFF}, "{3}{U}", + "Waildrifter", + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HIPPOGRIFF, SubType.SPIRIT}, "U"); - this.subtype.add(SubType.HIPPOGRIFF); - this.power = new MageInt(3); - this.toughness = new MageInt(2); - this.secondSideCardClazz = mage.cards.w.Waildrifter.class; + this.getLeftHalfCard().setPT(3, 2); + this.getRightHalfCard().setPT(2, 2); // Flying - this.addAbility(FlyingAbility.getInstance()); + this.getLeftHalfCard().addAbility(FlyingAbility.getInstance()); // Disturb {4}{U} - this.addAbility(new DisturbAbility(this, "{4}{U}")); + this.getLeftHalfCard().addAbility(new DisturbAbility(this, "{4}{U}")); + + // Waildrifter + // Flying + this.getRightHalfCard().addAbility(FlyingAbility.getInstance()); + + // If Waildrifter would be put into a graveyard from anywhere, exile it instead. + this.getRightHalfCard().addAbility(DisturbAbility.makeBackAbility()); } private Galedrifter(final Galedrifter card) { diff --git a/Mage.Sets/src/mage/cards/h/HinterlandLogger.java b/Mage.Sets/src/mage/cards/h/HinterlandLogger.java index 51df4a09630..6f940e16eb7 100644 --- a/Mage.Sets/src/mage/cards/h/HinterlandLogger.java +++ b/Mage.Sets/src/mage/cards/h/HinterlandLogger.java @@ -1,10 +1,10 @@ package mage.cards.h; -import mage.MageInt; +import mage.abilities.common.WerewolfBackTriggeredAbility; import mage.abilities.common.WerewolfFrontTriggeredAbility; -import mage.abilities.keyword.TransformAbility; -import mage.cards.CardImpl; +import mage.abilities.keyword.TrampleAbility; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; import mage.constants.CardType; import mage.constants.SubType; @@ -13,20 +13,30 @@ import java.util.UUID; /** * @author fireshoes */ -public final class HinterlandLogger extends CardImpl { +public final class HinterlandLogger extends TransformingDoubleFacedCard { public HinterlandLogger(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); - this.subtype.add(SubType.HUMAN); - this.subtype.add(SubType.WEREWOLF); - this.power = new MageInt(2); - this.toughness = new MageInt(1); + super(ownerId, setInfo, + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HUMAN, SubType.WEREWOLF}, "{1}{G}", + "Timber Shredder", + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.WEREWOLF}, "G"); - this.secondSideCardClazz = mage.cards.t.TimberShredder.class; + this.getLeftHalfCard().setPT(2, 1); + this.getRightHalfCard().setPT(4, 2); // At the beginning of each upkeep, if no spells were cast last turn, transform Hinterland Logger. - this.addAbility(new TransformAbility()); - this.addAbility(new WerewolfFrontTriggeredAbility()); +// this.getLeftHalfCard().addAbility(new TransformAbility()); + this.getLeftHalfCard().addAbility(new WerewolfFrontTriggeredAbility()); + + // Timber Shredder + + // Trample + this.getRightHalfCard().addAbility(TrampleAbility.getInstance()); + + // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Timber Shredder. + this.getRightHalfCard().addAbility(new WerewolfBackTriggeredAbility()); + + } private HinterlandLogger(final HinterlandLogger card) { diff --git a/Mage.Sets/src/mage/cards/h/HookHauntDrifter.java b/Mage.Sets/src/mage/cards/h/HookHauntDrifter.java deleted file mode 100644 index b63a9bb5b09..00000000000 --- a/Mage.Sets/src/mage/cards/h/HookHauntDrifter.java +++ /dev/null @@ -1,42 +0,0 @@ -package mage.cards.h; - -import mage.MageInt; -import mage.abilities.keyword.DisturbAbility; -import mage.abilities.keyword.FlyingAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; - -import java.util.UUID; - -/** - * @author TheElk801 - */ -public final class HookHauntDrifter extends CardImpl { - - public HookHauntDrifter(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); - - this.subtype.add(SubType.SPIRIT); - this.power = new MageInt(1); - this.toughness = new MageInt(2); - this.color.setBlue(true); - this.nightCard = true; - - // Flying - this.addAbility(FlyingAbility.getInstance()); - - // If Hook-Haunt Drifter would be put into a graveyard from anywhere, exile it instead. - this.addAbility(DisturbAbility.makeBackAbility()); - } - - private HookHauntDrifter(final HookHauntDrifter card) { - super(card); - } - - @Override - public HookHauntDrifter copy() { - return new HookHauntDrifter(this); - } -} diff --git a/Mage.Sets/src/mage/cards/h/HuntmasterOfTheFells.java b/Mage.Sets/src/mage/cards/h/HuntmasterOfTheFells.java index 5994fd49068..ca890cf5573 100644 --- a/Mage.Sets/src/mage/cards/h/HuntmasterOfTheFells.java +++ b/Mage.Sets/src/mage/cards/h/HuntmasterOfTheFells.java @@ -1,43 +1,64 @@ package mage.cards.h; -import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.common.TransformIntoSourceTriggeredAbility; import mage.abilities.common.TransformsOrEntersTriggeredAbility; +import mage.abilities.common.WerewolfBackTriggeredAbility; import mage.abilities.common.WerewolfFrontTriggeredAbility; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.GainLifeEffect; -import mage.abilities.keyword.TransformAbility; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; import mage.constants.CardType; +import mage.constants.Outcome; import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; import mage.game.permanent.token.WolfToken; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetOpponentOrPlaneswalker; +import mage.target.targetpointer.EachTargetPointer; +import java.util.Set; import java.util.UUID; /** * @author BetaSteward */ -public final class HuntmasterOfTheFells extends CardImpl { +public final class HuntmasterOfTheFells extends TransformingDoubleFacedCard { public HuntmasterOfTheFells(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}"); - this.subtype.add(SubType.HUMAN); - this.subtype.add(SubType.WEREWOLF); + super(ownerId, setInfo, + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HUMAN, SubType.WEREWOLF}, "{2}{R}{G}", + "Ravager of the Fells", + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.WEREWOLF}, "RG" + ); - this.secondSideCardClazz = mage.cards.r.RavagerOfTheFells.class; - - this.power = new MageInt(2); - this.toughness = new MageInt(2); + this.getLeftHalfCard().setPT(2, 2); + this.getRightHalfCard().setPT(4, 4); // Whenever this creature enters the battlefield or transforms into Huntmaster of the Fells, create a 2/2 green Wolf creature token and you gain 2 life. Ability ability = new TransformsOrEntersTriggeredAbility(new CreateTokenEffect(new WolfToken()), false); ability.addEffect(new GainLifeEffect(2).concatBy("and")); - this.addAbility(ability); + this.getLeftHalfCard().addAbility(ability); // At the beginning of each upkeep, if no spells were cast last turn, transform Huntmaster of the Fells. - this.addAbility(new TransformAbility()); - this.addAbility(new WerewolfFrontTriggeredAbility()); + this.getLeftHalfCard().addAbility(new WerewolfFrontTriggeredAbility()); + + + // Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls. + Ability ravagerAbility = new TransformIntoSourceTriggeredAbility( + new RavagerOfTheFellsEffect(), false, true + ); + ravagerAbility.addTarget(new TargetOpponentOrPlaneswalker()); + ravagerAbility.addTarget(new RavagerOfTheFellsTarget()); + this.getRightHalfCard().addAbility(ravagerAbility); + + // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Ravager of the Fells. + this.getRightHalfCard().addAbility(new WerewolfBackTriggeredAbility()); } private HuntmasterOfTheFells(final HuntmasterOfTheFells card) { @@ -49,3 +70,70 @@ public final class HuntmasterOfTheFells extends CardImpl { return new HuntmasterOfTheFells(this); } } + +class RavagerOfTheFellsEffect extends OneShotEffect { + + RavagerOfTheFellsEffect() { + super(Outcome.Damage); + this.setTargetPointer(new EachTargetPointer()); + staticText = "it deals 2 damage to target opponent or planeswalker and 2 damage " + + "to up to one target creature that player or that planeswalker's controller controls."; + } + + private RavagerOfTheFellsEffect(final RavagerOfTheFellsEffect effect) { + super(effect); + } + + @Override + public RavagerOfTheFellsEffect copy() { + return new RavagerOfTheFellsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + game.damagePlayerOrPermanent( + targetId, 2, source.getSourceId(), source, + game, false, true + ); + } + return true; + } + +} + +class RavagerOfTheFellsTarget extends TargetPermanent { + + RavagerOfTheFellsTarget() { + super(0, 1, StaticFilters.FILTER_PERMANENT_CREATURE); + } + + private RavagerOfTheFellsTarget(final RavagerOfTheFellsTarget target) { + super(target); + } + + @Override + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); + + Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); + if (needPlayer == null) { + // playable or not selected - use any + } else { + // filter by controller + possibleTargets.removeIf(id -> { + Permanent permanent = game.getPermanent(id); + return permanent == null + || permanent.getId().equals(source.getFirstTarget()) + || !permanent.isControlledBy(needPlayer.getId()); + }); + } + + return possibleTargets; + } + + @Override + public RavagerOfTheFellsTarget copy() { + return new RavagerOfTheFellsTarget(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfBelenon.java b/Mage.Sets/src/mage/cards/i/InvasionOfBelenon.java index 717ac38bdc8..797ae3caef3 100644 --- a/Mage.Sets/src/mage/cards/i/InvasionOfBelenon.java +++ b/Mage.Sets/src/mage/cards/i/InvasionOfBelenon.java @@ -2,10 +2,13 @@ package mage.cards.i; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SiegeAbility; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.CreateTokenEffect; -import mage.cards.CardImpl; +import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; import mage.constants.CardType; +import mage.constants.Duration; import mage.constants.SubType; import mage.game.permanent.token.KnightWhiteBlueToken; @@ -14,20 +17,27 @@ import java.util.UUID; /** * @author TheElk801 */ -public final class InvasionOfBelenon extends CardImpl { +public final class InvasionOfBelenon extends TransformingDoubleFacedCard { public InvasionOfBelenon(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.BATTLE}, "{2}{W}"); + super(ownerId, setInfo, + new CardType[]{CardType.BATTLE}, new SubType[]{SubType.SIEGE}, "{2}{W}", + "Belenon War Anthem", + new CardType[]{CardType.ENCHANTMENT}, new SubType[]{}, "W"); - this.subtype.add(SubType.SIEGE); - this.setStartingDefense(5); - this.secondSideCardClazz = mage.cards.b.BelenonWarAnthem.class; + this.getLeftHalfCard().setStartingDefense(5); // (As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.) - this.addAbility(new SiegeAbility()); + this.getLeftHalfCard().addAbility(new SiegeAbility()); // When Invasion of Belenon enters the battlefield, create a 2/2 white and blue Knight creature token with vigilance. - this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new KnightWhiteBlueToken()))); + this.getLeftHalfCard().addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new KnightWhiteBlueToken()))); + + // Belenon War Anthem + + // Creatures you control get +1/+1. + this.getRightHalfCard().addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield))); + } private InvasionOfBelenon(final InvasionOfBelenon card) { diff --git a/Mage.Sets/src/mage/cards/i/InvasionOfPyrulea.java b/Mage.Sets/src/mage/cards/i/InvasionOfPyrulea.java index beeee35bd4f..0c7368ae308 100644 --- a/Mage.Sets/src/mage/cards/i/InvasionOfPyrulea.java +++ b/Mage.Sets/src/mage/cards/i/InvasionOfPyrulea.java @@ -67,7 +67,7 @@ class InvasionOfPyruleaEffect extends OneShotEffect { player.scry(3, source, game); Card card = player.getLibrary().getFromTop(game); player.revealCards(source, new CardsImpl(card), game); - if (card != null && (card.isLand(game) || card instanceof ModalDoubleFacedCard || card.getSecondCardFace() != null)) { + if (card != null && (card.isLand(game) || card instanceof DoubleFacedCard || card.getSecondCardFace() != null)) { player.drawCards(1, source, game); } return true; diff --git a/Mage.Sets/src/mage/cards/j/JestersScepter.java b/Mage.Sets/src/mage/cards/j/JestersScepter.java index bb34acdfccd..482244ee81e 100644 --- a/Mage.Sets/src/mage/cards/j/JestersScepter.java +++ b/Mage.Sets/src/mage/cards/j/JestersScepter.java @@ -165,9 +165,9 @@ class JestersScepterCost extends CostImpl { if (card instanceof SplitCard) { game.getState().setValue(source.getSourceId() + "_nameOfExiledCardPayment", ((SplitCard) card).getLeftHalfCard().getName()); game.getState().setValue(source.getSourceId() + "_nameOfExiledCardPayment2", ((SplitCard) card).getRightHalfCard().getName()); - } else if (card instanceof ModalDoubleFacedCard) { - game.getState().setValue(source.getSourceId() + "_nameOfExiledCardPayment", ((ModalDoubleFacedCard) card).getLeftHalfCard().getName()); - game.getState().setValue(source.getSourceId() + "_nameOfExiledCardPayment2", ((ModalDoubleFacedCard) card).getRightHalfCard().getName()); + } else if (card instanceof DoubleFacedCard) { + game.getState().setValue(source.getSourceId() + "_nameOfExiledCardPayment", ((DoubleFacedCard) card).getLeftHalfCard().getName()); + game.getState().setValue(source.getSourceId() + "_nameOfExiledCardPayment2", ((DoubleFacedCard) card).getRightHalfCard().getName()); } else { game.getState().setValue(source.getSourceId() + "_nameOfExiledCardPayment", card.getName()); } diff --git a/Mage.Sets/src/mage/cards/l/LambholtButcher.java b/Mage.Sets/src/mage/cards/l/LambholtButcher.java deleted file mode 100644 index 7e168fbffb3..00000000000 --- a/Mage.Sets/src/mage/cards/l/LambholtButcher.java +++ /dev/null @@ -1,39 +0,0 @@ -package mage.cards.l; - -import java.util.UUID; -import mage.MageInt; -import mage.abilities.common.WerewolfBackTriggeredAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; - -/** - * - * @author fireshoes - */ -public final class LambholtButcher extends CardImpl { - - public LambholtButcher(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},""); - this.subtype.add(SubType.WEREWOLF); - this.power = new MageInt(4); - this.toughness = new MageInt(4); - this.color.setGreen(true); - - // this card is the second face of double-faced card - this.nightCard = true; - - // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Lambholt Butcher. - this.addAbility(new WerewolfBackTriggeredAbility()); - } - - private LambholtButcher(final LambholtButcher card) { - super(card); - } - - @Override - public LambholtButcher copy() { - return new LambholtButcher(this); - } -} diff --git a/Mage.Sets/src/mage/cards/l/LambholtPacifist.java b/Mage.Sets/src/mage/cards/l/LambholtPacifist.java index 36969373dc5..ab4f81c39fa 100644 --- a/Mage.Sets/src/mage/cards/l/LambholtPacifist.java +++ b/Mage.Sets/src/mage/cards/l/LambholtPacifist.java @@ -1,7 +1,7 @@ package mage.cards.l; -import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.WerewolfBackTriggeredAbility; import mage.abilities.common.WerewolfFrontTriggeredAbility; import mage.abilities.condition.Condition; import mage.abilities.condition.InvertCondition; @@ -9,9 +9,8 @@ import mage.abilities.condition.common.FerociousCondition; import mage.abilities.decorator.ConditionalRestrictionEffect; import mage.abilities.effects.common.combat.CantAttackSourceEffect; import mage.abilities.hint.common.FerociousHint; -import mage.abilities.keyword.TransformAbility; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; @@ -21,29 +20,32 @@ import java.util.UUID; /** * @author fireshoes */ -public final class LambholtPacifist extends CardImpl { +public final class LambholtPacifist extends TransformingDoubleFacedCard { private static final Condition condition = new InvertCondition(FerociousCondition.instance); public LambholtPacifist(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); - this.subtype.add(SubType.HUMAN); - this.subtype.add(SubType.SHAMAN); - this.subtype.add(SubType.WEREWOLF); - this.power = new MageInt(3); - this.toughness = new MageInt(3); + super(ownerId, setInfo, + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.HUMAN, SubType.SHAMAN, SubType.WEREWOLF}, "{1}{G}", + "Lambholt Butcher", + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.WEREWOLF}, "G"); - this.secondSideCardClazz = mage.cards.l.LambholtButcher.class; + this.getLeftHalfCard().setPT(3, 3); + this.getRightHalfCard().setPT(4, 4); // Lambholt Pacifist can't attack unless you control a creature with power 4 or greater. - this.addAbility(new SimpleStaticAbility(new ConditionalRestrictionEffect( + this.getLeftHalfCard().addAbility(new SimpleStaticAbility(new ConditionalRestrictionEffect( new CantAttackSourceEffect(Duration.WhileOnBattlefield), condition, "{this} can't attack unless you control a creature with power 4 or greater" )).addHint(FerociousHint.instance)); // At the beginning of each upkeep, if no spells were cast last turn, transform Lambholt Pacifist. - this.addAbility(new TransformAbility()); - this.addAbility(new WerewolfFrontTriggeredAbility()); + this.getLeftHalfCard().addAbility(new WerewolfFrontTriggeredAbility()); + + // Lambholt Butcher + + // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Lambholt Butcher. + this.getRightHalfCard().addAbility(new WerewolfBackTriggeredAbility()); } private LambholtPacifist(final LambholtPacifist card) { diff --git a/Mage.Sets/src/mage/cards/l/LikenessOfTheSeeker.java b/Mage.Sets/src/mage/cards/l/LikenessOfTheSeeker.java deleted file mode 100644 index a2fa4b8c3b0..00000000000 --- a/Mage.Sets/src/mage/cards/l/LikenessOfTheSeeker.java +++ /dev/null @@ -1,40 +0,0 @@ -package mage.cards.l; - -import java.util.UUID; -import mage.MageInt; -import mage.abilities.common.BecomesBlockedSourceTriggeredAbility; -import mage.abilities.effects.common.UntapLandsEffect; -import mage.constants.SubType; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; - -/** - * - * @author weirddan455 - */ -public final class LikenessOfTheSeeker extends CardImpl { - - public LikenessOfTheSeeker(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, ""); - - this.subtype.add(SubType.HUMAN); - this.subtype.add(SubType.MONK); - this.power = new MageInt(3); - this.toughness = new MageInt(3); - this.color.setGreen(true); - this.nightCard = true; - - // Whenever Likeness of the Seeker becomes blocked, untap up to three lands you control. - this.addAbility(new BecomesBlockedSourceTriggeredAbility(new UntapLandsEffect(3, true, true), false)); - } - - private LikenessOfTheSeeker(final LikenessOfTheSeeker card) { - super(card); - } - - @Override - public LikenessOfTheSeeker copy() { - return new LikenessOfTheSeeker(this); - } -} diff --git a/Mage.Sets/src/mage/cards/m/MilesMorales.java b/Mage.Sets/src/mage/cards/m/MilesMorales.java index 6069cffefeb..3e0f040fb29 100644 --- a/Mage.Sets/src/mage/cards/m/MilesMorales.java +++ b/Mage.Sets/src/mage/cards/m/MilesMorales.java @@ -60,7 +60,7 @@ public final class MilesMorales extends ModalDoubleFacedCard { // When Miles Morales enters, put a +1/+1 counter on each of up to two target creatures. Ability ability = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance())); ability.addTarget(new TargetCreaturePermanent(0, 2)); - this.addAbility(ability); + this.getLeftHalfCard().addAbility(ability); // {3}{R}{G}{W}: Transform Miles Morales. Activate only as a sorcery. this.getLeftHalfCard().addAbility(new ActivateAsSorceryActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/m/MycosynthLattice.java b/Mage.Sets/src/mage/cards/m/MycosynthLattice.java index cbefd32b853..7f14adf8639 100644 --- a/Mage.Sets/src/mage/cards/m/MycosynthLattice.java +++ b/Mage.Sets/src/mage/cards/m/MycosynthLattice.java @@ -134,10 +134,10 @@ class EverythingIsColorlessEffect extends ContinuousEffectImpl { affectedCards.forEach(card -> { game.getState().getCreateMageObjectAttribute(card, game).getColor().setColor(colorless); - // mdf cards - if (card instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + // df cards + if (card instanceof DoubleFacedCard) { + DoubleFacedCardHalf leftHalfCard = ((DoubleFacedCard) card).getLeftHalfCard(); + DoubleFacedCardHalf rightHalfCard = ((DoubleFacedCard) card).getRightHalfCard(); game.getState().getCreateMageObjectAttribute(leftHalfCard, game).getColor().setColor(colorless); game.getState().getCreateMageObjectAttribute(rightHalfCard, game).getColor().setColor(colorless); } @@ -151,6 +151,7 @@ class EverythingIsColorlessEffect extends ContinuousEffectImpl { } // double faces cards + // TODO: can remove after tdfc rework if (card.getSecondCardFace() != null) { game.getState().getCreateMageObjectAttribute(card, game).getColor().setColor(colorless); } diff --git a/Mage.Sets/src/mage/cards/o/OptimusPrimeAutobotLeader.java b/Mage.Sets/src/mage/cards/o/OptimusPrimeAutobotLeader.java deleted file mode 100644 index a3d53cfe4dc..00000000000 --- a/Mage.Sets/src/mage/cards/o/OptimusPrimeAutobotLeader.java +++ /dev/null @@ -1,130 +0,0 @@ -package mage.cards.o; - -import mage.MageInt; -import mage.MageObjectReference; -import mage.abilities.Ability; -import mage.abilities.DelayedTriggeredAbility; -import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.TransformSourceEffect; -import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; -import mage.abilities.effects.keyword.BolsterEffect; -import mage.abilities.keyword.LivingMetalAbility; -import mage.abilities.keyword.TrampleAbility; -import mage.abilities.keyword.TransformAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.game.Game; -import mage.game.events.DamagedPlayerEvent; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; - -import java.util.UUID; - -/** - * @author xenohedron - */ -public final class OptimusPrimeAutobotLeader extends CardImpl { - - public OptimusPrimeAutobotLeader(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, ""); - - this.addSuperType(SuperType.LEGENDARY); - this.subtype.add(SubType.VEHICLE); - this.power = new MageInt(6); - this.toughness = new MageInt(8); - this.color.setWhite(true); - this.color.setBlue(true); - this.color.setRed(true); - this.nightCard = true; - - // Living metal - this.addAbility(new LivingMetalAbility()); - - // Trample - this.addAbility(TrampleAbility.getInstance()); - - // Whenever you attack, bolster 2. The chosen creature gains trample until end of turn. When that creature deals combat damage to a player this turn, convert Optimus Prime. - this.addAbility(new AttacksWithCreaturesTriggeredAbility(new BolsterEffect(2) - .withAdditionalEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance())) - .withAdditionalEffect(new OptimusPrimeAutobotLeaderEffect()) - .setText("bolster 2. The chosen creature gains trample until end of turn. When that creature deals combat damage to a player this turn, convert {this}"), - 1)); - - // Transform Ability - this.addAbility(new TransformAbility()); - } - - private OptimusPrimeAutobotLeader(final OptimusPrimeAutobotLeader card) { - super(card); - } - - @Override - public OptimusPrimeAutobotLeader copy() { - return new OptimusPrimeAutobotLeader(this); - } -} - -class OptimusPrimeAutobotLeaderEffect extends OneShotEffect { - - OptimusPrimeAutobotLeaderEffect() { - super(Outcome.Transform); - } - - private OptimusPrimeAutobotLeaderEffect(final OptimusPrimeAutobotLeaderEffect effect) { - super(effect); - } - - @Override - public OptimusPrimeAutobotLeaderEffect copy() { - return new OptimusPrimeAutobotLeaderEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (creature == null) { - return false; - } - game.addDelayedTriggeredAbility(new OptimusPrimeAutobotLeaderDelayedTriggeredAbility(new MageObjectReference(creature, game)), source); - return true; - } - -} - -class OptimusPrimeAutobotLeaderDelayedTriggeredAbility extends DelayedTriggeredAbility { - - private final MageObjectReference mor; - - OptimusPrimeAutobotLeaderDelayedTriggeredAbility(MageObjectReference mor) { - super(new TransformSourceEffect().setText("convert {this}"), Duration.EndOfTurn); - this.mor = mor; - setTriggerPhrase("When that creature deals combat damage to a player this turn, "); - } - - private OptimusPrimeAutobotLeaderDelayedTriggeredAbility(final OptimusPrimeAutobotLeaderDelayedTriggeredAbility ability) { - super(ability); - this.mor = ability.mor; - } - - @Override - public OptimusPrimeAutobotLeaderDelayedTriggeredAbility copy() { - return new OptimusPrimeAutobotLeaderDelayedTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (((DamagedPlayerEvent) event).isCombatDamage()) { - Permanent permanent = game.getPermanent(event.getSourceId()); - return mor.refersTo(permanent, game); - } - return false; - } - -} diff --git a/Mage.Sets/src/mage/cards/o/OptimusPrimeHero.java b/Mage.Sets/src/mage/cards/o/OptimusPrimeHero.java index 833b7d03404..11a4d5db80c 100644 --- a/Mage.Sets/src/mage/cards/o/OptimusPrimeHero.java +++ b/Mage.Sets/src/mage/cards/o/OptimusPrimeHero.java @@ -1,7 +1,13 @@ package mage.cards.o; -import mage.MageInt; +import mage.MageObjectReference; import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.LivingMetalAbility; +import mage.abilities.keyword.TrampleAbility; import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.effects.OneShotEffect; @@ -9,10 +15,14 @@ import mage.abilities.effects.keyword.BolsterEffect; import mage.abilities.keyword.MoreThanMeetsTheEyeAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.Card; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; +import mage.cards.TransformingDoubleFacedCardHalf; import mage.constants.*; import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; import mage.players.Player; import java.util.UUID; @@ -20,25 +30,40 @@ import java.util.UUID; /** * @author jbureau88 */ -public final class OptimusPrimeHero extends CardImpl { +public final class OptimusPrimeHero extends TransformingDoubleFacedCard { public OptimusPrimeHero(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{U}{R}{W}"); + super(ownerId, setInfo, + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, new SubType[]{SubType.ROBOT}, "{3}{U}{R}{W}", + "Optimus Prime, Autobot Leader", + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.ARTIFACT}, new SubType[]{SubType.VEHICLE}, "WUR"); - this.addSuperType(SuperType.LEGENDARY); - this.subtype.add(SubType.ROBOT); - this.power = new MageInt(4); - this.toughness = new MageInt(8); - this.secondSideCardClazz = mage.cards.o.OptimusPrimeAutobotLeader.class; + this.getLeftHalfCard().setPT(4, 8); + this.getRightHalfCard().setPT(6, 8); // More Than Meets the Eye {2}{U}{R}{W} - this.addAbility(new MoreThanMeetsTheEyeAbility(this, "{2}{U}{R}{W}")); + this.getLeftHalfCard().addAbility(new MoreThanMeetsTheEyeAbility(this, "{2}{U}{R}{W}")); // At the beginning of each end step, bolster 1. - this.addAbility(new BeginningOfEndStepTriggeredAbility(TargetController.ANY, new BolsterEffect(1), false)); + this.getLeftHalfCard().addAbility(new BeginningOfEndStepTriggeredAbility(TargetController.ANY, new BolsterEffect(1), false)); // When Optimus Prime dies, return it to the battlefield converted under its owner’s control. - this.addAbility(new DiesSourceTriggeredAbility(new OptimusPrimeHeroEffect())); + this.getLeftHalfCard().addAbility(new DiesSourceTriggeredAbility(new OptimusPrimeHeroEffect())); + + // Optimus Prime, Autobot Leader + + // Living metal + this.getRightHalfCard().addAbility(new LivingMetalAbility()); + + // Trample + this.getRightHalfCard().addAbility(TrampleAbility.getInstance()); + + // Whenever you attack, bolster 2. The chosen creature gains trample until end of turn. When that creature deals combat damage to a player this turn, convert Optimus Prime. + this.getRightHalfCard().addAbility(new AttacksWithCreaturesTriggeredAbility(new BolsterEffect(2) + .withAdditionalEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance())) + .withAdditionalEffect(new OptimusPrimeAutobotLeaderEffect()) + .setText("bolster 2. The chosen creature gains trample until end of turn. When that creature deals combat damage to a player this turn, convert {this}"), + 1)); } private OptimusPrimeHero(final OptimusPrimeHero card) { @@ -70,11 +95,81 @@ class OptimusPrimeHeroEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Card card = game.getCard(source.getSourceId()); - if (card == null || controller == null) { + Card card = source.getSourceCardIfItStillExists(game); + if (controller == null || card == null) { return false; } - game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), Boolean.TRUE); - return controller.moveCards(card, Zone.BATTLEFIELD, source, game); + if (game.getState().getZone(source.getSourceId()) != Zone.GRAVEYARD) { + return true; + } + Card backSide = ((TransformingDoubleFacedCardHalf) card).getOtherSide(); + if (backSide == null) { + return true; + } + game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + backSide.getId(), true); + controller.moveCards(backSide, Zone.BATTLEFIELD, source, game); + return true; + } +} + +class OptimusPrimeAutobotLeaderEffect extends OneShotEffect { + + OptimusPrimeAutobotLeaderEffect() { + super(Outcome.Transform); + } + + private OptimusPrimeAutobotLeaderEffect(final OptimusPrimeAutobotLeaderEffect effect) { + super(effect); + } + + @Override + public OptimusPrimeAutobotLeaderEffect copy() { + return new OptimusPrimeAutobotLeaderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (creature == null) { + return false; + } + game.addDelayedTriggeredAbility(new OptimusPrimeAutobotLeaderDelayedTriggeredAbility(new MageObjectReference(creature, game)), source); + return true; + } + +} + +class OptimusPrimeAutobotLeaderDelayedTriggeredAbility extends DelayedTriggeredAbility { + + private final MageObjectReference mor; + + OptimusPrimeAutobotLeaderDelayedTriggeredAbility(MageObjectReference mor) { + super(new TransformSourceEffect().setText("convert {this}"), Duration.EndOfTurn); + this.mor = mor; + setTriggerPhrase("When that creature deals combat damage to a player this turn, "); + } + + private OptimusPrimeAutobotLeaderDelayedTriggeredAbility(final OptimusPrimeAutobotLeaderDelayedTriggeredAbility ability) { + super(ability); + this.mor = ability.mor; + } + + @Override + public OptimusPrimeAutobotLeaderDelayedTriggeredAbility copy() { + return new OptimusPrimeAutobotLeaderDelayedTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (((DamagedPlayerEvent) event).isCombatDamage()) { + Permanent permanent = game.getPermanent(event.getSourceId()); + return mor.refersTo(permanent, game); + } + return false; } } diff --git a/Mage.Sets/src/mage/cards/p/PaintersServant.java b/Mage.Sets/src/mage/cards/p/PaintersServant.java index bdffc476a83..4c47d77655f 100644 --- a/Mage.Sets/src/mage/cards/p/PaintersServant.java +++ b/Mage.Sets/src/mage/cards/p/PaintersServant.java @@ -107,10 +107,10 @@ class PaintersServantEffect extends ContinuousEffectImpl { affectedCards.forEach(card -> { game.getState().getCreateMageObjectAttribute(card, game).getColor().addColor(color); - // mdf cards - if (card instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + // df cards + if (card instanceof DoubleFacedCard) { + DoubleFacedCardHalf leftHalfCard = ((DoubleFacedCard) card).getLeftHalfCard(); + DoubleFacedCardHalf rightHalfCard = ((DoubleFacedCard) card).getRightHalfCard(); game.getState().getCreateMageObjectAttribute(leftHalfCard, game).getColor().addColor(color); game.getState().getCreateMageObjectAttribute(rightHalfCard, game).getColor().addColor(color); } @@ -124,6 +124,7 @@ class PaintersServantEffect extends ContinuousEffectImpl { } // double faces cards + // TODO: can remove after tdfc rework if (card.getSecondCardFace() != null) { game.getState().getCreateMageObjectAttribute(card.getSecondCardFace(), game).getColor().addColor(color); } diff --git a/Mage.Sets/src/mage/cards/p/PersistentNightmare.java b/Mage.Sets/src/mage/cards/p/PersistentNightmare.java deleted file mode 100644 index d1825f3f44b..00000000000 --- a/Mage.Sets/src/mage/cards/p/PersistentNightmare.java +++ /dev/null @@ -1,46 +0,0 @@ -package mage.cards.p; - -import mage.MageInt; -import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; -import mage.abilities.effects.common.ReturnToHandSourceEffect; -import mage.abilities.keyword.SkulkAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; - -import java.util.UUID; - -/** - * @author LevelX2 - */ -public final class PersistentNightmare extends CardImpl { - - public PersistentNightmare(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); - this.subtype.add(SubType.NIGHTMARE); - this.power = new MageInt(1); - this.toughness = new MageInt(1); - this.color.setBlue(true); - - // this card is the second face of double-faced card - this.nightCard = true; - - // Skulk - this.addAbility(new SkulkAbility()); - - // When Persistent Nightmare deals combat damage to a player, return it to its owner's hand. - this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( - new ReturnToHandSourceEffect(), false - ).setTriggerPhrase("When {this} deals combat damage to a player, ")); - } - - private PersistentNightmare(final PersistentNightmare card) { - super(card); - } - - @Override - public PersistentNightmare copy() { - return new PersistentNightmare(this); - } -} diff --git a/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java b/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java deleted file mode 100644 index ed4bd054210..00000000000 --- a/Mage.Sets/src/mage/cards/r/RavagerOfTheFells.java +++ /dev/null @@ -1,131 +0,0 @@ -package mage.cards.r; - -import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.TransformIntoSourceTriggeredAbility; -import mage.abilities.common.WerewolfBackTriggeredAbility; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.keyword.TrampleAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.TargetPermanent; -import mage.target.common.TargetOpponentOrPlaneswalker; -import mage.target.targetpointer.EachTargetPointer; - -import java.util.Set; -import java.util.UUID; - -/** - * @author BetaSteward - */ -public final class RavagerOfTheFells extends CardImpl { - - public RavagerOfTheFells(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); - this.subtype.add(SubType.WEREWOLF); - this.color.setRed(true); - this.color.setGreen(true); - - // this card is the second face of double-faced card - this.nightCard = true; - - this.power = new MageInt(4); - this.toughness = new MageInt(4); - - this.addAbility(TrampleAbility.getInstance()); - - // Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls. - Ability ability = new TransformIntoSourceTriggeredAbility( - new RavagerOfTheFellsEffect(), false, true - ); - ability.addTarget(new TargetOpponentOrPlaneswalker()); - ability.addTarget(new RavagerOfTheFellsTarget()); - this.addAbility(ability); - - // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Ravager of the Fells. - this.addAbility(new WerewolfBackTriggeredAbility()); - } - - private RavagerOfTheFells(final RavagerOfTheFells card) { - super(card); - } - - @Override - public RavagerOfTheFells copy() { - return new RavagerOfTheFells(this); - } -} - -class RavagerOfTheFellsEffect extends OneShotEffect { - - RavagerOfTheFellsEffect() { - super(Outcome.Damage); - this.setTargetPointer(new EachTargetPointer()); - staticText = "it deals 2 damage to target opponent or planeswalker and 2 damage " + - "to up to one target creature that player or that planeswalker's controller controls."; - } - - private RavagerOfTheFellsEffect(final RavagerOfTheFellsEffect effect) { - super(effect); - } - - @Override - public RavagerOfTheFellsEffect copy() { - return new RavagerOfTheFellsEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - for (UUID targetId : getTargetPointer().getTargets(game, source)) { - game.damagePlayerOrPermanent( - targetId, 2, source.getSourceId(), source, - game, false, true - ); - } - return true; - } - -} - -class RavagerOfTheFellsTarget extends TargetPermanent { - - RavagerOfTheFellsTarget() { - super(0, 1, StaticFilters.FILTER_PERMANENT_CREATURE); - } - - private RavagerOfTheFellsTarget(final RavagerOfTheFellsTarget target) { - super(target); - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); - - Player needPlayer = game.getPlayerOrPlaneswalkerController(source.getFirstTarget()); - if (needPlayer == null) { - // playable or not selected - use any - } else { - // filter by controller - possibleTargets.removeIf(id -> { - Permanent permanent = game.getPermanent(id); - return permanent == null - || permanent.getId().equals(source.getFirstTarget()) - || !permanent.isControlledBy(needPlayer.getId()); - }); - } - - return possibleTargets; - } - - @Override - public RavagerOfTheFellsTarget copy() { - return new RavagerOfTheFellsTarget(this); - } -} diff --git a/Mage.Sets/src/mage/cards/s/SinnersJudgment.java b/Mage.Sets/src/mage/cards/s/SinnersJudgment.java deleted file mode 100644 index 2014fb6d7d0..00000000000 --- a/Mage.Sets/src/mage/cards/s/SinnersJudgment.java +++ /dev/null @@ -1,90 +0,0 @@ -package mage.cards.s; - -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.counter.AddCountersSourceEffect; -import mage.abilities.keyword.DisturbAbility; -import mage.abilities.keyword.EnchantAbility; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.counters.CounterType; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.target.TargetPlayer; - -import java.util.Optional; -import java.util.UUID; - -/** - * @author TheElk801 - */ -public final class SinnersJudgment extends CardImpl { - - public SinnersJudgment(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, ""); - - this.subtype.add(SubType.AURA); - this.subtype.add(SubType.CURSE); - this.color.setWhite(true); - this.nightCard = true; - - // Enchant player - TargetPlayer auraTarget = new TargetPlayer(); - this.getSpellAbility().addTarget(auraTarget); - this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); - this.addAbility(new EnchantAbility(auraTarget)); - - // At the beginning of your upkeep, put a judgment counter on Sinner's Judgment. Then if there are three or more judgment counters on it, enchanted player loses the game. - Ability ability = new BeginningOfUpkeepTriggeredAbility(new AddCountersSourceEffect(CounterType.JUDGMENT.createInstance())); - ability.addEffect(new SinnersJudgmentEffect()); - this.addAbility(ability); - - // If Sinner's Judgment would be put into a graveyard from anywhere, exile it instead. - this.addAbility(DisturbAbility.makeBackAbility()); - } - - private SinnersJudgment(final SinnersJudgment card) { - super(card); - } - - @Override - public SinnersJudgment copy() { - return new SinnersJudgment(this); - } -} - -class SinnersJudgmentEffect extends OneShotEffect { - - SinnersJudgmentEffect() { - super(Outcome.Benefit); - staticText = "Then if there are three or more judgment counters on it, enchanted player loses the game"; - } - - private SinnersJudgmentEffect(final SinnersJudgmentEffect effect) { - super(effect); - } - - @Override - public SinnersJudgmentEffect copy() { - return new SinnersJudgmentEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return Optional - .ofNullable(source.getSourcePermanentOrLKI(game)) - .filter(permanent -> permanent.getCounters(game).getCount(CounterType.JUDGMENT) >= 3) - .map(Permanent::getAttachedTo) - .map(game::getPlayer) - .filter(player -> { - player.lost(game); - return true; - }) - .isPresent(); - } -} diff --git a/Mage.Sets/src/mage/cards/s/SpringLoadedSawblades.java b/Mage.Sets/src/mage/cards/s/SpringLoadedSawblades.java index d9c7374a10f..19c939bd874 100644 --- a/Mage.Sets/src/mage/cards/s/SpringLoadedSawblades.java +++ b/Mage.Sets/src/mage/cards/s/SpringLoadedSawblades.java @@ -2,45 +2,73 @@ package mage.cards.s; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapTargetCost; import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.continuous.AddCardTypeSourceEffect; import mage.abilities.keyword.CraftAbility; +import mage.abilities.keyword.CrewAbility; import mage.abilities.keyword.FlashAbility; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; import mage.filter.predicate.permanent.TappedPredicate; import mage.target.TargetPermanent; +import mage.target.common.TargetControlledPermanent; import java.util.UUID; /** * @author TheElk801 */ -public final class SpringLoadedSawblades extends CardImpl { +public final class SpringLoadedSawblades extends TransformingDoubleFacedCard { + private static final FilterControlledPermanent bladeWheelFilter + = new FilterControlledArtifactPermanent("other untapped artifacts you control"); private static final FilterPermanent filter = new FilterOpponentsCreaturePermanent("tapped creature an opponent controls"); static { filter.add(TappedPredicate.TAPPED); + bladeWheelFilter.add(AnotherPredicate.instance); + bladeWheelFilter.add(TappedPredicate.UNTAPPED); } public SpringLoadedSawblades(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); - this.secondSideCardClazz = mage.cards.b.BladewheelChariot.class; + super(ownerId, setInfo, + new CardType[]{CardType.ARTIFACT}, new SubType[]{}, "{1}{W}", + "Bladewheel Chariot", + new CardType[]{CardType.ARTIFACT}, new SubType[]{SubType.VEHICLE}, "W"); + + this.getRightHalfCard().setPT(5, 5); // Flash - this.addAbility(FlashAbility.getInstance()); + this.getLeftHalfCard().addAbility(FlashAbility.getInstance()); // When Spring-Loaded Sawblades enters the battlefield, it deals 5 damage to target tapped creature an opponent controls. Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(5, "it")); ability.addTarget(new TargetPermanent(filter)); - this.addAbility(ability); + this.getLeftHalfCard().addAbility(ability); // Craft with artifact {3}{W} - this.addAbility(new CraftAbility("{3}{W}")); + this.getLeftHalfCard().addAbility(new CraftAbility("{3}{W}")); + + // Bladewheel Chariot + + // Tap two other untapped artifacts you control: Bladewheel Chariot becomes an artifact creature until end of turn. + this.getRightHalfCard().addAbility(new SimpleActivatedAbility(new AddCardTypeSourceEffect( + Duration.EndOfTurn, CardType.ARTIFACT, CardType.CREATURE + ).setText("{this} becomes an artifact creature until end of turn"), new TapTargetCost(new TargetControlledPermanent(2, bladeWheelFilter)))); + + // Crew 1 + this.getRightHalfCard().addAbility(new CrewAbility(1)); } private SpringLoadedSawblades(final SpringLoadedSawblades card) { diff --git a/Mage.Sets/src/mage/cards/s/StartledAwake.java b/Mage.Sets/src/mage/cards/s/StartledAwake.java index d9e1a5b049b..d46bc468036 100644 --- a/Mage.Sets/src/mage/cards/s/StartledAwake.java +++ b/Mage.Sets/src/mage/cards/s/StartledAwake.java @@ -1,17 +1,21 @@ package mage.cards.s; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.effects.common.ReturnToHandSourceEffect; +import mage.abilities.keyword.SkulkAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.Card; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; +import mage.cards.TransformingDoubleFacedCardHalf; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.SubType; import mage.constants.Zone; import mage.game.Game; import mage.players.Player; @@ -22,22 +26,34 @@ import java.util.UUID; /** * @author LevelX2 */ -public final class StartledAwake extends CardImpl { +public final class StartledAwake extends TransformingDoubleFacedCard { public StartledAwake(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}{U}"); + super(ownerId, setInfo, + new CardType[]{CardType.SORCERY}, new SubType[]{}, "{2}{U}{U}", + "Persistent Nightmare", + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.NIGHTMARE}, "U"); - this.secondSideCardClazz = mage.cards.p.PersistentNightmare.class; + this.getRightHalfCard().setPT(1, 1); // Target opponent puts the top thirteen cards of their library into their graveyard. - this.getSpellAbility().addTarget(new TargetOpponent()); - this.getSpellAbility().addEffect(new MillCardsTargetEffect(13)); + this.getLeftHalfCard().getSpellAbility().addTarget(new TargetOpponent()); + this.getLeftHalfCard().getSpellAbility().addEffect(new MillCardsTargetEffect(13)); // {3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery. - this.addAbility(new TransformAbility()); - this.addAbility(new ActivateAsSorceryActivatedAbility( + this.getLeftHalfCard().addAbility(new ActivateAsSorceryActivatedAbility( Zone.GRAVEYARD, new StartledAwakeReturnTransformedEffect(), new ManaCostsImpl<>("{3}{U}{U}") )); + + // Persistent Nightmare + + // Skulk + this.getRightHalfCard().addAbility(new SkulkAbility()); + + // When Persistent Nightmare deals combat damage to a player, return it to its owner's hand. + this.getRightHalfCard().addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new ReturnToHandSourceEffect(), false + ).setTriggerPhrase("When {this} deals combat damage to a player, ")); } private StartledAwake(final StartledAwake card) { @@ -76,8 +92,12 @@ class StartledAwakeReturnTransformedEffect extends OneShotEffect { if (game.getState().getZone(source.getSourceId()) != Zone.GRAVEYARD) { return true; } - game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + source.getSourceId(), true); - controller.moveCards(card, Zone.BATTLEFIELD, source, game); + Card backSide = ((TransformingDoubleFacedCardHalf) card).getOtherSide(); + if (backSide == null) { + return true; + } + game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + backSide.getId(), true); + controller.moveCards(backSide, Zone.BATTLEFIELD, source, game); return true; } } diff --git a/Mage.Sets/src/mage/cards/s/SurgicalSuiteHospitalRoom.java b/Mage.Sets/src/mage/cards/s/SurgicalSuiteHospitalRoom.java index daa9c7beb46..05814b89827 100644 --- a/Mage.Sets/src/mage/cards/s/SurgicalSuiteHospitalRoom.java +++ b/Mage.Sets/src/mage/cards/s/SurgicalSuiteHospitalRoom.java @@ -1,7 +1,5 @@ package mage.cards.s; -import java.util.UUID; - import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; import mage.abilities.common.UnlockThisDoorTriggeredAbility; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; @@ -19,6 +17,8 @@ import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.target.common.TargetAttackingCreature; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** * * @author oscscull @@ -45,19 +45,19 @@ public final class SurgicalSuiteHospitalRoom extends RoomCard { "{1}{W}", "{3}{W}", SpellAbilityType.SPLIT); this.subtype.add(SubType.ROOM); - // Left half ability - "When you unlock this door, return target creature card with mana value 3 or - // less from your graveyard to the battlefield." + // Left half ability - "When you unlock this door, return target creature card with mana value 3 or less from your graveyard to the battlefield." UnlockThisDoorTriggeredAbility left = new UnlockThisDoorTriggeredAbility( new ReturnFromGraveyardToBattlefieldTargetEffect(), false, true); left.addTarget(new TargetCardInYourGraveyard(filter)); + this.getLeftHalfCard().addAbility(left); // Right half ability - "Whenever you attack, put a +1/+1 counter on target attacking creature." AttacksWithCreaturesTriggeredAbility right = new AttacksWithCreaturesTriggeredAbility( new AddCountersTargetEffect(CounterType.P1P1.createInstance()), 1 ); right.addTarget(new TargetAttackingCreature()); + this.getRightHalfCard().addAbility(right); - this.addRoomAbilities(left, right); } private SurgicalSuiteHospitalRoom(final SurgicalSuiteHospitalRoom card) { diff --git a/Mage.Sets/src/mage/cards/t/TamiyoInquisitiveStudent.java b/Mage.Sets/src/mage/cards/t/TamiyoInquisitiveStudent.java index 6db926dfb59..5573bd25466 100644 --- a/Mage.Sets/src/mage/cards/t/TamiyoInquisitiveStudent.java +++ b/Mage.Sets/src/mage/cards/t/TamiyoInquisitiveStudent.java @@ -1,50 +1,89 @@ package mage.cards.t; -import mage.MageInt; -import mage.constants.Pronoun; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.AttacksAllTriggeredAbility; +import mage.abilities.common.delayed.UntilYourNextTurnDelayedTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerLibraryCount; +import mage.abilities.dynamicvalue.common.HalfValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.*; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.mana.AddManaOfAnyColorEffect; +import mage.cards.Card; +import mage.cards.TransformingDoubleFacedCard; +import mage.constants.*; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.common.DrawNthCardTriggeredAbility; -import mage.abilities.effects.common.ExileAndReturnSourceEffect; import mage.abilities.effects.keyword.InvestigateEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TransformAbility; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.PutCards; -import mage.constants.SubType; -import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.command.emblems.TamiyoSeasonedScholarEmblem; +import mage.target.common.TargetCardInYourGraveyard; import java.util.UUID; /** * @author Susucr */ -public final class TamiyoInquisitiveStudent extends CardImpl { +public final class TamiyoInquisitiveStudent extends TransformingDoubleFacedCard { + + private static final DynamicValue xValue = new HalfValue(CardsInControllerLibraryCount.instance, true); public TamiyoInquisitiveStudent(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}"); + super(ownerId, setInfo, + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.CREATURE}, new SubType[]{SubType.MOONFOLK, SubType.WIZARD}, "{U}", + "Tamiyo, Seasoned Scholar", + new SuperType[]{SuperType.LEGENDARY}, new CardType[]{CardType.PLANESWALKER}, new SubType[]{SubType.TAMIYO}, "GU"); - this.supertype.add(SuperType.LEGENDARY); - this.subtype.add(SubType.MOONFOLK); - this.subtype.add(SubType.WIZARD); - this.power = new MageInt(0); - this.toughness = new MageInt(3); - - this.secondSideCardClazz = TamiyoSeasonedScholar.class; + this.getLeftHalfCard().setPT(0, 3); + this.getRightHalfCard().setStartingLoyalty(2); // Flying - this.addAbility(FlyingAbility.getInstance()); + this.getLeftHalfCard().addAbility(FlyingAbility.getInstance()); // Whenever Tamiyo, Inquisitive Student attacks, investigate. - this.addAbility(new AttacksTriggeredAbility(new InvestigateEffect())); + this.getLeftHalfCard().addAbility(new AttacksTriggeredAbility(new InvestigateEffect())); // When you draw your third card in a turn, exile Tamiyo, then return her to the battlefield transformed under her owner's control. - this.addAbility(new TransformAbility()); - this.addAbility(new DrawNthCardTriggeredAbility( + this.getLeftHalfCard().addAbility(new TransformAbility()); + this.getLeftHalfCard().addAbility(new DrawNthCardTriggeredAbility( new ExileAndReturnSourceEffect(PutCards.BATTLEFIELD_TRANSFORMED, Pronoun.SHE), false, 3 ).setTriggerPhrase("When you draw your third card in a turn, ")); + + // Tamiyo, Seasoned Scholar + + // +2: Until your next turn, whenever a creature attacks you or a planeswalker you control, it gets -1/-0 until end of turn. + this.getRightHalfCard().addAbility(new LoyaltyAbility(new CreateDelayedTriggeredAbilityEffect( + new UntilYourNextTurnDelayedTriggeredAbility( + new AttacksAllTriggeredAbility( + new BoostTargetEffect(-1, 0, Duration.EndOfTurn) + .setText("it gets -1/-0 until end of turn"), + false, StaticFilters.FILTER_PERMANENT_CREATURE, + SetTargetPointer.PERMANENT, true + ) + ) + ), 2)); + + // -3: Return target instant or sorcery card from your graveyard to your hand. If it's a green card, add one mana of any color. + Ability ability = new LoyaltyAbility(new TamiyoSeasonedScholarMinus3Effect(), -3); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD)); + this.getRightHalfCard().addAbility(ability); + + // -7: Draw cards equal to half the number of cards in your library, rounded up. You get an emblem with "You have no maximum hand size." + ability = new LoyaltyAbility( + new DrawCardSourceControllerEffect(xValue) + .setText("Draw cards equal to half the number of cards in your library, rounded up."), + -7 + ); + ability.addEffect(new GetEmblemEffect(new TamiyoSeasonedScholarEmblem())); + this.getRightHalfCard().addAbility(ability); } private TamiyoInquisitiveStudent(final TamiyoInquisitiveStudent card) { @@ -56,3 +95,36 @@ public final class TamiyoInquisitiveStudent extends CardImpl { return new TamiyoInquisitiveStudent(this); } } + +class TamiyoSeasonedScholarMinus3Effect extends OneShotEffect { + + TamiyoSeasonedScholarMinus3Effect() { + super(Outcome.DrawCard); + this.staticText = "Return target instant or sorcery card from your graveyard to your hand. " + + "If it's a green card, add one mana of any color"; + } + + private TamiyoSeasonedScholarMinus3Effect(final TamiyoSeasonedScholarMinus3Effect effect) { + super(effect); + } + + @Override + public TamiyoSeasonedScholarMinus3Effect copy() { + return new TamiyoSeasonedScholarMinus3Effect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(source.getFirstTarget()); + if (card == null) { + return false; + } + Effect effect = new ReturnToHandTargetEffect(); + effect.setTargetPointer(getTargetPointer().copy()); + effect.apply(game, source); + if (card.getColor(game).isGreen()) { + new AddManaOfAnyColorEffect().apply(game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/t/TamiyoSeasonedScholar.java b/Mage.Sets/src/mage/cards/t/TamiyoSeasonedScholar.java deleted file mode 100644 index 22fb297f6d1..00000000000 --- a/Mage.Sets/src/mage/cards/t/TamiyoSeasonedScholar.java +++ /dev/null @@ -1,115 +0,0 @@ -package mage.cards.t; - -import mage.abilities.Ability; -import mage.abilities.LoyaltyAbility; -import mage.abilities.common.AttacksAllTriggeredAbility; -import mage.abilities.common.delayed.UntilYourNextTurnDelayedTriggeredAbility; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.CardsInControllerLibraryCount; -import mage.abilities.dynamicvalue.common.HalfValue; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; -import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.GetEmblemEffect; -import mage.abilities.effects.common.ReturnToHandTargetEffect; -import mage.abilities.effects.common.continuous.BoostTargetEffect; -import mage.abilities.effects.mana.AddManaOfAnyColorEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.game.command.emblems.TamiyoSeasonedScholarEmblem; -import mage.target.common.TargetCardInYourGraveyard; - -import java.util.UUID; - -/** - * @author Susucr - */ -public final class TamiyoSeasonedScholar extends CardImpl { - - private static final DynamicValue xValue = new HalfValue(CardsInControllerLibraryCount.instance, true); - - public TamiyoSeasonedScholar(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, ""); - - this.supertype.add(SuperType.LEGENDARY); - this.subtype.add(SubType.TAMIYO); - this.setStartingLoyalty(2); - - this.color.setGreen(true); - this.color.setBlue(true); - this.nightCard = true; - - // +2: Until your next turn, whenever a creature attacks you or a planeswalker you control, it gets -1/-0 until end of turn. - this.addAbility(new LoyaltyAbility(new CreateDelayedTriggeredAbilityEffect( - new UntilYourNextTurnDelayedTriggeredAbility( - new AttacksAllTriggeredAbility( - new BoostTargetEffect(-1, 0, Duration.EndOfTurn) - .setText("it gets -1/-0 until end of turn"), - false, StaticFilters.FILTER_PERMANENT_CREATURE, - SetTargetPointer.PERMANENT, true - ) - ) - ), 2)); - - // -3: Return target instant or sorcery card from your graveyard to your hand. If it's a green card, add one mana of any color. - Ability ability = new LoyaltyAbility(new TamiyoSeasonedScholarMinus3Effect(), -3); - ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD)); - this.addAbility(ability); - - // -7: Draw cards equal to half the number of cards in your library, rounded up. You get an emblem with "You have no maximum hand size." - ability = new LoyaltyAbility( - new DrawCardSourceControllerEffect(xValue) - .setText("Draw cards equal to half the number of cards in your library, rounded up."), - -7 - ); - ability.addEffect(new GetEmblemEffect(new TamiyoSeasonedScholarEmblem())); - this.addAbility(ability); - } - - private TamiyoSeasonedScholar(final TamiyoSeasonedScholar card) { - super(card); - } - - @Override - public TamiyoSeasonedScholar copy() { - return new TamiyoSeasonedScholar(this); - } -} - -class TamiyoSeasonedScholarMinus3Effect extends OneShotEffect { - - TamiyoSeasonedScholarMinus3Effect() { - super(Outcome.DrawCard); - this.staticText = "Return target instant or sorcery card from your graveyard to your hand. " - + "If it's a green card, add one mana of any color"; - } - - private TamiyoSeasonedScholarMinus3Effect(final TamiyoSeasonedScholarMinus3Effect effect) { - super(effect); - } - - @Override - public TamiyoSeasonedScholarMinus3Effect copy() { - return new TamiyoSeasonedScholarMinus3Effect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Card card = game.getCard(source.getFirstTarget()); - if (card == null) { - return false; - } - Effect effect = new ReturnToHandTargetEffect(); - effect.setTargetPointer(getTargetPointer().copy()); - effect.apply(game, source); - if (card.getColor(game).isGreen()) { - new AddManaOfAnyColorEffect().apply(game, source); - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TimberShredder.java b/Mage.Sets/src/mage/cards/t/TimberShredder.java deleted file mode 100644 index 6fe22125f0b..00000000000 --- a/Mage.Sets/src/mage/cards/t/TimberShredder.java +++ /dev/null @@ -1,43 +0,0 @@ -package mage.cards.t; - -import mage.MageInt; -import mage.abilities.common.WerewolfBackTriggeredAbility; -import mage.abilities.keyword.TrampleAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; - -import java.util.UUID; - -/** - * @author fireshoes - */ -public final class TimberShredder extends CardImpl { - - public TimberShredder(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); - this.subtype.add(SubType.WEREWOLF); - this.power = new MageInt(4); - this.toughness = new MageInt(2); - this.color.setGreen(true); - - // this card is the second face of double-faced card - this.nightCard = true; - - // Trample - this.addAbility(TrampleAbility.getInstance()); - - // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Timber Shredder. - this.addAbility(new WerewolfBackTriggeredAbility()); - } - - private TimberShredder(final TimberShredder card) { - super(card); - } - - @Override - public TimberShredder copy() { - return new TimberShredder(this); - } -} diff --git a/Mage.Sets/src/mage/cards/t/TinybonesBaubleBurglar.java b/Mage.Sets/src/mage/cards/t/TinybonesBaubleBurglar.java index 83473f261c3..b26b3873ca8 100644 --- a/Mage.Sets/src/mage/cards/t/TinybonesBaubleBurglar.java +++ b/Mage.Sets/src/mage/cards/t/TinybonesBaubleBurglar.java @@ -213,8 +213,8 @@ class TinybonesBaubleBurglarSpendAnyManaEffect extends AsThoughEffectImpl implem cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); } else if (card instanceof CardWithSpellOption) { cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); - } else if (card instanceof ModalDoubleFacedCard) { - cardState = game.getLastKnownInformationCard(((ModalDoubleFacedCard) card).getLeftHalfCard().getId(), Zone.EXILED); + } else if (card instanceof DoubleFacedCard) { + cardState = game.getLastKnownInformationCard(((DoubleFacedCard) card).getLeftHalfCard().getId(), Zone.EXILED); } else { cardState = game.getLastKnownInformationCard(card.getId(), Zone.EXILED); } diff --git a/Mage.Sets/src/mage/cards/u/UnholyAnnexRitualChamber.java b/Mage.Sets/src/mage/cards/u/UnholyAnnexRitualChamber.java index 04773cc23ab..0e84acb85eb 100644 --- a/Mage.Sets/src/mage/cards/u/UnholyAnnexRitualChamber.java +++ b/Mage.Sets/src/mage/cards/u/UnholyAnnexRitualChamber.java @@ -45,11 +45,11 @@ public final class UnholyAnnexRitualChamber extends RoomCard { new PermanentsOnTheBattlefieldCondition(filter), "If you control a Demon, each opponent loses 2 life and you gain 2 life. Otherwise, you lose 2 life" )); left.addHint(new ConditionHint(new PermanentsOnTheBattlefieldCondition(filter), "You control a Demon")); + this.getLeftHalfCard().addAbility(left); // Ritual Chamber: When you unlock this door, create a 6/6 black Demon creature token with flying. Ability right = new UnlockThisDoorTriggeredAbility(new CreateTokenEffect(new Demon66Token()), false, false); - - this.addRoomAbilities(left, right); + this.getRightHalfCard().addAbility(right); } private UnholyAnnexRitualChamber(final UnholyAnnexRitualChamber card) { diff --git a/Mage.Sets/src/mage/cards/u/UnimpededTrespasser.java b/Mage.Sets/src/mage/cards/u/UnimpededTrespasser.java deleted file mode 100644 index 71bf6341fa8..00000000000 --- a/Mage.Sets/src/mage/cards/u/UnimpededTrespasser.java +++ /dev/null @@ -1,40 +0,0 @@ - -package mage.cards.u; - -import java.util.UUID; -import mage.MageInt; -import mage.abilities.keyword.CantBeBlockedSourceAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; - -/** - * - * @author fireshoes - */ -public final class UnimpededTrespasser extends CardImpl { - - public UnimpededTrespasser(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},""); - this.subtype.add(SubType.SPIRIT); - this.power = new MageInt(3); - this.toughness = new MageInt(3); - this.color.setBlue(true); - - // this card is the second face of double-faced card - this.nightCard = true; - - // Unimpeded Trespasser can't be blocked. - this.addAbility(new CantBeBlockedSourceAbility()); - } - - private UnimpededTrespasser(final UnimpededTrespasser card) { - super(card); - } - - @Override - public UnimpededTrespasser copy() { - return new UnimpededTrespasser(this); - } -} diff --git a/Mage.Sets/src/mage/cards/u/UninvitedGeist.java b/Mage.Sets/src/mage/cards/u/UninvitedGeist.java index 4f142aba650..d3a58adfc90 100644 --- a/Mage.Sets/src/mage/cards/u/UninvitedGeist.java +++ b/Mage.Sets/src/mage/cards/u/UninvitedGeist.java @@ -1,13 +1,12 @@ package mage.cards.u; -import mage.MageInt; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.CantBeBlockedSourceAbility; import mage.abilities.keyword.SkulkAbility; -import mage.abilities.keyword.TransformAbility; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.TransformingDoubleFacedCard; import mage.constants.CardType; import mage.constants.SubType; @@ -16,23 +15,27 @@ import java.util.UUID; /** * @author fireshoes */ -public final class UninvitedGeist extends CardImpl { +public final class UninvitedGeist extends TransformingDoubleFacedCard { public UninvitedGeist(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); - this.subtype.add(SubType.SPIRIT); - this.power = new MageInt(2); - this.toughness = new MageInt(2); + super(ownerId, setInfo, + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.SPIRIT}, "{2}{U}", + "Unimpeded Trespasser", + new CardType[]{CardType.CREATURE}, new SubType[]{SubType.SPIRIT}, "U"); - this.secondSideCardClazz = mage.cards.u.UnimpededTrespasser.class; + this.getLeftHalfCard().setPT(2, 2); + this.getRightHalfCard().setPT(3, 3); // Skulk (This creature can't be blocked by creatures with greater power.) - this.addAbility(new SkulkAbility()); + this.getLeftHalfCard().addAbility(new SkulkAbility()); // When Uninvited Geist deals combat damage to a player, transform it. - this.addAbility(new TransformAbility()); - this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new TransformSourceEffect(), false)); + this.getLeftHalfCard().addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new TransformSourceEffect(), false)); + // Unimpeded Trespasser + + // Unimpeded Trespasser can't be blocked. + this.getRightHalfCard().addAbility(new CantBeBlockedSourceAbility()); } private UninvitedGeist(final UninvitedGeist card) { diff --git a/Mage.Sets/src/mage/cards/w/Waildrifter.java b/Mage.Sets/src/mage/cards/w/Waildrifter.java deleted file mode 100644 index 321a1d31d12..00000000000 --- a/Mage.Sets/src/mage/cards/w/Waildrifter.java +++ /dev/null @@ -1,43 +0,0 @@ -package mage.cards.w; - -import mage.MageInt; -import mage.abilities.keyword.DisturbAbility; -import mage.abilities.keyword.FlyingAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; - -import java.util.UUID; - -/** - * @author TheElk801 - */ -public final class Waildrifter extends CardImpl { - - public Waildrifter(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); - - this.subtype.add(SubType.HIPPOGRIFF); - this.subtype.add(SubType.SPIRIT); - this.power = new MageInt(2); - this.toughness = new MageInt(2); - this.color.setBlue(true); - this.nightCard = true; - - // Flying - this.addAbility(FlyingAbility.getInstance()); - - // If Waildrifter would be put into a graveyard from anywhere, exile it instead. - this.addAbility(DisturbAbility.makeBackAbility()); - } - - private Waildrifter(final Waildrifter card) { - super(card); - } - - @Override - public Waildrifter copy() { - return new Waildrifter(this); - } -} diff --git a/Mage.Sets/src/mage/cards/w/WalkInClosetForgottenCellar.java b/Mage.Sets/src/mage/cards/w/WalkInClosetForgottenCellar.java index 1c78a4c29da..5cebbbdc39c 100644 --- a/Mage.Sets/src/mage/cards/w/WalkInClosetForgottenCellar.java +++ b/Mage.Sets/src/mage/cards/w/WalkInClosetForgottenCellar.java @@ -25,6 +25,7 @@ public final class WalkInClosetForgottenCellar extends RoomCard { // Walk-In Closet: You may play lands from your graveyard. SimpleStaticAbility left = new SimpleStaticAbility(PlayFromGraveyardControllerEffect.playLands()); + this.getLeftHalfCard().addAbility(left); // Forgotten Cellar: When you unlock this door, you may cast spells from your graveyard this turn, and if a card would be put into your graveyard from anywhere this turn, exile it instead. UnlockThisDoorTriggeredAbility right = new UnlockThisDoorTriggeredAbility( @@ -34,8 +35,7 @@ public final class WalkInClosetForgottenCellar extends RoomCard { right.addEffect(new GraveyardFromAnywhereExileReplacementEffect(Duration.EndOfTurn).concatBy(", and") .setText("if a card would be put into your graveyard from anywhere this turn, exile it instead") ); - - this.addRoomAbilities(left, right); + this.getRightHalfCard().addAbility(right); } private WalkInClosetForgottenCellar(final WalkInClosetForgottenCellar card) { diff --git a/Mage.Sets/src/mage/sets/DarkAscension.java b/Mage.Sets/src/mage/sets/DarkAscension.java index 8abef1f136e..ca5307df048 100644 --- a/Mage.Sets/src/mage/sets/DarkAscension.java +++ b/Mage.Sets/src/mage/sets/DarkAscension.java @@ -4,7 +4,6 @@ package mage.sets; import mage.cards.Card; import mage.cards.ExpansionSet; import mage.cards.repository.CardInfo; -import mage.cards.repository.CardRepository; import mage.collation.BoosterCollator; import mage.collation.BoosterStructure; import mage.collation.CardRun; @@ -155,7 +154,6 @@ public final class DarkAscension extends ExpansionSet { cards.add(new SetCardInfo("Niblis of the Urn", 16, Rarity.UNCOMMON, mage.cards.n.NiblisOfTheUrn.class)); cards.add(new SetCardInfo("Predator Ooze", 124, Rarity.RARE, mage.cards.p.PredatorOoze.class)); cards.add(new SetCardInfo("Pyreheart Wolf", 101, Rarity.UNCOMMON, mage.cards.p.PyreheartWolf.class)); - cards.add(new SetCardInfo("Ravager of the Fells", 140, Rarity.MYTHIC, mage.cards.r.RavagerOfTheFells.class)); cards.add(new SetCardInfo("Ravenous Demon", 71, Rarity.RARE, mage.cards.r.RavenousDemon.class)); cards.add(new SetCardInfo("Ray of Revelation", 17, Rarity.COMMON, mage.cards.r.RayOfRevelation.class)); cards.add(new SetCardInfo("Reap the Seagraf", 72, Rarity.COMMON, mage.cards.r.ReapTheSeagraf.class)); diff --git a/Mage.Sets/src/mage/sets/FromTheVaultTransform.java b/Mage.Sets/src/mage/sets/FromTheVaultTransform.java index 6bbc1f7e337..2365e9d79ab 100644 --- a/Mage.Sets/src/mage/sets/FromTheVaultTransform.java +++ b/Mage.Sets/src/mage/sets/FromTheVaultTransform.java @@ -21,7 +21,6 @@ public final class FromTheVaultTransform extends ExpansionSet { this.hasBasicLands = false; cards.add(new SetCardInfo("Archangel Avacyn", 1, Rarity.MYTHIC, mage.cards.a.ArchangelAvacyn.class)); - cards.add(new SetCardInfo("Avacyn, the Purifier", 1, Rarity.MYTHIC, mage.cards.a.AvacynThePurifier.class)); cards.add(new SetCardInfo("Arguel's Blood Fast", 2, Rarity.MYTHIC, mage.cards.a.ArguelsBloodFast.class)); cards.add(new SetCardInfo("Temple of Aclazotz", 2, Rarity.MYTHIC, mage.cards.t.TempleOfAclazotz.class)); cards.add(new SetCardInfo("Arlinn Kord", 3, Rarity.MYTHIC, mage.cards.a.ArlinnKord.class)); @@ -40,7 +39,6 @@ public final class FromTheVaultTransform extends ExpansionSet { cards.add(new SetCardInfo("Garruk, the Veil-Cursed", 9, Rarity.MYTHIC, mage.cards.g.GarrukTheVeilCursed.class)); cards.add(new SetCardInfo("Gisela, the Broken Blade", 10, Rarity.MYTHIC, mage.cards.g.GiselaTheBrokenBlade.class)); cards.add(new SetCardInfo("Huntmaster of the Fells", 11, Rarity.MYTHIC, mage.cards.h.HuntmasterOfTheFells.class)); - cards.add(new SetCardInfo("Ravager of the Fells", 11, Rarity.MYTHIC, mage.cards.r.RavagerOfTheFells.class)); cards.add(new SetCardInfo("Jace, Vryn's Prodigy", 12, Rarity.MYTHIC, mage.cards.j.JaceVrynsProdigy.class)); cards.add(new SetCardInfo("Jace, Telepath Unbound", 12, Rarity.MYTHIC, mage.cards.j.JaceTelepathUnbound.class)); cards.add(new SetCardInfo("Kytheon, Hero of Akros", 13, Rarity.MYTHIC, mage.cards.k.KytheonHeroOfAkros.class)); diff --git a/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java b/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java index d2e0b5fbf70..6ed27986bef 100644 --- a/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java +++ b/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java @@ -421,8 +421,6 @@ public final class InnistradCrimsonVow extends ExpansionSet { cards.add(new SetCardInfo("Sigarda's Summons", 36, Rarity.RARE, mage.cards.s.SigardasSummons.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sigarda's Summons", 404, Rarity.RARE, mage.cards.s.SigardasSummons.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sigardian Paladin", 247, Rarity.UNCOMMON, mage.cards.s.SigardianPaladin.class)); - cards.add(new SetCardInfo("Sinner's Judgment", 12, Rarity.MYTHIC, mage.cards.s.SinnersJudgment.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Sinner's Judgment", 348, Rarity.MYTHIC, mage.cards.s.SinnersJudgment.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Skulking Killer", 130, Rarity.UNCOMMON, mage.cards.s.SkulkingKiller.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Skulking Killer", 296, Rarity.UNCOMMON, mage.cards.s.SkulkingKiller.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Skull Skaab", 248, Rarity.UNCOMMON, mage.cards.s.SkullSkaab.class)); diff --git a/Mage.Sets/src/mage/sets/InnistradDoubleFeature.java b/Mage.Sets/src/mage/sets/InnistradDoubleFeature.java index 5c5007cae54..245e5cbd79b 100644 --- a/Mage.Sets/src/mage/sets/InnistradDoubleFeature.java +++ b/Mage.Sets/src/mage/sets/InnistradDoubleFeature.java @@ -324,7 +324,6 @@ public final class InnistradDoubleFeature extends ExpansionSet { cards.add(new SetCardInfo("Homestead Courage", 24, Rarity.COMMON, mage.cards.h.HomesteadCourage.class)); cards.add(new SetCardInfo("Honeymoon Hearse", 427, Rarity.UNCOMMON, mage.cards.h.HoneymoonHearse.class)); cards.add(new SetCardInfo("Honored Heirloom", 524, Rarity.COMMON, mage.cards.h.HonoredHeirloom.class)); - cards.add(new SetCardInfo("Hook-Haunt Drifter", 42, Rarity.COMMON, mage.cards.h.HookHauntDrifter.class)); cards.add(new SetCardInfo("Hookhand Mariner", 470, Rarity.COMMON, mage.cards.h.HookhandMariner.class)); cards.add(new SetCardInfo("Hopeful Initiate", 287, Rarity.RARE, mage.cards.h.HopefulInitiate.class)); cards.add(new SetCardInfo("Hostile Hostel", 264, Rarity.MYTHIC, mage.cards.h.HostileHostel.class)); @@ -526,7 +525,6 @@ public final class InnistradDoubleFeature extends ExpansionSet { cards.add(new SetCardInfo("Sigardian Paladin", 514, Rarity.UNCOMMON, mage.cards.s.SigardianPaladin.class)); cards.add(new SetCardInfo("Sigardian Savior", 34, Rarity.MYTHIC, mage.cards.s.SigardianSavior.class)); cards.add(new SetCardInfo("Silver Bolt", 258, Rarity.COMMON, mage.cards.s.SilverBolt.class)); - cards.add(new SetCardInfo("Sinner's Judgment", 279, Rarity.MYTHIC, mage.cards.s.SinnersJudgment.class)); cards.add(new SetCardInfo("Siphon Insight", 241, Rarity.RARE, mage.cards.s.SiphonInsight.class)); cards.add(new SetCardInfo("Skaab Wrangler", 75, Rarity.UNCOMMON, mage.cards.s.SkaabWrangler.class)); cards.add(new SetCardInfo("Skulking Killer", 397, Rarity.UNCOMMON, mage.cards.s.SkulkingKiller.class)); @@ -633,7 +631,6 @@ public final class InnistradDoubleFeature extends ExpansionSet { cards.add(new SetCardInfo("Voldaren Stinger", 167, Rarity.COMMON, mage.cards.v.VoldarenStinger.class)); cards.add(new SetCardInfo("Volt-Charged Berserker", 450, Rarity.UNCOMMON, mage.cards.v.VoltChargedBerserker.class)); cards.add(new SetCardInfo("Voltaic Visionary", 450, Rarity.UNCOMMON, mage.cards.v.VoltaicVisionary.class)); - cards.add(new SetCardInfo("Waildrifter", 55, Rarity.COMMON, mage.cards.w.Waildrifter.class)); cards.add(new SetCardInfo("Wake to Slaughter", 250, Rarity.RARE, mage.cards.w.WakeToSlaughter.class)); cards.add(new SetCardInfo("Wandering Mind", 518, Rarity.UNCOMMON, mage.cards.w.WanderingMind.class)); cards.add(new SetCardInfo("Wanderlight Spirit", 353, Rarity.COMMON, mage.cards.w.WanderlightSpirit.class)); diff --git a/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java b/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java index 5617a15cdf1..b82683c7454 100644 --- a/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java +++ b/Mage.Sets/src/mage/sets/InnistradMidnightHunt.java @@ -248,7 +248,6 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Heirloom Mirror", 105, Rarity.UNCOMMON, mage.cards.h.HeirloomMirror.class)); cards.add(new SetCardInfo("Hobbling Zombie", 106, Rarity.COMMON, mage.cards.h.HobblingZombie.class)); cards.add(new SetCardInfo("Homestead Courage", 24, Rarity.COMMON, mage.cards.h.HomesteadCourage.class)); - cards.add(new SetCardInfo("Hook-Haunt Drifter", 42, Rarity.COMMON, mage.cards.h.HookHauntDrifter.class)); cards.add(new SetCardInfo("Hostile Hostel", 264, Rarity.MYTHIC, mage.cards.h.HostileHostel.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Hostile Hostel", 379, Rarity.MYTHIC, mage.cards.h.HostileHostel.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Hound Tamer", 187, Rarity.UNCOMMON, mage.cards.h.HoundTamer.class, NON_FULL_USE_VARIOUS)); @@ -499,7 +498,6 @@ public final class InnistradMidnightHunt extends ExpansionSet { cards.add(new SetCardInfo("Vivisection", 83, Rarity.UNCOMMON, mage.cards.v.Vivisection.class)); cards.add(new SetCardInfo("Voldaren Ambusher", 166, Rarity.UNCOMMON, mage.cards.v.VoldarenAmbusher.class)); cards.add(new SetCardInfo("Voldaren Stinger", 167, Rarity.COMMON, mage.cards.v.VoldarenStinger.class)); - cards.add(new SetCardInfo("Waildrifter", 55, Rarity.COMMON, mage.cards.w.Waildrifter.class)); cards.add(new SetCardInfo("Wake to Slaughter", 250, Rarity.RARE, mage.cards.w.WakeToSlaughter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Wake to Slaughter", 376, Rarity.RARE, mage.cards.w.WakeToSlaughter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Willow Geist", 207, Rarity.RARE, mage.cards.w.WillowGeist.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/InnistradRemastered.java b/Mage.Sets/src/mage/sets/InnistradRemastered.java index cb192ab1fb0..a97f76b5117 100644 --- a/Mage.Sets/src/mage/sets/InnistradRemastered.java +++ b/Mage.Sets/src/mage/sets/InnistradRemastered.java @@ -62,8 +62,6 @@ public class InnistradRemastered extends ExpansionSet { cards.add(new SetCardInfo("Aurora of Emrakul", 472, Rarity.UNCOMMON, mage.cards.a.AuroraOfEmrakul.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Avacyn, Angel of Hope", 477, Rarity.MYTHIC, mage.cards.a.AvacynAngelOfHope.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Avacyn, Angel of Hope", 482, Rarity.MYTHIC, mage.cards.a.AvacynAngelOfHope.class, FULL_ART_USE_VARIOUS)); - cards.add(new SetCardInfo("Avacyn, the Purifier", 11, Rarity.MYTHIC, mage.cards.a.AvacynThePurifier.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Avacyn, the Purifier", 449, Rarity.MYTHIC, mage.cards.a.AvacynThePurifier.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Avacynian Priest", 12, Rarity.COMMON, mage.cards.a.AvacynianPriest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avacynian Priest", 334, Rarity.COMMON, mage.cards.a.AvacynianPriest.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Awoken Demon", 107, Rarity.COMMON, mage.cards.a.AwokenDemon.class, NON_FULL_USE_VARIOUS)); @@ -417,9 +415,6 @@ public class InnistradRemastered extends ExpansionSet { cards.add(new SetCardInfo("Plains", 289, Rarity.LAND, mage.cards.basiclands.Plains.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Rally the Peasants", 347, Rarity.UNCOMMON, mage.cards.r.RallyThePeasants.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Rally the Peasants", 37, Rarity.UNCOMMON, mage.cards.r.RallyThePeasants.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Ravager of the Fells", 241, Rarity.RARE, mage.cards.r.RavagerOfTheFells.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Ravager of the Fells", 325, Rarity.RARE, mage.cards.r.RavagerOfTheFells.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Ravager of the Fells", 470, Rarity.RARE, mage.cards.r.RavagerOfTheFells.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Reckless Scholar", 81, Rarity.COMMON, mage.cards.r.RecklessScholar.class)); cards.add(new SetCardInfo("Reforge the Soul", 167, Rarity.RARE, mage.cards.r.ReforgeTheSoul.class)); cards.add(new SetCardInfo("Restless Bloodseeker", 128, Rarity.UNCOMMON, mage.cards.r.RestlessBloodseeker.class, NON_FULL_USE_VARIOUS)); @@ -521,7 +516,6 @@ public class InnistradRemastered extends ExpansionSet { cards.add(new SetCardInfo("Through the Breach", 175, Rarity.MYTHIC, mage.cards.t.ThroughTheBreach.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Through the Breach", 404, Rarity.MYTHIC, mage.cards.t.ThroughTheBreach.class, RETRO_ART_USE_VARIOUS)); cards.add(new SetCardInfo("Through the Breach", 487, Rarity.MYTHIC, mage.cards.t.ThroughTheBreach.class, FULL_ART_USE_VARIOUS)); - cards.add(new SetCardInfo("Timber Shredder", 203, Rarity.COMMON, mage.cards.t.TimberShredder.class)); cards.add(new SetCardInfo("Tireless Tracker", 219, Rarity.RARE, mage.cards.t.TirelessTracker.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tireless Tracker", 318, Rarity.RARE, mage.cards.t.TirelessTracker.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Torens, Fist of the Angels", 250, Rarity.RARE, mage.cards.t.TorensFistOfTheAngels.class)); diff --git a/Mage.Sets/src/mage/sets/KamigawaNeonDynasty.java b/Mage.Sets/src/mage/sets/KamigawaNeonDynasty.java index 35596662e26..c340e16170b 100644 --- a/Mage.Sets/src/mage/sets/KamigawaNeonDynasty.java +++ b/Mage.Sets/src/mage/sets/KamigawaNeonDynasty.java @@ -312,7 +312,6 @@ public final class KamigawaNeonDynasty extends ExpansionSet { cards.add(new SetCardInfo("Light-Paws, Emperor's Voice", 25, Rarity.RARE, mage.cards.l.LightPawsEmperorsVoice.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Light-Paws, Emperor's Voice", 367, Rarity.RARE, mage.cards.l.LightPawsEmperorsVoice.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Light-Paws, Emperor's Voice", 439, Rarity.RARE, mage.cards.l.LightPawsEmperorsVoice.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Likeness of the Seeker", 172, Rarity.UNCOMMON, mage.cards.l.LikenessOfTheSeeker.class)); cards.add(new SetCardInfo("Lion Sash", 26, Rarity.RARE, mage.cards.l.LionSash.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lion Sash", 368, Rarity.RARE, mage.cards.l.LionSash.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lion Sash", 440, Rarity.RARE, mage.cards.l.LionSash.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/MagicOnlinePromos.java b/Mage.Sets/src/mage/sets/MagicOnlinePromos.java index bcb19295bb5..136626b012a 100644 --- a/Mage.Sets/src/mage/sets/MagicOnlinePromos.java +++ b/Mage.Sets/src/mage/sets/MagicOnlinePromos.java @@ -2458,7 +2458,6 @@ public class MagicOnlinePromos extends ExpansionSet { cards.add(new SetCardInfo("Simic Signet", 62391, Rarity.COMMON, mage.cards.s.SimicSignet.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sin Collector", 50112, Rarity.UNCOMMON, mage.cards.s.SinCollector.class)); cards.add(new SetCardInfo("Sinkhole", 43566, Rarity.RARE, mage.cards.s.Sinkhole.class)); - cards.add(new SetCardInfo("Sinner's Judgment", 95279, Rarity.MYTHIC, mage.cards.s.SinnersJudgment.class)); cards.add(new SetCardInfo("Siphon Insight", 94066, Rarity.RARE, mage.cards.s.SiphonInsight.class)); cards.add(new SetCardInfo("Sisay, Weatherlight Captain", 91211, Rarity.RARE, mage.cards.s.SisayWeatherlightCaptain.class, RETRO_ART)); cards.add(new SetCardInfo("Skarrg Goliath", 47989, Rarity.RARE, mage.cards.s.SkarrgGoliath.class)); diff --git a/Mage.Sets/src/mage/sets/MarchOfTheMachine.java b/Mage.Sets/src/mage/sets/MarchOfTheMachine.java index 4a286cd8326..cfe3f93b1e5 100644 --- a/Mage.Sets/src/mage/sets/MarchOfTheMachine.java +++ b/Mage.Sets/src/mage/sets/MarchOfTheMachine.java @@ -69,7 +69,6 @@ public final class MarchOfTheMachine extends ExpansionSet { cards.add(new SetCardInfo("Baral and Kari Zev", 218, Rarity.RARE, mage.cards.b.BaralAndKariZev.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Baral and Kari Zev", 302, Rarity.RARE, mage.cards.b.BaralAndKariZev.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Beamtown Beatstick", 131, Rarity.COMMON, mage.cards.b.BeamtownBeatstick.class)); - cards.add(new SetCardInfo("Belenon War Anthem", 20, Rarity.UNCOMMON, mage.cards.b.BelenonWarAnthem.class)); cards.add(new SetCardInfo("Belligerent Regisaur", 191, Rarity.RARE, mage.cards.b.BelligerentRegisaur.class)); cards.add(new SetCardInfo("Bladed Battle-Fan", 91, Rarity.COMMON, mage.cards.b.BladedBattleFan.class)); cards.add(new SetCardInfo("Blighted Burgeoning", 177, Rarity.COMMON, mage.cards.b.BlightedBurgeoning.class)); diff --git a/Mage.Sets/src/mage/sets/MarvelLegendsSeriesInserts.java b/Mage.Sets/src/mage/sets/MarvelLegendsSeriesInserts.java index 7a4ebe5eb0c..9381ea8a8b2 100644 --- a/Mage.Sets/src/mage/sets/MarvelLegendsSeriesInserts.java +++ b/Mage.Sets/src/mage/sets/MarvelLegendsSeriesInserts.java @@ -23,7 +23,6 @@ public class MarvelLegendsSeriesInserts extends ExpansionSet { cards.add(new SetCardInfo("Anti-Venom, Horrifying Healer", 1, Rarity.MYTHIC, mage.cards.a.AntiVenomHorrifyingHealer.class)); cards.add(new SetCardInfo("Huntmaster of the Fells", 3, Rarity.RARE, mage.cards.h.HuntmasterOfTheFells.class)); cards.add(new SetCardInfo("Iron Spider, Stark Upgrade", 4, Rarity.RARE, mage.cards.i.IronSpiderStarkUpgrade.class)); - cards.add(new SetCardInfo("Ravager of the Fells", 3, Rarity.RARE, mage.cards.r.RavagerOfTheFells.class)); cards.add(new SetCardInfo("Spectacular Spider-Man", 2, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class)); } } diff --git a/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java b/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java index ae7b50e8bc2..8f713b9a4d7 100644 --- a/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java +++ b/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java @@ -4,15 +4,11 @@ import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; -import java.util.Arrays; -import java.util.List; - /** * @author TheElk801 */ public final class MarvelsSpiderMan extends ExpansionSet { - private static final List unfinished = Arrays.asList("Eddie Brock", "Gwen Stacy", "Miles Morales", "Norman Osborn", "Peter Parker"); private static final MarvelsSpiderMan instance = new MarvelsSpiderMan(); public static MarvelsSpiderMan getInstance() { @@ -310,7 +306,5 @@ public final class MarvelsSpiderMan extends ExpansionSet { cards.add(new SetCardInfo("With Great Power...", 24, Rarity.RARE, mage.cards.w.WithGreatPower.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("With Great Power...", 248, Rarity.RARE, mage.cards.w.WithGreatPower.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Wraith, Vicious Vigilante", 160, Rarity.UNCOMMON, mage.cards.w.WraithViciousVigilante.class)); - - cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); } } diff --git a/Mage.Sets/src/mage/sets/ModernHorizons3.java b/Mage.Sets/src/mage/sets/ModernHorizons3.java index b4afbf883c7..fc079c227d9 100644 --- a/Mage.Sets/src/mage/sets/ModernHorizons3.java +++ b/Mage.Sets/src/mage/sets/ModernHorizons3.java @@ -482,9 +482,6 @@ public final class ModernHorizons3 extends ExpansionSet { cards.add(new SetCardInfo("Tamiyo, Inquisitive Student", 242, Rarity.MYTHIC, mage.cards.t.TamiyoInquisitiveStudent.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tamiyo, Inquisitive Student", 443, Rarity.MYTHIC, mage.cards.t.TamiyoInquisitiveStudent.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Tamiyo, Inquisitive Student", 469, Rarity.MYTHIC, mage.cards.t.TamiyoInquisitiveStudent.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Tamiyo, Seasoned Scholar", 242, Rarity.MYTHIC, mage.cards.t.TamiyoSeasonedScholar.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Tamiyo, Seasoned Scholar", 443, Rarity.MYTHIC, mage.cards.t.TamiyoSeasonedScholar.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Tamiyo, Seasoned Scholar", 469, Rarity.MYTHIC, mage.cards.t.TamiyoSeasonedScholar.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Temperamental Oozewagg", 172, Rarity.COMMON, mage.cards.t.TemperamentalOozewagg.class)); cards.add(new SetCardInfo("Tempest Harvester", 73, Rarity.COMMON, mage.cards.t.TempestHarvester.class)); cards.add(new SetCardInfo("Territory Culler", 173, Rarity.UNCOMMON, mage.cards.t.TerritoryCuller.class)); diff --git a/Mage.Sets/src/mage/sets/SecretLairDrop.java b/Mage.Sets/src/mage/sets/SecretLairDrop.java index 2875eacd749..7be492c4f93 100644 --- a/Mage.Sets/src/mage/sets/SecretLairDrop.java +++ b/Mage.Sets/src/mage/sets/SecretLairDrop.java @@ -507,7 +507,6 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Kozilek, the Great Distortion", 493, Rarity.MYTHIC, mage.cards.k.KozilekTheGreatDistortion.class)); cards.add(new SetCardInfo("Primeval Titan", 494, Rarity.MYTHIC, mage.cards.p.PrimevalTitan.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Huntmaster of the Fells", 495, Rarity.MYTHIC, mage.cards.h.HuntmasterOfTheFells.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Ravager of the Fells", 495, Rarity.MYTHIC, mage.cards.r.RavagerOfTheFells.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Platinum Angel", 496, Rarity.RARE, mage.cards.p.PlatinumAngel.class)); cards.add(new SetCardInfo("Brimaz, King of Oreskos", 497, Rarity.MYTHIC, mage.cards.b.BrimazKingOfOreskos.class)); cards.add(new SetCardInfo("Arcanis the Omnipotent", 498, Rarity.RARE, mage.cards.a.ArcanisTheOmnipotent.class)); @@ -706,7 +705,6 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Dakkon Blackblade", 698, Rarity.RARE, mage.cards.d.DakkonBlackblade.class)); cards.add(new SetCardInfo("Olivia, Mobilized for War", 699, Rarity.MYTHIC, mage.cards.o.OliviaMobilizedForWar.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Huntmaster of the Fells", 700, Rarity.MYTHIC, mage.cards.h.HuntmasterOfTheFells.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Ravager of the Fells", 700, Rarity.MYTHIC, mage.cards.r.RavagerOfTheFells.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Elspeth, Knight-Errant", 701, Rarity.MYTHIC, mage.cards.e.ElspethKnightErrant.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Wastes", 704, Rarity.RARE, mage.cards.w.Wastes.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Wastes", 705, Rarity.RARE, mage.cards.w.Wastes.class, FULL_ART_BFZ_VARIOUS)); @@ -1063,7 +1061,6 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Eldrazi Temple", 1154, Rarity.RARE, mage.cards.e.EldraziTemple.class)); cards.add(new SetCardInfo("Esika, God of the Tree", 1155, Rarity.MYTHIC, mage.cards.e.EsikaGodOfTheTree.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Archangel Avacyn", 1156, Rarity.MYTHIC, mage.cards.a.ArchangelAvacyn.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Avacyn, the Purifier", 1156, Rarity.MYTHIC, mage.cards.a.AvacynThePurifier.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bloodline Keeper", 1157, Rarity.RARE, mage.cards.b.BloodlineKeeper.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lord of Lineage", 1157, Rarity.RARE, mage.cards.l.LordOfLineage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Nicol Bolas, the Ravager", 1158, Rarity.MYTHIC, mage.cards.n.NicolBolasTheRavager.class, NON_FULL_USE_VARIOUS)); @@ -1120,7 +1117,6 @@ public class SecretLairDrop extends ExpansionSet { cards.add(new SetCardInfo("Inkmoth Nexus", 1207, Rarity.RARE, mage.cards.i.InkmothNexus.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Esika, God of the Tree", 1208, Rarity.MYTHIC, mage.cards.e.EsikaGodOfTheTree.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Archangel Avacyn", 1209, Rarity.MYTHIC, mage.cards.a.ArchangelAvacyn.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Avacyn, the Purifier", 1209, Rarity.MYTHIC, mage.cards.a.AvacynThePurifier.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bloodline Keeper", 1210, Rarity.RARE, mage.cards.b.BloodlineKeeper.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lord of Lineage", 1210, Rarity.RARE, mage.cards.l.LordOfLineage.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Nicol Bolas, the Ravager", 1211, Rarity.MYTHIC, mage.cards.n.NicolBolasTheRavager.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/ShadowsOfThePast.java b/Mage.Sets/src/mage/sets/ShadowsOfThePast.java index c84cbce0064..fabcac3daa0 100644 --- a/Mage.Sets/src/mage/sets/ShadowsOfThePast.java +++ b/Mage.Sets/src/mage/sets/ShadowsOfThePast.java @@ -79,7 +79,6 @@ public class ShadowsOfThePast extends ExpansionSet { cards.add(new SetCardInfo("Mystic Retrieval", 20, Rarity.UNCOMMON, mage.cards.m.MysticRetrieval.class)); cards.add(new SetCardInfo("Past in Flames", 43, Rarity.MYTHIC, mage.cards.p.PastInFlames.class)); cards.add(new SetCardInfo("Rally the Peasants", 10, Rarity.UNCOMMON, mage.cards.r.RallyThePeasants.class)); - cards.add(new SetCardInfo("Ravager of the Fells", 64, Rarity.MYTHIC, mage.cards.r.RavagerOfTheFells.class)); cards.add(new SetCardInfo("Requiem Angel", 11, Rarity.RARE, mage.cards.r.RequiemAngel.class)); cards.add(new SetCardInfo("Seance", 12, Rarity.UNCOMMON, mage.cards.s.Seance.class)); cards.add(new SetCardInfo("Selhoff Occultist", 21, Rarity.COMMON, mage.cards.s.SelhoffOccultist.class)); diff --git a/Mage.Sets/src/mage/sets/ShadowsOverInnistrad.java b/Mage.Sets/src/mage/sets/ShadowsOverInnistrad.java index bd1e36b3394..fe85f3ac87c 100644 --- a/Mage.Sets/src/mage/sets/ShadowsOverInnistrad.java +++ b/Mage.Sets/src/mage/sets/ShadowsOverInnistrad.java @@ -55,7 +55,6 @@ public final class ShadowsOverInnistrad extends ExpansionSet { cards.add(new SetCardInfo("Asylum Visitor", 99, Rarity.RARE, mage.cards.a.AsylumVisitor.class)); cards.add(new SetCardInfo("Autumnal Gloom", 194, Rarity.UNCOMMON, mage.cards.a.AutumnalGloom.class)); cards.add(new SetCardInfo("Avacyn's Judgment", 145, Rarity.RARE, mage.cards.a.AvacynsJudgment.class)); - cards.add(new SetCardInfo("Avacyn, the Purifier", 5, Rarity.MYTHIC, mage.cards.a.AvacynThePurifier.class)); cards.add(new SetCardInfo("Avacynian Missionaries", 6, Rarity.UNCOMMON, mage.cards.a.AvacynianMissionaries.class)); cards.add(new SetCardInfo("Awoken Horror", 92, Rarity.RARE, mage.cards.a.AwokenHorror.class)); cards.add(new SetCardInfo("Bearer of Overwhelming Truths", 54, Rarity.UNCOMMON, mage.cards.b.BearerOfOverwhelmingTruths.class)); @@ -195,7 +194,6 @@ public final class ShadowsOverInnistrad extends ExpansionSet { cards.add(new SetCardInfo("Kessig Forgemaster", 169, Rarity.UNCOMMON, mage.cards.k.KessigForgemaster.class)); cards.add(new SetCardInfo("Kindly Stranger", 119, Rarity.UNCOMMON, mage.cards.k.KindlyStranger.class)); cards.add(new SetCardInfo("Krallenhorde Howler", 203, Rarity.UNCOMMON, mage.cards.k.KrallenhordeHowler.class)); - cards.add(new SetCardInfo("Lambholt Butcher", 215, Rarity.UNCOMMON, mage.cards.l.LambholtButcher.class)); cards.add(new SetCardInfo("Lambholt Pacifist", 215, Rarity.UNCOMMON, mage.cards.l.LambholtPacifist.class)); cards.add(new SetCardInfo("Lamplighter of Selhoff", 72, Rarity.COMMON, mage.cards.l.LamplighterOfSelhoff.class)); cards.add(new SetCardInfo("Lightning Axe", 170, Rarity.UNCOMMON, mage.cards.l.LightningAxe.class)); @@ -245,7 +243,6 @@ public final class ShadowsOverInnistrad extends ExpansionSet { cards.add(new SetCardInfo("Pale Rider of Trostad", 128, Rarity.UNCOMMON, mage.cards.p.PaleRiderOfTrostad.class)); cards.add(new SetCardInfo("Paranoid Parish-Blade", 33, Rarity.UNCOMMON, mage.cards.p.ParanoidParishBlade.class)); cards.add(new SetCardInfo("Perfected Form", 49, Rarity.UNCOMMON, mage.cards.p.PerfectedForm.class)); - cards.add(new SetCardInfo("Persistent Nightmare", 88, Rarity.MYTHIC, mage.cards.p.PersistentNightmare.class)); cards.add(new SetCardInfo("Pick the Brain", 129, Rarity.UNCOMMON, mage.cards.p.PickTheBrain.class)); cards.add(new SetCardInfo("Pieces of the Puzzle", 78, Rarity.COMMON, mage.cards.p.PiecesOfThePuzzle.class)); cards.add(new SetCardInfo("Pious Evangel", 34, Rarity.UNCOMMON, mage.cards.p.PiousEvangel.class)); @@ -331,7 +328,6 @@ public final class ShadowsOverInnistrad extends ExpansionSet { cards.add(new SetCardInfo("Thraben Gargoyle", 266, Rarity.UNCOMMON, mage.cards.t.ThrabenGargoyle.class)); cards.add(new SetCardInfo("Thraben Inspector", 44, Rarity.COMMON, mage.cards.t.ThrabenInspector.class)); cards.add(new SetCardInfo("Throttle", 138, Rarity.COMMON, mage.cards.t.Throttle.class)); - cards.add(new SetCardInfo("Timber Shredder", 210, Rarity.COMMON, mage.cards.t.TimberShredder.class)); cards.add(new SetCardInfo("Tireless Tracker", 233, Rarity.RARE, mage.cards.t.TirelessTracker.class)); cards.add(new SetCardInfo("To the Slaughter", 139, Rarity.RARE, mage.cards.t.ToTheSlaughter.class)); cards.add(new SetCardInfo("Tooth Collector", 140, Rarity.UNCOMMON, mage.cards.t.ToothCollector.class)); @@ -347,7 +343,6 @@ public final class ShadowsOverInnistrad extends ExpansionSet { cards.add(new SetCardInfo("Ulvenwald Hydra", 235, Rarity.MYTHIC, mage.cards.u.UlvenwaldHydra.class)); cards.add(new SetCardInfo("Ulvenwald Mysteries", 236, Rarity.UNCOMMON, mage.cards.u.UlvenwaldMysteries.class)); cards.add(new SetCardInfo("Uncaged Fury", 188, Rarity.COMMON, mage.cards.u.UncagedFury.class)); - cards.add(new SetCardInfo("Unimpeded Trespasser", 94, Rarity.UNCOMMON, mage.cards.u.UnimpededTrespasser.class)); cards.add(new SetCardInfo("Uninvited Geist", 94, Rarity.UNCOMMON, mage.cards.u.UninvitedGeist.class)); cards.add(new SetCardInfo("Unruly Mob", 47, Rarity.COMMON, mage.cards.u.UnrulyMob.class)); cards.add(new SetCardInfo("Vampire Noble", 143, Rarity.COMMON, mage.cards.v.VampireNoble.class)); diff --git a/Mage.Sets/src/mage/sets/ShadowsOverInnistradPromos.java b/Mage.Sets/src/mage/sets/ShadowsOverInnistradPromos.java index 8c445e2fa97..5b6672fcb47 100644 --- a/Mage.Sets/src/mage/sets/ShadowsOverInnistradPromos.java +++ b/Mage.Sets/src/mage/sets/ShadowsOverInnistradPromos.java @@ -32,7 +32,6 @@ public class ShadowsOverInnistradPromos extends ExpansionSet { cards.add(new SetCardInfo("Arlinn, Embraced by the Moon", "243s", Rarity.MYTHIC, mage.cards.a.ArlinnEmbracedByTheMoon.class)); cards.add(new SetCardInfo("Asylum Visitor", "99s", Rarity.RARE, mage.cards.a.AsylumVisitor.class)); cards.add(new SetCardInfo("Avacyn's Judgment", "145s", Rarity.RARE, mage.cards.a.AvacynsJudgment.class)); - cards.add(new SetCardInfo("Avacyn, the Purifier", "5s", Rarity.MYTHIC, mage.cards.a.AvacynThePurifier.class)); cards.add(new SetCardInfo("Awoken Horror", "92s", Rarity.RARE, mage.cards.a.AwokenHorror.class)); cards.add(new SetCardInfo("Behold the Beyond", "101s", Rarity.MYTHIC, mage.cards.b.BeholdTheBeyond.class)); cards.add(new SetCardInfo("Brain in a Jar", "252s", Rarity.RARE, mage.cards.b.BrainInAJar.class)); @@ -86,7 +85,6 @@ public class ShadowsOverInnistradPromos extends ExpansionSet { cards.add(new SetCardInfo("Odric, Lunarch Marshal", "31s", Rarity.RARE, mage.cards.o.OdricLunarchMarshal.class)); cards.add(new SetCardInfo("Olivia, Mobilized for War", "248s", Rarity.MYTHIC, mage.cards.o.OliviaMobilizedForWar.class)); cards.add(new SetCardInfo("Ormendahl, Profane Prince", "281s", Rarity.RARE, mage.cards.o.OrmendahlProfanePrince.class)); - cards.add(new SetCardInfo("Persistent Nightmare", "88s", Rarity.MYTHIC, mage.cards.p.PersistentNightmare.class)); cards.add(new SetCardInfo("Port Town", "278s", Rarity.RARE, mage.cards.p.PortTown.class)); cards.add(new SetCardInfo("Prized Amalgam", "249s", Rarity.RARE, mage.cards.p.PrizedAmalgam.class)); cards.add(new SetCardInfo("Rattlechains", "81s", Rarity.RARE, mage.cards.r.Rattlechains.class)); diff --git a/Mage.Sets/src/mage/sets/ShadowsOverInnistradRemastered.java b/Mage.Sets/src/mage/sets/ShadowsOverInnistradRemastered.java index 8739ee9b6ea..2b06fee63c0 100644 --- a/Mage.Sets/src/mage/sets/ShadowsOverInnistradRemastered.java +++ b/Mage.Sets/src/mage/sets/ShadowsOverInnistradRemastered.java @@ -44,7 +44,6 @@ public class ShadowsOverInnistradRemastered extends ExpansionSet { cards.add(new SetCardInfo("Assembled Alphas", 141, Rarity.RARE, mage.cards.a.AssembledAlphas.class)); cards.add(new SetCardInfo("Aurora of Emrakul", 248, Rarity.UNCOMMON, mage.cards.a.AuroraOfEmrakul.class)); cards.add(new SetCardInfo("Avacyn's Judgment", 142, Rarity.RARE, mage.cards.a.AvacynsJudgment.class)); - cards.add(new SetCardInfo("Avacyn, the Purifier", 13, Rarity.MYTHIC, mage.cards.a.AvacynThePurifier.class)); cards.add(new SetCardInfo("Awoken Horror", 95, Rarity.RARE, mage.cards.a.AwokenHorror.class)); cards.add(new SetCardInfo("Bearer of Overwhelming Truths", 59, Rarity.UNCOMMON, mage.cards.b.BearerOfOverwhelmingTruths.class)); cards.add(new SetCardInfo("Bedlam Reveler", 143, Rarity.RARE, mage.cards.b.BedlamReveler.class)); @@ -244,7 +243,6 @@ public class ShadowsOverInnistradRemastered extends ExpansionSet { cards.add(new SetCardInfo("Ormendahl, Profane Prince", 275, Rarity.RARE, mage.cards.o.OrmendahlProfanePrince.class)); cards.add(new SetCardInfo("Pack Guardian", 208, Rarity.UNCOMMON, mage.cards.p.PackGuardian.class)); cards.add(new SetCardInfo("Permeating Mass", 209, Rarity.RARE, mage.cards.p.PermeatingMass.class)); - cards.add(new SetCardInfo("Persistent Nightmare", 90, Rarity.MYTHIC, mage.cards.p.PersistentNightmare.class)); cards.add(new SetCardInfo("Pick the Brain", 129, Rarity.UNCOMMON, mage.cards.p.PickTheBrain.class)); cards.add(new SetCardInfo("Pieces of the Puzzle", 85, Rarity.UNCOMMON, mage.cards.p.PiecesOfThePuzzle.class)); cards.add(new SetCardInfo("Plains", 277, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); @@ -313,7 +311,6 @@ public class ShadowsOverInnistradRemastered extends ExpansionSet { cards.add(new SetCardInfo("Thornhide Wolves", 218, Rarity.COMMON, mage.cards.t.ThornhideWolves.class)); cards.add(new SetCardInfo("Thraben Foulbloods", 135, Rarity.COMMON, mage.cards.t.ThrabenFoulbloods.class)); cards.add(new SetCardInfo("Thraben Inspector", 51, Rarity.COMMON, mage.cards.t.ThrabenInspector.class)); - cards.add(new SetCardInfo("Timber Shredder", 201, Rarity.COMMON, mage.cards.t.TimberShredder.class)); cards.add(new SetCardInfo("Tireless Tracker", 219, Rarity.RARE, mage.cards.t.TirelessTracker.class)); cards.add(new SetCardInfo("Topplegeist", 52, Rarity.UNCOMMON, mage.cards.t.Topplegeist.class)); cards.add(new SetCardInfo("Tormenting Voice", 181, Rarity.COMMON, mage.cards.t.TormentingVoice.class)); diff --git a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java index b69a7f099d0..d0146c2adf2 100644 --- a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java +++ b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java @@ -1,11 +1,8 @@ package mage.sets; -import mage.cards.Card; import mage.cards.ExpansionSet; -import mage.cards.repository.CardInfo; import mage.constants.Rarity; import mage.constants.SetType; -import mage.util.RandomUtil; import mage.collation.BoosterCollator; import mage.collation.BoosterStructure; import mage.collation.CardRun; @@ -77,7 +74,6 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet { cards.add(new SetCardInfo("Belligerent Yearling", 133, Rarity.UNCOMMON, mage.cards.b.BelligerentYearling.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Belligerent Yearling", 320, Rarity.UNCOMMON, mage.cards.b.BelligerentYearling.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bitter Triumph", 91, Rarity.UNCOMMON, mage.cards.b.BitterTriumph.class)); - cards.add(new SetCardInfo("Bladewheel Chariot", 36, Rarity.UNCOMMON, mage.cards.b.BladewheelChariot.class)); cards.add(new SetCardInfo("Bloodletter of Aclazotz", 336, Rarity.MYTHIC, mage.cards.b.BloodletterOfAclazotz.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bloodletter of Aclazotz", 92, Rarity.MYTHIC, mage.cards.b.BloodletterOfAclazotz.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bloodthorn Flail", 93, Rarity.UNCOMMON, mage.cards.b.BloodthornFlail.class)); diff --git a/Mage.Sets/src/mage/sets/Transformers.java b/Mage.Sets/src/mage/sets/Transformers.java index 0e8daddb516..df8213ad7bc 100644 --- a/Mage.Sets/src/mage/sets/Transformers.java +++ b/Mage.Sets/src/mage/sets/Transformers.java @@ -33,9 +33,7 @@ public final class Transformers extends ExpansionSet { cards.add(new SetCardInfo("Jetfire, Ingenious Scientist", 3, Rarity.MYTHIC, mage.cards.j.JetfireIngeniousScientist.class)); cards.add(new SetCardInfo("Megatron, Destructive Force", 12, Rarity.MYTHIC, mage.cards.m.MegatronDestructiveForce.class)); cards.add(new SetCardInfo("Megatron, Tyrant", 12, Rarity.MYTHIC, mage.cards.m.MegatronTyrant.class)); - cards.add(new SetCardInfo("Optimus Prime, Autobot Leader", 13, Rarity.MYTHIC, mage.cards.o.OptimusPrimeAutobotLeader.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Optimus Prime, Hero", 13, Rarity.MYTHIC, mage.cards.o.OptimusPrimeHero.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Optimus Prime, Autobot Leader", 27, Rarity.MYTHIC, mage.cards.o.OptimusPrimeAutobotLeader.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Optimus Prime, Hero", 27, Rarity.MYTHIC, mage.cards.o.OptimusPrimeHero.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ratchet, Field Medic", 2, Rarity.MYTHIC, mage.cards.r.RatchetFieldMedic.class)); cards.add(new SetCardInfo("Ratchet, Rescue Racer", 2, Rarity.MYTHIC, mage.cards.r.RatchetRescueRacer.class)); 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 index f2e12c3e5e0..b81b26e844b 100644 --- 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 @@ -200,23 +200,8 @@ public class IncubateTest extends CardTestPlayerBase { 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); + checkPermanentCount("after copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phyrexian Token", 1); + // copy dies to state based actions setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java index 04417cc619c..c8ccc2e413d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java @@ -1,17 +1,290 @@ package org.mage.test.cards.abilities.keywords; +import mage.ObjectColor; +import mage.abilities.common.SagaAbility; +import mage.abilities.common.WerewolfFrontTriggeredAbility; import mage.constants.CardType; import mage.constants.PhaseStep; +import mage.constants.SubType; import mage.constants.Zone; import mage.counters.CounterType; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + /** * @author LevelX2 */ public class TransformTest extends CardTestPlayerBase { + + /* + Silvercoat Lion + {1}{W} + Creature - Cat + + 2/2 + */ + private static final String silvercoatLion = "Silvercoat Lion"; + + /* + Lightning Bolt + {R} + Instant + Lightning Bolt deals 3 damage to any target. + */ + private static final String lightningBolt = "Lightning Bolt"; + + /* + Azusa's Many Journeys + {1}{G} + Enchantment - Saga + (As this Saga enters and after your draw step, add a lore counter.) + I - You may play an additional land this turn. + II - You gain 3 life. + III - Exile this Saga, then return it to the battlefield transformed under your control. + Likeness of the Seeker + + Enchantment Creature - Human Monk + Whenever Likeness of the Seeker becomes blocked, untap up to three lands you control. + 3/3 + */ + private static final String azusasManyJourneys = "Azusa's Many Journeys"; + private static final String likenessOfTheSeeker = "Likeness of the Seeker"; + + /* + Liliana, Heretical Healer + {1}{B}{B} + Legendary Creature - Human Cleric + Lifelink + Whenever another nontoken creature you control dies, exile Liliana Heretical Healer, then return her to the battlefield transformed under her owner's control. If you do, put a 2/2 black Zombie creature token onto the battlefield. + 2/3 + + Liliana, Defiant Necromancer + Legendary Planeswalker - Liliana + +2: Each player discards a card. + -X: Return target nonlegendary creature with converted mana cost X from your graveyard to the battlefield. + -8: You get an emblem with "Whenever a creature you control dies, return it to the battlefield under your control at the beginning of the next end step." + */ + private static final String lilianaHereticalHealer = "Liliana, Heretical Healer"; + private static final String lilianaDefiantNecromancer = "Liliana, Defiant Necromancer"; + + /* + Languish + {2}{B}{B} + Sorcery + All creatures get -4/-4 until end of turn. + */ + private static final String languish = "Languish"; + + /* + Nissa, Vastwood Seer + {2}{G} + Legendary Creature - Elf Scout + When Nissa, Vastwood Seer enters the battlefield, you may search your library for a basic Forest card, reveal it, put it into your hand, then shuffle your library. + Whenever a land enters the battlefield under your control, if you control seven or more lands, exile Nissa, then return her to the battlefield transformed under her owner's control. + 2/2 + + Nissa, Sage Animist + Legendary Planeswalker - Nissa + +1: Reveal the top card of your library. If it's a land card, put it onto the battlefield. Otherwise, put it into your hand. + -2: Put a legendary 4/4 green Elemental creature token named Ashaya, the Awoken World onto the battlefield. + -7: Untap up to six target lands. They become 6/6 Elemental creatures. They're still lands. + */ + private static final String nissaVastwoodSeer = "Nissa, Vastwood Seer"; + private static final String nissaSageAnimist = "Nissa, Sage Animist"; + + /* + Fireball + {X}{R} + Sorcery + Fireball deals X damage divided evenly, rounded down, among any number of target creatures and/or players. + Fireball costs {1} more to cast for each target beyond the first. + */ + private static final String fireball = "Fireball"; + + /* + Infernal Scarring + {1}{B} + Enchantment - Aura + Enchant creature + Enchanted creature gets +2/+0 and has "When this creature dies, draw a card." + */ + private static final String infernalScarring = "Infernal Scarring"; + + /* + Autumnal Gloom + {2}{G} + Enchantment + {B}: Put the top card of your library into your graveyard. + Delirium - At the beginning of your end step, if there are four or more card types among cards in your graveyard, transform Autumnal Gloom. + + Ancient of the Equinox + Creature - Treefolk + Trample, hexproof + 4/4 + */ + private static final String autumnalGloom = "Autumnal Gloom"; + private static final String ancientOfTheEquinox = "Ancient of the Equinox"; + + /* + Huntmaster of the Fells + {2}{R}{G} + Creature - Human Werewolf + Whenever this creature enters the battlefield or transforms into Huntmaster of the Fells, put a 2/2 green Wolf creature token onto the battlefield and you gain 2 life. + At the beginning of each upkeep, if no spells were cast last turn, transform Huntmaster of the Fells. + 2/2 + + Ravager of the Fells + Creature - Werewolf + Trample + Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls. + At the beginning of each upkeep, if a player cast two or more spells last turn, transform Ravager of the Fells. + 4/4 + */ + private static final String huntmasterOfTheFells = "Huntmaster of the Fells"; + private static final String ravagerOfTheFells = "Ravager of the Fells"; + + /* + Eldrazi Displacer + {2}{W} + Creature - Eldrazi + Devoid + {2}{C}: Exile another target creature, then return it to the battlefield tapped under its owner's control. + 3/3 + */ + private static final String eldraziDisplacer = "Eldrazi Displacer"; + + /* + Howlpack Piper + {3}{G} + Creature - Human Werewolf + This spell can't be countered. + {1}{G}, {T}: You may put a creature card from your hand onto the battlefield. If it's a Wolf or Werewolf, untap Howlpack Piper. Activate only as a sorcery. + Daybound + 2/2 + + Wildsong Howler + Creature - Werewolf + Whenever this creature enters the battlefield or transforms into Wildsong Howler, look at the top six cards of your library. You may reveal a creature card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. + Nightbound + 4/4 + */ + private static final String howlpackPiper = "Howlpack Piper"; + private static final String wildsongHowler = "Wildsong Howler"; + + /* + Thing in the Ice + {1}{U} + Creature - Horror + Defender + Thing in the Ice enters the battlefield with four ice counters on it. + Whenever you cast an instant or sorcery spell, remove an ice counter from Thing in the Ice. Then if it has no ice counters on it, transform it. + + Awoken Horror + Creature - Kraken Horror + When this creature transforms into Awoken Horrow, return all non-Horror creatures to their owners' hands. + 7/8 + */ + private static final String thingInTheIce = "Thing in the Ice"; + private static final String awokenHorror = "Awoken Horror"; + + /* + Banners Raised + {R} + Instant + Creatures you control get +1/+0 until end of turn. + */ + private static final String bannersRaised = "Banners Raised"; + + /* + Phantasmal Image + {1}{U} + Creature - Illusion + You may have Phantasmal Image enter the battlefield as a copy of any creature on the battlefield, except it's an Illusion in addition to its other types and it gains "When this creature becomes the target of a spell or ability, sacrifice it." + */ + private static final String phantasmalImage = "Phantasmal Image"; + + /* + Delver of Secrets + {U} + Creature - Human Wizard + At the beginning of your upkeep, look at the top card of your library. You may reveal that card. If an instant or sorcery card is revealed this way, transform Delver of Secrets. + 1/1 + + Insectile Aberration + Creature - Human Insect + Flying + 3/2 + */ + private static final String delverOfSecrets = "Delver of Secrets"; + private static final String insectileAberration = "Insectile Aberration"; + + /* + Moonmist + {1}{G} + Instant + Transform all Humans. Prevent all combat damage that would be dealt this turn by creatures other than Werewolves and Wolves. (Only double-faced cards can be transformed.) + */ + private static final String moonmist = "Moonmist"; + + /* + Maskwood Nexus + {4} + Artifact + Creatures you control are every creature type. The same is true for creature spells you control and creature cards you own that aren't on the battlefield. + {3}, {T}: Create a 2/2 blue Shapeshifter creature token with changeling. + */ + private static final String maskwoodNexus = "Maskwood Nexus"; + + /* + Dress Down + {1}{U} + Enchantment + Flash + When Dress Down enters the battlefield, draw a card. + Creatures lose all abilities. + At the beginning of the end step, sacrifice Dress Down. + */ + private static final String dressDown = "Dress Down"; + + /* + Baithook Angler + {1}{U} + Creature - Human Peasant + Disturb {1}{U} + 2/1 + + Hook-Haunt Drifter + Creature - Spirit + Flying + If Hook-Haunt Drifter would be put into a graveyard from anywhere, exile it instead. + 1/2 + */ + private static final String baithookAngler = "Baithook Angler"; + private static final String hookHauntDrifter = "Hook-Haunt Drifter"; + + /* + Croaking Counterpart + {1}{G}{U} + Sorcery + Create a token that's a copy of target non-Frog creature, except it's a 1/1 green Frog. + Flashback {3}{G}{U} + */ + private static final String croakingCounterpart = "Croaking Counterpart"; + + /* + Abnormal Endurance + {1}{B} + Instant + Until end of turn, target creature gets +2/+0 and gains "When this creature dies, return it to the battlefield tapped under its owner's control." + */ + private static final String abnormalEndurance = "Abnormal Endurance"; + @Test public void NissaVastwoodSeerTest() { @@ -22,13 +295,13 @@ public class TransformTest extends CardTestPlayerBase { // When Nissa, Vastwood Seer enters the battlefield, you may search your library for a basic Forest card, reveal it, put it into your hand, then shuffle your library. // Whenever a land you control enters, if you control seven or more lands, exile Nissa, then return her to the battlefield transformed under her owner's control. - addCard(Zone.HAND, playerA, "Nissa, Vastwood Seer"); + addCard(Zone.HAND, playerA, nissaVastwoodSeer); addCard(Zone.BATTLEFIELD, playerB, "Forest", 2); // {G}{G}, Sacrifice Rootrunner: Put target land on top of its owner's library. addCard(Zone.BATTLEFIELD, playerB, "Rootrunner"); // {2}{G}{G} - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Nissa, Vastwood Seer", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nissaVastwoodSeer, true); playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Forest"); activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "{G}{G}", "Swamp"); @@ -40,38 +313,38 @@ public class TransformTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Rootrunner", 1); - assertPermanentCount(playerA, "Nissa, Vastwood Seer", 0); - assertPermanentCount(playerA, "Nissa, Sage Animist", 1); + assertPermanentCount(playerA, nissaVastwoodSeer, 0); + assertPermanentCount(playerA, nissaSageAnimist, 1); - assertCounterCount("Nissa, Sage Animist", CounterType.LOYALTY, 4); + assertCounterCount(nissaSageAnimist, CounterType.LOYALTY, 4); assertPermanentCount(playerA, "Forest", 6); assertPermanentCount(playerA, "Swamp", 1); } @Test public void LilianaHereticalHealer() { - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + addCard(Zone.BATTLEFIELD, playerA, silvercoatLion, 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); // Lifelink // Whenever another nontoken creature you control dies, exile Liliana Heretical Healer, then return her to the battlefield transformed under her owner's control. If you do, put a 2/2 black Zombie creature token onto the battlefield. - addCard(Zone.HAND, playerA, "Liliana, Heretical Healer"); + addCard(Zone.HAND, playerA, lilianaHereticalHealer); - addCard(Zone.HAND, playerB, "Lightning Bolt"); + addCard(Zone.HAND, playerB, lightningBolt); addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Liliana, Heretical Healer"); - castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, "Lightning Bolt", "Silvercoat Lion"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lilianaHereticalHealer); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, lightningBolt, silvercoatLion); setStopAt(1, PhaseStep.DECLARE_ATTACKERS); execute(); - assertGraveyardCount(playerA, "Silvercoat Lion", 1); - assertGraveyardCount(playerB, "Lightning Bolt", 1); + assertGraveyardCount(playerA, silvercoatLion, 1); + assertGraveyardCount(playerB, lightningBolt, 1); - assertPermanentCount(playerA, "Liliana, Heretical Healer", 0); - assertPermanentCount(playerA, "Liliana, Defiant Necromancer", 1); - assertCounterCount("Liliana, Defiant Necromancer", CounterType.LOYALTY, 3); + assertPermanentCount(playerA, lilianaHereticalHealer, 0); + assertPermanentCount(playerA, lilianaDefiantNecromancer, 1); + assertCounterCount(lilianaDefiantNecromancer, CounterType.LOYALTY, 3); assertPermanentCount(playerA, "Zombie Token", 1); } @@ -84,45 +357,45 @@ public class TransformTest extends CardTestPlayerBase { */ @Test public void LilianaHereticalHealer2() { - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + addCard(Zone.BATTLEFIELD, playerA, silvercoatLion, 1); // Lifelink // Whenever another nontoken creature you control dies, exile Liliana Heretical Healer, then return her to the battlefield transformed under her owner's control. If you do, put a 2/2 black Zombie creature token onto the battlefield. - addCard(Zone.BATTLEFIELD, playerA, "Liliana, Heretical Healer"); + addCard(Zone.BATTLEFIELD, playerA, lilianaHereticalHealer); // All creatures get -4/-4 until end of turn. - addCard(Zone.HAND, playerB, "Languish"); + addCard(Zone.HAND, playerB, languish); addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Languish"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, languish); setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); - assertGraveyardCount(playerB, "Languish", 1); - assertPermanentCount(playerA, "Liliana, Defiant Necromancer", 0); + assertGraveyardCount(playerB, languish, 1); + assertPermanentCount(playerA, lilianaDefiantNecromancer, 0); assertPermanentCount(playerA, "Zombie Token", 0); - assertGraveyardCount(playerA, "Silvercoat Lion", 1); - assertGraveyardCount(playerA, "Liliana, Heretical Healer", 1); + assertGraveyardCount(playerA, silvercoatLion, 1); + assertGraveyardCount(playerA, lilianaHereticalHealer, 1); } @Test public void TestEnchantmentToCreature() { - addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 1); - addCard(Zone.GRAVEYARD, playerA, "Lightning Bolt", 1); - addCard(Zone.GRAVEYARD, playerA, "Fireball", 1); - addCard(Zone.GRAVEYARD, playerA, "Infernal Scarring", 1); + addCard(Zone.GRAVEYARD, playerA, silvercoatLion, 1); + addCard(Zone.GRAVEYARD, playerA, lightningBolt, 1); + addCard(Zone.GRAVEYARD, playerA, fireball, 1); + addCard(Zone.GRAVEYARD, playerA, infernalScarring, 1); // {B}: Put the top card of your library into your graveyard. // Delirium &mdash At the beginning of your end step, if there are four or more card types among cards in your graveyard, transform Autumnal Gloom. - addCard(Zone.BATTLEFIELD, playerA, "Autumnal Gloom"); + addCard(Zone.BATTLEFIELD, playerA, autumnalGloom); setStopAt(2, PhaseStep.PRECOMBAT_MAIN); execute(); - assertPermanentCount(playerA, "Autumnal Gloom", 0); - assertPermanentCount(playerA, "Ancient of the Equinox", 1); + assertPermanentCount(playerA, autumnalGloom, 0); + assertPermanentCount(playerA, ancientOfTheEquinox, 1); } /** @@ -138,6 +411,7 @@ public class TransformTest extends CardTestPlayerBase { */ @Test public void testCultOfTheWaxingMoon() { + setStrictChooseMode(true); // Whenever a permanent you control transforms into a non-Human creature, put a 2/2 green Wolf creature token onto the battlefield. addCard(Zone.BATTLEFIELD, playerA, "Cult of the Waxing Moon"); // {1}{G} - Human Werewolf @@ -151,6 +425,7 @@ public class TransformTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Cult of the Waxing Moon", 1); assertPermanentCount(playerA, "Timber Shredder", 1); // Night-side card of Hinterland Logger, Werewolf (non-human) assertPermanentCount(playerA, "Wolf Token", 1); // wolf token created + assertAbilityCount(playerA, "Timber Shredder", WerewolfFrontTriggeredAbility.class, 0); // no front face ability } /** @@ -184,18 +459,42 @@ public class TransformTest extends CardTestPlayerBase { } @Test - public void testStartledAwakeMoonmist() { - addCard(Zone.HAND, playerA, "Startled Awake"); - addCard(Zone.HAND, playerA, "Moonmist"); - addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 11); - addCard(Zone.BATTLEFIELD, playerA, "Maskwood Nexus"); + public void testPersistentNightmareTrigger() { + // Target opponent puts the top thirteen cards of their library into their graveyard. + // {3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate this ability only any time you could cast a sorcery. + addCard(Zone.HAND, playerA, "Startled Awake"); // SORCERY {2}{U}{U}" + addCard(Zone.BATTLEFIELD, playerA, "Island", 9); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Startled Awake", playerB); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}{U}{U}"); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Moonmist"); + attack(3, playerA, "Persistent Nightmare"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.COMBAT_DAMAGE); + execute(); + + assertGraveyardCount(playerB, 13); + assertGraveyardCount(playerA, "Startled Awake", 0); + assertPermanentCount(playerA, "Persistent Nightmare", 0); // Night-side card of Startled Awake + assertHandCount(playerA, "Startled Awake", 1); + } + + @Test + public void testStartledAwakeMoonmist() { + addCard(Zone.HAND, playerA, "Startled Awake"); + addCard(Zone.HAND, playerA, moonmist); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 11); + addCard(Zone.BATTLEFIELD, playerA, maskwoodNexus); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Startled Awake", playerB); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}{U}{U}"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, moonmist); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -241,6 +540,25 @@ public class TransformTest extends CardTestPlayerBase { assertPermanentCount(playerB, "Lambholt Pacifist", 1); } + @Test + public void testDisturbExile() { + addCard(Zone.GRAVEYARD, playerA, baithookAngler); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + addCard(Zone.HAND, playerB, lightningBolt); + + // Disturb {1}{U} + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hookHauntDrifter + " using Disturb"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, lightningBolt, hookHauntDrifter); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, hookHauntDrifter, 0); + assertExileCount(playerA, baithookAngler, 1); + } + /** * Mirror Mockery copies the front face of a Transformed card rather than * the current face. @@ -251,7 +569,7 @@ public class TransformTest extends CardTestPlayerBase { * Trespasser. */ @Test - public void testTransformCopyrnansformed() { + public void testTransformCopyTransformed() { // Skulk (This creature can't be blocked by creatures with greater power.) // When Uninvited Geist deals combat damage to a player, transform it. addCard(Zone.BATTLEFIELD, playerA, "Uninvited Geist"); // Creature 2/2 {2}{U} @@ -272,7 +590,7 @@ public class TransformTest extends CardTestPlayerBase { setStopAt(3, PhaseStep.COMBAT_DAMAGE); execute(); - assertLife(playerB, 15); + assertLife(playerB, 20 - 2 - 3); assertPermanentCount(playerB, "Mirror Mockery", 1); assertPermanentCount(playerA, "Unimpeded Trespasser", 1); @@ -293,16 +611,16 @@ public class TransformTest extends CardTestPlayerBase { // Transformed side: Avacyn, the Purifier - Creature 6/5 // Flying // When this creature transforms into Avacyn, the Purifier, it deals 3 damage to each other creature and each opponent. - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); - addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, silvercoatLion); + addCard(Zone.HAND, playerA, lightningBolt); addCard(Zone.BATTLEFIELD, playerA, "Mountain"); // Devoid // {2}{C}: Exile another target creature, then return it to the battlefield tapped under its owner's control. - addCard(Zone.BATTLEFIELD, playerB, "Eldrazi Displacer", 1); + addCard(Zone.BATTLEFIELD, playerB, eldraziDisplacer, 1); addCard(Zone.BATTLEFIELD, playerB, "Wastes", 3); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Silvercoat Lion"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, silvercoatLion); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, true); activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}{C}", "Archangel Avacyn"); @@ -310,10 +628,10 @@ public class TransformTest extends CardTestPlayerBase { setStopAt(3, PhaseStep.PRECOMBAT_MAIN); execute(); - assertGraveyardCount(playerA, "Lightning Bolt", 1); - assertGraveyardCount(playerA, "Silvercoat Lion", 1); + assertGraveyardCount(playerA, lightningBolt, 1); + assertGraveyardCount(playerA, silvercoatLion, 1); - assertPermanentCount(playerB, "Eldrazi Displacer", 1); + assertPermanentCount(playerB, eldraziDisplacer, 1); assertPermanentCount(playerA, "Avacyn, the Purifier", 0); assertPermanentCount(playerA, "Archangel Avacyn", 1); } @@ -355,20 +673,20 @@ public class TransformTest extends CardTestPlayerBase { // Ravager of the Fells // Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls. // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Ravager of the Fells. - addCard(Zone.HAND, playerA, "Huntmaster of the Fells"); // Creature {2}{R}{G} + addCard(Zone.HAND, playerA, huntmasterOfTheFells); // Creature {2}{R}{G} addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); // Devoid // {2}{C}: Exile another target creature, then return it to the battlefield tapped under its owner's control. - addCard(Zone.HAND, playerB, "Eldrazi Displacer", 1); // Creature {2}{W} + addCard(Zone.HAND, playerB, eldraziDisplacer, 1); // Creature {2}{W} addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); addCard(Zone.BATTLEFIELD, playerB, "Wastes", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Huntmaster of the Fells"); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Eldrazi Displacer"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, huntmasterOfTheFells); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, eldraziDisplacer); - activateAbility(4, PhaseStep.UPKEEP, playerB, "{2}{C}", "Huntmaster of the Fells", "At the beginning of each upkeep"); + activateAbility(4, PhaseStep.UPKEEP, playerB, "{2}{C}", huntmasterOfTheFells, "At the beginning of each upkeep"); setStopAt(4, PhaseStep.PRECOMBAT_MAIN); execute(); @@ -376,11 +694,11 @@ public class TransformTest extends CardTestPlayerBase { assertLife(playerA, 24); assertPermanentCount(playerA, "Wolf Token", 2); - assertPermanentCount(playerB, "Eldrazi Displacer", 1); + assertPermanentCount(playerB, eldraziDisplacer, 1); - assertPermanentCount(playerA, "Ravager of the Fells", 0); - assertPermanentCount(playerA, "Huntmaster of the Fells", 1); - assertPowerToughness(playerA, "Huntmaster of the Fells", 2, 2); + assertPermanentCount(playerA, ravagerOfTheFells, 0); + assertPermanentCount(playerA, huntmasterOfTheFells, 1); + assertPowerToughness(playerA, huntmasterOfTheFells, 2, 2); assertTappedCount("Plains", true, 2); assertTappedCount("Wastes", true, 1); } @@ -392,50 +710,50 @@ public class TransformTest extends CardTestPlayerBase { // Ravager of the Fells // Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent and 2 damage to up to one target creature that player controls. // At the beginning of each upkeep, if a player cast two or more spells last turn, transform Ravager of the Fells. - addCard(Zone.HAND, playerA, "Huntmaster of the Fells"); // Creature {2}{R}{G} - addCard(Zone.HAND, playerA, "Silvercoat Lion", 2); + addCard(Zone.HAND, playerA, huntmasterOfTheFells); // Creature {2}{R}{G} + addCard(Zone.HAND, playerA, silvercoatLion, 2); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Huntmaster of the Fells", true); - castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion", true); - castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, huntmasterOfTheFells, true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, silvercoatLion, true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, silvercoatLion); setStopAt(4, PhaseStep.PRECOMBAT_MAIN); execute(); assertLife(playerA, 24); assertLife(playerB, 18); assertPermanentCount(playerA, "Wolf Token", 2); - assertPermanentCount(playerA, "Silvercoat Lion", 2); - assertPermanentCount(playerA, "Ravager of the Fells", 0); - assertPermanentCount(playerA, "Huntmaster of the Fells", 1); - assertPowerToughness(playerA, "Huntmaster of the Fells", 2, 2); + assertPermanentCount(playerA, silvercoatLion, 2); + assertPermanentCount(playerA, ravagerOfTheFells, 0); + assertPermanentCount(playerA, huntmasterOfTheFells, 1); + assertPowerToughness(playerA, huntmasterOfTheFells, 2, 2); } @Test public void testWildsongHowlerTrigger() { // The only Daybound/Nightbound card with a Transforms trigger on the back side removeAllCardsFromLibrary(playerA); - addCard(Zone.HAND, playerA, "Howlpack Piper", 2); // Creature {2}{R}{G} - addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 50); + addCard(Zone.HAND, playerA, howlpackPiper, 2); // Creature {2}{R}{G} + addCard(Zone.LIBRARY, playerA, silvercoatLion, 50); addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Howlpack Piper"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, howlpackPiper); setChoice(playerA, true); //Transform trigger - addTarget(playerA, "Silvercoat Lion"); + addTarget(playerA, silvercoatLion); - castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Howlpack Piper"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, howlpackPiper); setChoice(playerA, true); //ETB trigger - addTarget(playerA, "Silvercoat Lion"); + addTarget(playerA, silvercoatLion); setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); setStrictChooseMode(true); execute(); - assertPermanentCount(playerA, "Wildsong Howler", 2); - assertPermanentCount(playerA, "Howlpack Piper", 0); // They should be both transformed - assertHandCount(playerA, "Silvercoat Lion", 3); + assertPermanentCount(playerA, wildsongHowler, 2); + assertPermanentCount(playerA, howlpackPiper, 0); // They should be both transformed + assertHandCount(playerA, silvercoatLion, 3); assertHandCount(playerA, 3); //The two Silvercoat Lions from triggers and 1 from natural card draw } @@ -455,37 +773,37 @@ public class TransformTest extends CardTestPlayerBase { // Thing in the Ice enters the battlefield with four ice counters on it. // Whenever you cast an instant or sorcery spell, remove an ice counter from Thing in the Ice. // Then if it has no ice counters on it, transform it. - addCard(Zone.HAND, playerA, "Thing in the Ice"); // Creature {1}{U} + addCard(Zone.HAND, playerA, thingInTheIce); // Creature {1}{U} // Creatures you control get +1/+0 until end of turn. - addCard(Zone.HAND, playerA, "Banners Raised", 4); // Creature {R} + addCard(Zone.HAND, playerA, bannersRaised, 4); // Creature {R} addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); addCard(Zone.BATTLEFIELD, playerA, "Island", 1); // You may have Phantasmal Image enter the battlefield as a copy of any creature // on the battlefield, except it's an Illusion in addition to its other types and // it has "When this creature becomes the target of a spell or ability, sacrifice it." - addCard(Zone.HAND, playerB, "Phantasmal Image", 1); // Creature {1}{U} + addCard(Zone.HAND, playerB, phantasmalImage, 1); // Creature {1}{U} addCard(Zone.BATTLEFIELD, playerB, "Island", 2); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thing in the Ice"); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Banners Raised"); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Banners Raised"); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Banners Raised"); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Banners Raised"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, thingInTheIce); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bannersRaised); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bannersRaised); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bannersRaised); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bannersRaised); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, phantasmalImage); setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); assertLife(playerA, 20); - assertGraveyardCount(playerA, "Banners Raised", 4); - assertPermanentCount(playerA, "Thing in the Ice", 0); - assertPermanentCount(playerA, "Awoken Horror", 1); - assertPowerToughness(playerA, "Awoken Horror", 7, 8); + assertGraveyardCount(playerA, bannersRaised, 4); + assertPermanentCount(playerA, thingInTheIce, 0); + assertPermanentCount(playerA, awokenHorror, 1); + assertPowerToughness(playerA, awokenHorror, 7, 8); - assertPermanentCount(playerB, "Awoken Horror", 1); - assertPowerToughness(playerB, "Awoken Horror", 7, 8); + assertPermanentCount(playerB, awokenHorror, 1); + assertPowerToughness(playerB, awokenHorror, 7, 8); } @@ -493,45 +811,45 @@ public class TransformTest extends CardTestPlayerBase { public void testMoonmistDelver() { addCard(Zone.BATTLEFIELD, playerA, "Island"); addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); - addCard(Zone.HAND, playerA, "Delver of Secrets"); - addCard(Zone.HAND, playerA, "Moonmist", 2); + addCard(Zone.HAND, playerA, delverOfSecrets); + addCard(Zone.HAND, playerA, moonmist, 2); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Delver of Secrets"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, delverOfSecrets); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Moonmist"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, moonmist); setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, "Delver of Secrets", 0); - assertPermanentCount(playerA, "Insectile Aberration", 1); + assertPermanentCount(playerA, delverOfSecrets, 0); + assertPermanentCount(playerA, insectileAberration, 1); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Moonmist"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, moonmist); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - assertPermanentCount(playerA, "Delver of Secrets", 1); - assertPermanentCount(playerA, "Insectile Aberration", 0); + assertPermanentCount(playerA, delverOfSecrets, 1); + assertPermanentCount(playerA, insectileAberration, 0); } @Test public void testMoonmistHuntmasterDressdown() { addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 6); - addCard(Zone.BATTLEFIELD, playerA, "Huntmaster of the Fells"); //Has on-transform triggers - addCard(Zone.BATTLEFIELD, playerA, "Maskwood Nexus"); //Make back side human + addCard(Zone.BATTLEFIELD, playerA, huntmasterOfTheFells); //Has on-transform triggers + addCard(Zone.BATTLEFIELD, playerA, maskwoodNexus); //Make back side human - addCard(Zone.HAND, playerA, "Dress Down"); //Creatures lose all abilities - addCard(Zone.HAND, playerA, "Moonmist", 2); + addCard(Zone.HAND, playerA, dressDown); //Creatures lose all abilities + addCard(Zone.HAND, playerA, moonmist, 2); - castSpell(1, PhaseStep.UPKEEP, playerA, "Dress Down"); + castSpell(1, PhaseStep.UPKEEP, playerA, dressDown); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Moonmist"); - checkPermanentCount("Huntmaster flipped", 1, PhaseStep.BEGIN_COMBAT, playerA, "Ravager of the Fells", 1); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Moonmist"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, moonmist); + checkPermanentCount("Huntmaster flipped", 1, PhaseStep.BEGIN_COMBAT, playerA, ravagerOfTheFells, 1); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, moonmist); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -539,8 +857,71 @@ public class TransformTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20); - assertGraveyardCount(playerA, "Dress Down", 1); - assertPermanentCount(playerA, "Huntmaster of the Fells", 1); + assertGraveyardCount(playerA, dressDown, 1); + assertPermanentCount(playerA, huntmasterOfTheFells, 1); assertPermanentCount(playerA, 6+1+1); } + + @Test + public void testTokenCopyTransformed() { + addCard(Zone.GRAVEYARD, playerA, baithookAngler); + addCard(Zone.BATTLEFIELD, playerA, "Breeding Pool", 5); + addCard(Zone.HAND, playerA, croakingCounterpart); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Hook-Haunt Drifter using Disturb"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, croakingCounterpart, hookHauntDrifter); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + for (Permanent token : currentGame.getBattlefield().getActivePermanents(playerA.getId(), currentGame)) { + if (!(token instanceof PermanentToken)) { + continue; + } + assertTrue(token.getSubtype(currentGame).contains(SubType.FROG)); + assertEquals(ObjectColor.GREEN, token.getColor(currentGame)); + } + } + + @Test + public void testFrontSaga() { + addCard(Zone.BATTLEFIELD, playerA, azusasManyJourneys); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, azusasManyJourneys, 0); + assertPermanentCount(playerA, likenessOfTheSeeker, 1); + assertPowerToughness(playerA, likenessOfTheSeeker, 3, 3); + assertAbilityCount(playerA, likenessOfTheSeeker, SagaAbility.class, 0); // does not have saga ability + assertLife(playerA, 20 + 3); + } + + /** + * testing if a double faced card gains a death trigger, it still works correctly + */ + @Test + public void testDiesTrigger() { + addCard(Zone.BATTLEFIELD, playerA, baithookAngler); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerA, abnormalEndurance); + + addCard(Zone.HAND, playerB, lightningBolt); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, lightningBolt, baithookAngler); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, abnormalEndurance, baithookAngler, lightningBolt); + + setStopAt(1, PhaseStep.DECLARE_ATTACKERS); + execute(); + + assertGraveyardCount(playerA, baithookAngler, 0); + assertGraveyardCount(playerB, lightningBolt, 1); + + assertPermanentCount(playerA, baithookAngler, 1); + assertTrue(getPermanent(baithookAngler, playerA).isTapped()); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopyCreatureCardToTokenImplTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopyCreatureCardToTokenImplTest.java index 32a18f84bbb..26cc4aa5df6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopyCreatureCardToTokenImplTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopyCreatureCardToTokenImplTest.java @@ -3,10 +3,13 @@ package org.mage.test.cards.copy; import mage.constants.CardType; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.game.permanent.Permanent; import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import static org.junit.jupiter.api.Assertions.assertTrue; + /** * * @author LevelX2 @@ -85,4 +88,22 @@ public class CopyCreatureCardToTokenImplTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Thrashing Brontodon", 1); assertType("Thrashing Brontodon", CardType.ARTIFACT, true); } + + @Test + public void testTokenCopyTransformedHasSecondFaceWithModifications() { + setStrictChooseMode(true); + + String hookHauntDrifter = "Hook-Haunt Drifter"; + addCard(Zone.GRAVEYARD, playerA, "Baithook Angler"); + addCard(Zone.BATTLEFIELD, playerB, "Faerie Artisans", 1); + addCard(Zone.BATTLEFIELD, playerA, "Breeding Pool", 5); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Hook-Haunt Drifter using Disturb"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + Permanent token = getPermanent(hookHauntDrifter, playerB); + assertTrue(token.getCardType(currentGame).contains(CardType.ARTIFACT)); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java index 257cae4f572..07909a33372 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modaldoublefaced/ModalDoubleFacedCardsTest.java @@ -907,6 +907,32 @@ public class ModalDoubleFacedCardsTest extends CardTestPlayerBase { execute(); } + @Test + public void test_Copy_AsSpell_Backside() { + addCard(Zone.HAND, playerA, "Alrund, God of the Cosmos", 1); // {3}{U}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + // + // Copy target creature spell you control, except it isn't legendary if the spell is legendary. + addCard(Zone.HAND, playerA, "Double Major", 1); // {G}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + + // cast mdf card + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 2); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hakka, Whispering Raven"); + // prepare copy of spell + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", "Hakka, Whispering Raven", "Hakka, Whispering Raven"); + checkStackSize("before copy spell", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true); + checkStackSize("after copy spell", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hakka, Whispering Raven", 2); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + @Test public void test_Copy_AsCloneFromPermanent() { addCard(Zone.HAND, playerA, "Akoum Warrior", 1); // {5}{R} @@ -1138,8 +1164,8 @@ public class ModalDoubleFacedCardsTest extends CardTestPlayerBase { Assert.assertNotNull(permanent); // MDFC on battlefield has only one side (not transformable) - Assert.assertFalse("server must not be transformable", permanent.isTransformable()); - Assert.assertNull("server must have not other side", permanent.getOtherFace()); +// Assert.assertFalse("server must not be transformable", permanent.isTransformable()); +// Assert.assertNull("server must have not other side", permanent.getOtherFace()); List rules = permanent.getRules(game); Assert.assertTrue("server must ignore side 2 - untap ability", rules.stream().noneMatch(r -> r.contains("Untap"))); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/RoomCardTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/RoomCardTest.java index c7f6aad84d6..b9d380b5c23 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/RoomCardTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/splitcards/RoomCardTest.java @@ -1,19 +1,191 @@ package org.mage.test.cards.cost.splitcards; -import mage.constants.CardType; -import mage.constants.EmptyNames; -import mage.constants.PhaseStep; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; +import mage.view.CardView; +import mage.view.GameView; +import mage.view.PlayerView; import org.junit.Test; import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + /** * @author oscscull */ public class RoomCardTest extends CardTestPlayerBase { + /* + Bottomless Pool // Locker Room + {U} + Enchantment - Room + When you unlock this door, return up to one target creature to its owner's hand. + (You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.) + Locker Room + {4}{U} + Enchantment -- Room + Whenever one or more creatures you control deal combat damage to a player, draw a card. + (You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.) + */ + private static final String bottomlessPoolLockerRoom = "Bottomless Pool // Locker Room"; + private static final String bottomlessPool = "Bottomless Pool"; + public static final String lockerRoom = "Locker Room"; + + + @Test + public void testCardViewLeftHalf() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool, true); + addTarget(playerA, TestPlayer.TARGET_SKIP); + + runCode("print card view", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(playerA); + PlayerView playerView = gameView.getPlayers().stream().findFirst().orElse(null); + assertNotNull(playerView, "Player view must be found"); + CardView cardView = playerView.getBattlefield().values() + .stream() + .filter(pv -> pv.getName().equals(bottomlessPool)) + .findFirst() + .orElse(null); + assertNotNull(cardView, "Locker Room card view must be found on battlefield"); + // permanent should have 3 abilities + // unlock ability + unlock trigger from right half + info + assertEquals(3, cardView.getRules().size(), "Locker Room must have 3 rules, has: " + cardView.getRules().size()); + assertEquals("{U}", cardView.getManaCostStr(), "Mana cost must be from left half"); + StringBuilder sb = new StringBuilder("\n" + cardView.getName()); + sb.append("\n - Types: ").append(cardView.getCardTypes()); + sb.append("\n - Subtypes: ").append(cardView.getSubTypes()); + sb.append("\n - Mana Cost: ").append(cardView.getManaCostStr()); + sb.append("\n - Abilities: "); + cardView.getRules().forEach(rule -> sb.append("\n * ").append(rule)); + logger.info(sb.toString()); + }); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + } + + @Test + public void testCardViewRightHalf() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lockerRoom, true); + + runCode("print card view", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(playerA); + PlayerView playerView = gameView.getPlayers().stream().findFirst().orElse(null); + assertNotNull(playerView, "Player view must be found"); + CardView cardView = playerView.getBattlefield().values() + .stream() + .filter(pv -> pv.getName().equals(lockerRoom)) + .findFirst() + .orElse(null); + assertNotNull(cardView, "Locker Room card view must be found on battlefield"); + // permanent should have 3 abilities + // unlock ability + unlock trigger from left half + info + assertEquals(3, cardView.getRules().size(), "Locker Room must have 3 rules, has: " + cardView.getRules().size()); + assertEquals("{4}{U}", cardView.getManaCostStr(), "Mana cost must be from right half"); + StringBuilder sb = new StringBuilder("\n" + cardView.getName()); + sb.append("\n - Types: ").append(cardView.getCardTypes()); + sb.append("\n - Subtypes: ").append(cardView.getSubTypes()); + sb.append("\n - Mana Cost: ").append(cardView.getManaCostStr()); + sb.append("\n - Abilities: "); + cardView.getRules().forEach(rule -> sb.append("\n * ").append(rule)); + logger.info(sb.toString()); + }); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + } + + @Test + public void testCardViewBothHalves() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool, true); + addTarget(playerA, TestPlayer.TARGET_SKIP); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}: Unlock the right half."); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + runCode("print card view", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(playerA); + PlayerView playerView = gameView.getPlayers().stream().findFirst().orElse(null); + assertNotNull(playerView, "Player view must be found"); + CardView cardView = playerView.getBattlefield().values() + .stream() + .filter(pv -> pv.getName().equals(bottomlessPoolLockerRoom)) + .findFirst() + .orElse(null); + assertNotNull(cardView, "Locker Room card view must be found on battlefield"); + // permanent should have 3 abilities + // 1 ability from both halves + // no unlock abilities + // 1 info + String manaCost = getPermanent(bottomlessPoolLockerRoom) + .getManaCost() + .stream() + .map(Objects::toString) + .reduce("", String::concat); + assertEquals(3, cardView.getRules().size(), "Locker Room must have 3 rules, has: " + cardView.getRules().size()); + assertEquals(manaCost, cardView.getManaCostStr(), "Mana cost must be combined from both halves"); + StringBuilder sb = new StringBuilder("\n" + cardView.getName()); + sb.append("\n - Types: ").append(cardView.getCardTypes()); + sb.append("\n - Subtypes: ").append(cardView.getSubTypes()); + sb.append("\n - Mana Cost: ").append(cardView.getManaCostStr()); + sb.append("\n - Abilities: "); + cardView.getRules().forEach(rule -> sb.append("\n * ").append(rule)); + logger.info(sb.toString()); + }); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + } + + @Test + public void testCardViewFullyLocked() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, bottomlessPoolLockerRoom); + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + + runCode("print card view", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + GameView gameView = getGameView(playerA); + PlayerView playerView = gameView.getPlayers().stream().findFirst().orElse(null); + assertNotNull(playerView, "Player view must be found"); + CardView cardView = playerView.getBattlefield().values() + .stream() + .filter(pv -> pv.getName().isEmpty()) + .findFirst() + .orElse(null); + assertNotNull(cardView, "Locked room card view must be found on battlefield"); + // permanent should have 3 abilities + // 1 info + // two unlock abilities + assertEquals(3, cardView.getRules().size(), "Locked room must have 3 rules, has: " + cardView.getRules().size()); + assertEquals("", cardView.getManaCostStr(), "Mana cost must be empty"); + StringBuilder sb = new StringBuilder("\n" + cardView.getName()); + sb.append("\n - Types: ").append(cardView.getCardTypes()); + sb.append("\n - Subtypes: ").append(cardView.getSubTypes()); + sb.append("\n - Mana Cost: ").append(cardView.getManaCostStr()); + sb.append("\n - Abilities: "); + cardView.getRules().forEach(rule -> sb.append("\n * ").append(rule)); + logger.info(sb.toString()); + }); + + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + } + // Bottomless pool is cast. It unlocks, and the trigger to return a creature // should bounce one of two grizzly bears. @Test @@ -23,13 +195,13 @@ public class RoomCardTest extends CardTestPlayerBase { // creature to its owner’s hand. // Locker Room {4}{U} Whenever one or more creatures you control deal combat // damage to a player, draw a card. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.BATTLEFIELD, playerA, "Island", 1); addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 2); checkPlayableAbility("playerA can cast Bottomless Pool", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Bottomless Pool", true); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); // Target one of playerB's "Grizzly Bears" with the return effect. addTarget(playerA, "Grizzly Bears"); @@ -43,11 +215,11 @@ public class RoomCardTest extends CardTestPlayerBase { // Verify that one "Grizzly Bears" has been returned to playerB's hand. assertHandCount(playerB, "Grizzly Bears", 1); // Verify that "Bottomless Pool" is on playerA's battlefield. - assertPermanentCount(playerA, "Bottomless Pool", 1); + assertPermanentCount(playerA, bottomlessPool, 1); // Verify that "Bottomless Pool" is an Enchantment. - assertType("Bottomless Pool", CardType.ENCHANTMENT, true); + assertType(bottomlessPool, CardType.ENCHANTMENT, true); // Verify that "Bottomless Pool" has the Room subtype. - assertSubtype("Bottomless Pool", SubType.ROOM); + assertSubtype(bottomlessPool, SubType.ROOM); } // Locker room is cast. It enters, and gives a coastal piracy effect that @@ -59,7 +231,7 @@ public class RoomCardTest extends CardTestPlayerBase { // creature to its owner’s hand. // Locker Room {4}{U} Whenever one or more creatures you control deal combat // damage to a player, draw a card. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.BATTLEFIELD, playerA, "Island", 5); // Cards to be drawn @@ -68,7 +240,7 @@ public class RoomCardTest extends CardTestPlayerBase { // 2 attackers addCard(Zone.BATTLEFIELD, playerA, "Memnite", 2); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Locker Room"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lockerRoom); attack(1, playerA, "Memnite"); attack(1, playerA, "Memnite"); // After combat damage, Memnites dealt combat damage to playerB (1 damage * 2). @@ -83,9 +255,9 @@ public class RoomCardTest extends CardTestPlayerBase { // Assertions after the first execute() (Locker Room and creatures are on // battlefield, combat resolved): - assertPermanentCount(playerA, "Locker Room", 1); - assertType("Locker Room", CardType.ENCHANTMENT, true); - assertSubtype("Locker Room", SubType.ROOM); + assertPermanentCount(playerA, lockerRoom, 1); + assertType(lockerRoom, CardType.ENCHANTMENT, true); + assertSubtype(lockerRoom, SubType.ROOM); assertPermanentCount(playerA, "Memnite", 2); setStrictChooseMode(true); @@ -102,13 +274,13 @@ public class RoomCardTest extends CardTestPlayerBase { // creature to its owner’s hand. // Locker Room {4}{U} Whenever one or more creatures you control deal combat // damage to a player, draw a card. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.BATTLEFIELD, playerA, "Island", 6); // 2 creatures owned by player A addCard(Zone.BATTLEFIELD, playerA, "Memnite", 2); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Locker Room"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lockerRoom); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); checkPlayableAbility("playerA can unlock Bottomless Pool", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}: Unlock the left half.", true); @@ -125,12 +297,12 @@ public class RoomCardTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Memnite", 1); // Verify that one "Memnite" has been returned to playerA's hand. assertHandCount(playerA, "Memnite", 1); - // Verify that "Bottomless Pool // Locker Room" is on playerA's battlefield. - assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1); - // Verify that "Bottomless Pool // Locker Room" is an Enchantment. - assertType("Bottomless Pool // Locker Room", CardType.ENCHANTMENT, true); - // Verify that "Bottomless Pool // Locker Room" has the Room subtype. - assertSubtype("Bottomless Pool // Locker Room", SubType.ROOM); + // Verify that bottomlessPoolLockerRoom is on playerA's battlefield. + assertPermanentCount(playerA, bottomlessPoolLockerRoom, 1); + // Verify that bottomlessPoolLockerRoom is an Enchantment. + assertType(bottomlessPoolLockerRoom, CardType.ENCHANTMENT, true); + // Verify that bottomlessPoolLockerRoom has the Room subtype. + assertSubtype(bottomlessPoolLockerRoom, SubType.ROOM); } @Test @@ -140,7 +312,7 @@ public class RoomCardTest extends CardTestPlayerBase { // creature to its owner’s hand. // Locker Room {4}{U} Whenever one or more creatures you control deal combat // damage to a player, draw a card. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.HAND, playerA, "Felidar Guardian"); addCard(Zone.BATTLEFIELD, playerA, "Island", 1); addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); @@ -148,7 +320,7 @@ public class RoomCardTest extends CardTestPlayerBase { // creatures owned by player A addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); // resolve spell cast waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // unlock and trigger bounce on Memnite @@ -160,7 +332,7 @@ public class RoomCardTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // etb and flicker on Bottomless Pool setChoice(playerA, "Yes"); - addTarget(playerA, "Bottomless Pool"); + addTarget(playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); setStopAt(1, PhaseStep.END_TURN); @@ -187,7 +359,7 @@ public class RoomCardTest extends CardTestPlayerBase { // creature to its owner’s hand. // Locker Room {4}{U} Whenever one or more creatures you control deal combat // damage to a player, draw a card. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.HAND, playerA, "Felidar Guardian"); addCard(Zone.BATTLEFIELD, playerA, "Island", 5); addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); @@ -196,7 +368,7 @@ public class RoomCardTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1); addCard(Zone.BATTLEFIELD, playerA, "Black Knight", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); // resolve spell cast waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // unlock and trigger bounce on Memnite @@ -208,7 +380,7 @@ public class RoomCardTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // etb and flicker on Bottomless Pool setChoice(playerA, "Yes"); - addTarget(playerA, "Bottomless Pool"); + addTarget(playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // can unlock again checkPlayableAbility("playerA can unlock Bottomless Pool", 1, PhaseStep.PRECOMBAT_MAIN, playerA, @@ -228,7 +400,7 @@ public class RoomCardTest extends CardTestPlayerBase { // Verify that one "Black Knight" has been returned to playerA's hand. assertHandCount(playerA, "Black Knight", 1); // Verify that "Bottomless Pool" is on playerA's battlefield. - assertPermanentCount(playerA, "Bottomless Pool", 1); + assertPermanentCount(playerA, bottomlessPool, 1); // Verify that "Felidar Guardian" is on playerA's battlefield. assertPermanentCount(playerA, "Felidar Guardian", 1); } @@ -240,11 +412,11 @@ public class RoomCardTest extends CardTestPlayerBase { // creature to its owner’s hand. // Locker Room {4}{U} Whenever one or more creatures you control deal combat // damage to a player, draw a card. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.BATTLEFIELD, playerA, "Island", 6); addCard(Zone.BATTLEFIELD, playerA, "Erratic Apparition", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); // resolve spell cast waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); setChoice(playerA, "When you unlock"); // x2 triggers @@ -262,8 +434,8 @@ public class RoomCardTest extends CardTestPlayerBase { execute(); // Assertions: - // Verify that "Bottomless Pool // Locker Room" is on playerA's battlefield. - assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1); + // Verify that bottomlessPoolLockerRoom is on playerA's battlefield. + assertPermanentCount(playerA, bottomlessPoolLockerRoom, 1); // Verify that "Erratic Apparition" is on playerA's battlefield. assertPermanentCount(playerA, "Erratic Apparition", 1); // Verify that "Erratic Apparition" has been pumped twice (etb + fully unlock) @@ -277,17 +449,17 @@ public class RoomCardTest extends CardTestPlayerBase { // creature to its owner’s hand. // Locker Room {4}{U} Whenever one or more creatures you control deal combat // damage to a player, draw a card. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.HAND, playerA, "See Double"); addCard(Zone.BATTLEFIELD, playerA, "Island", 5); addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1); addCard(Zone.BATTLEFIELD, playerA, "Ornithopter", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); // Copy spell on the stack castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "See Double"); setModeChoice(playerA, "1"); - addTarget(playerA, "Bottomless Pool"); + addTarget(playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 3); addTarget(playerA, "Memnite"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); @@ -303,7 +475,7 @@ public class RoomCardTest extends CardTestPlayerBase { // Verify that one "Ornithopter" has been returned to playerA's hand. assertHandCount(playerA, "Ornithopter", 1); // Verify that 2 "Bottomless Pool" are on playerA's battlefield. - assertPermanentCount(playerA, "Bottomless Pool", 2); + assertPermanentCount(playerA, bottomlessPool, 2); } @Test @@ -313,13 +485,13 @@ public class RoomCardTest extends CardTestPlayerBase { // creature to its owner's hand. // Locker Room {4}{U} Whenever one or more creatures you control deal combat // damage to a player, draw a card. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.HAND, playerA, "Clever Impersonator"); addCard(Zone.BATTLEFIELD, playerA, "Island", 6); addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1); addCard(Zone.BATTLEFIELD, playerA, "Ornithopter", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); addTarget(playerA, "Memnite"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); @@ -327,7 +499,7 @@ public class RoomCardTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Clever Impersonator"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); setChoice(playerA, "Yes"); - setChoice(playerA, "Bottomless Pool"); + setChoice(playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, @@ -347,7 +519,7 @@ public class RoomCardTest extends CardTestPlayerBase { assertHandCount(playerA, "Ornithopter", 1); // Verify that the original "Bottomless Pool" is on playerA's battlefield, and a // clone. - assertPermanentCount(playerA, "Bottomless Pool", 2); + assertPermanentCount(playerA, bottomlessPool, 2); } @Test @@ -366,11 +538,11 @@ public class RoomCardTest extends CardTestPlayerBase { // {U}{U}, Sacrifice this creature: Counter target spell with the same name as a // card exiled with this creature. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.HAND, playerA, "Twiddle"); addCard(Zone.BATTLEFIELD, playerA, "Mindreaver", 1); addCard(Zone.BATTLEFIELD, playerA, "Island", 6); - addCard(Zone.LIBRARY, playerA, "Bottomless Pool // Locker Room", 1); + addCard(Zone.LIBRARY, playerA, bottomlessPoolLockerRoom, 1); addCard(Zone.LIBRARY, playerA, "Plains", 2); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Twiddle"); @@ -384,10 +556,10 @@ public class RoomCardTest extends CardTestPlayerBase { addTarget(playerA, playerA); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}{U}, Sacrifice {this}:"); - addTarget(playerA, "Bottomless Pool"); + addTarget(playerA, bottomlessPool); setStopAt(1, PhaseStep.END_TURN); setStrictChooseMode(true); @@ -424,7 +596,7 @@ public class RoomCardTest extends CardTestPlayerBase { // Target creature and all other creatures with the same name as that creature // get -3/-3 until end of turn. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room", 4); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom, 4); addCard(Zone.HAND, playerA, "Cackling Counterpart"); addCard(Zone.HAND, playerA, "Bile Blight"); addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 17); @@ -432,17 +604,17 @@ public class RoomCardTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Opalescence"); // Cast Bottomless Pool (unlocked left half) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Locker Room (unlocked right half) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Locker Room"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lockerRoom); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Bottomless Pool then unlock Locker Room (both halves unlocked) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); @@ -451,7 +623,7 @@ public class RoomCardTest extends CardTestPlayerBase { // Create a fully locked room using Cackling Counterpart castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cackling Counterpart"); - addTarget(playerA, "Bottomless Pool // Locker Room"); + addTarget(playerA, bottomlessPoolLockerRoom); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Bile Blight targeting the fully locked room @@ -468,21 +640,21 @@ public class RoomCardTest extends CardTestPlayerBase { // then -2/-2 after Bile Blight (dies) assertPermanentCount(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 0); // Token, so nothing should be in grave - assertGraveyardCount(playerA, "Bottomless Pool // Locker Room", 0); + assertGraveyardCount(playerA, bottomlessPoolLockerRoom, 0); // Other rooms should NOT be affected by Bile Blight since they have different // names // Bottomless Pool: 1/1 base + 1/1 from anthem = 2/2 - assertPowerToughness(playerA, "Bottomless Pool", 2, 2); + assertPowerToughness(playerA, bottomlessPool, 2, 2); // Locker Room: 5/5 base + 1/1 from anthem = 6/6 - assertPowerToughness(playerA, "Locker Room", 6, 6); + assertPowerToughness(playerA, lockerRoom, 6, 6); // Bottomless Pool // Locker Room: 6/6 base + 1/1 from anthem = 7/7 - assertPowerToughness(playerA, "Bottomless Pool // Locker Room", 7, 7); + assertPowerToughness(playerA, bottomlessPoolLockerRoom, 7, 7); // Verify remaining rooms are still on battlefield - assertPermanentCount(playerA, "Bottomless Pool", 1); - assertPermanentCount(playerA, "Locker Room", 1); - assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1); + assertPermanentCount(playerA, bottomlessPool, 1); + assertPermanentCount(playerA, lockerRoom, 1); + assertPermanentCount(playerA, bottomlessPoolLockerRoom, 1); } @Test @@ -515,7 +687,7 @@ public class RoomCardTest extends CardTestPlayerBase { // Target creature and all other creatures with the same name as that creature // get -3/-3 until end of turn. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room", 4); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom, 4); addCard(Zone.HAND, playerA, "Cackling Counterpart"); addCard(Zone.HAND, playerA, "Bile Blight"); addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 17); @@ -523,17 +695,17 @@ public class RoomCardTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Opalescence"); // Cast Bottomless Pool (unlocked left half) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Locker Room (unlocked right half) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Locker Room"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lockerRoom); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Bottomless Pool then unlock Locker Room (both halves unlocked) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); @@ -542,12 +714,12 @@ public class RoomCardTest extends CardTestPlayerBase { // Create a fully locked room using Cackling Counterpart castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cackling Counterpart"); - addTarget(playerA, "Bottomless Pool // Locker Room"); + addTarget(playerA, bottomlessPoolLockerRoom); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Bile Blight targeting the half locked room castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bile Blight"); - addTarget(playerA, "Locker Room"); + addTarget(playerA, lockerRoom); setStopAt(1, PhaseStep.END_TURN); setStrictChooseMode(true); @@ -559,21 +731,21 @@ public class RoomCardTest extends CardTestPlayerBase { // since they share the "Locker Room" name component // Locker Room: 5/5 base + 1/1 from anthem - 3/3 from Bile Blight = 3/3 - assertPowerToughness(playerA, "Locker Room", 3, 3); + assertPowerToughness(playerA, lockerRoom, 3, 3); // Bottomless Pool // Locker Room: 6/6 base + 1/1 from anthem - 3/3 from Bile // Blight = 4/4 - assertPowerToughness(playerA, "Bottomless Pool // Locker Room", 4, 4); + assertPowerToughness(playerA, bottomlessPoolLockerRoom, 4, 4); // Other rooms should NOT be affected // Bottomless Pool: 1/1 base + 1/1 from anthem = 2/2 (unaffected) - assertPowerToughness(playerA, "Bottomless Pool", 2, 2); + assertPowerToughness(playerA, bottomlessPool, 2, 2); // Fully locked room: 0/0 base + 1/1 from anthem = 1/1 (unaffected) assertPowerToughness(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 1, 1); // Verify all rooms are still on battlefield - assertPermanentCount(playerA, "Bottomless Pool", 1); - assertPermanentCount(playerA, "Locker Room", 1); - assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1); + assertPermanentCount(playerA, bottomlessPool, 1); + assertPermanentCount(playerA, lockerRoom, 1); + assertPermanentCount(playerA, bottomlessPoolLockerRoom, 1); assertPermanentCount(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 1); } @@ -607,7 +779,7 @@ public class RoomCardTest extends CardTestPlayerBase { // Target creature and all other creatures with the same name as that creature // get -3/-3 until end of turn. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room", 4); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom, 4); addCard(Zone.HAND, playerA, "Cackling Counterpart"); addCard(Zone.HAND, playerA, "Bile Blight"); addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 17); @@ -615,17 +787,17 @@ public class RoomCardTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Opalescence"); // Cast Bottomless Pool (unlocked left half) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Locker Room (unlocked right half) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Locker Room"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lockerRoom); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Bottomless Pool then unlock Locker Room (both halves unlocked) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); @@ -634,12 +806,12 @@ public class RoomCardTest extends CardTestPlayerBase { // Create a fully locked room using Cackling Counterpart castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cackling Counterpart"); - addTarget(playerA, "Bottomless Pool // Locker Room"); + addTarget(playerA, bottomlessPoolLockerRoom); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Bile Blight targeting the fully locked room castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bile Blight"); - addTarget(playerA, "Bottomless Pool // Locker Room"); + addTarget(playerA, bottomlessPoolLockerRoom); setStopAt(1, PhaseStep.END_TURN); setStrictChooseMode(true); @@ -647,27 +819,27 @@ public class RoomCardTest extends CardTestPlayerBase { // Assertions: // All rooms except the fully locked room should be affected by Bile Blight - // since they all share name components with "Bottomless Pool // Locker Room" + // since they all share name components with bottomlessPoolLockerRoom // Bottomless Pool: 1/1 base + 1/1 from anthem - 3/3 from Bile Blight = -1/-1 // (dies) - assertPermanentCount(playerA, "Bottomless Pool", 0); - assertGraveyardCount(playerA, "Bottomless Pool // Locker Room", 1); + assertPermanentCount(playerA, bottomlessPool, 0); + assertGraveyardCount(playerA, bottomlessPoolLockerRoom, 1); // Locker Room: 5/5 base + 1/1 from anthem - 3/3 from Bile Blight = 3/3 - assertPowerToughness(playerA, "Locker Room", 3, 3); + assertPowerToughness(playerA, lockerRoom, 3, 3); // Bottomless Pool // Locker Room: 6/6 base + 1/1 from anthem - 3/3 from Bile // Blight = 4/4 - assertPowerToughness(playerA, "Bottomless Pool // Locker Room", 4, 4); + assertPowerToughness(playerA, bottomlessPoolLockerRoom, 4, 4); // Fully locked room should NOT be affected (different name) // Fully locked room: 0/0 base + 1/1 from anthem = 1/1 (unaffected) assertPowerToughness(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 1, 1); // Verify remaining rooms are still on battlefield - assertPermanentCount(playerA, "Locker Room", 1); - assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1); + assertPermanentCount(playerA, lockerRoom, 1); + assertPermanentCount(playerA, bottomlessPoolLockerRoom, 1); assertPermanentCount(playerA, EmptyNames.FULLY_LOCKED_ROOM.getTestCommand(), 1); } @@ -678,7 +850,7 @@ public class RoomCardTest extends CardTestPlayerBase { // creature to its owner's hand. // Locker Room {4}{U} Whenever one or more creatures you control deal combat // damage to a player, draw a card. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.HAND, playerA, "Counterspell"); addCard(Zone.HAND, playerA, "Campus Renovation"); addCard(Zone.BATTLEFIELD, playerA, "Island", 3); @@ -689,16 +861,16 @@ public class RoomCardTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears", 1); // Cast Bottomless Pool - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); // Counter it while on stack castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Counterspell"); - addTarget(playerA, "Bottomless Pool"); + addTarget(playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Use Campus Renovation to return it from graveyard castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Campus Renovation"); - addTarget(playerA, "Bottomless Pool // Locker Room"); + addTarget(playerA, bottomlessPoolLockerRoom); setStopAt(1, PhaseStep.END_TURN); setStrictChooseMode(true); @@ -749,26 +921,26 @@ public class RoomCardTest extends CardTestPlayerBase { // As Pithing Needle enters, choose a card name. // Activated abilities of sources with the chosen name can't be activated. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.HAND, playerA, "Pithing Needle"); addCard(Zone.BATTLEFIELD, playerA, "Opalescence"); addCard(Zone.BATTLEFIELD, playerA, "Diviner's Wand"); addCard(Zone.BATTLEFIELD, playerA, "Island", 20); // Cast Bottomless Pool (unlocked left half only) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Equip Diviner's Wand activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {3}"); - addTarget(playerA, "Bottomless Pool"); + addTarget(playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Pithing Needle naming the locked side castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle"); - setChoice(playerA, "Locker Room"); + setChoice(playerA, lockerRoom); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Validate that the room can activate the gained ability @@ -789,7 +961,7 @@ public class RoomCardTest extends CardTestPlayerBase { execute(); // Verify the room is now fully unlocked - assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1); + assertPermanentCount(playerA, bottomlessPoolLockerRoom, 1); } // Test converting one permanent into one room, then another (the room halves @@ -813,14 +985,14 @@ public class RoomCardTest extends CardTestPlayerBase { // or // land until end of turn. - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.HAND, playerA, "Surgical Suite // Hospital Room"); addCard(Zone.BATTLEFIELD, playerA, "Mirage Mirror"); addCard(Zone.BATTLEFIELD, playerA, "Tundra", 20); addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1); // Cast Bottomless Pool (unlocked left half only) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); @@ -832,7 +1004,7 @@ public class RoomCardTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: {this} becomes a copy"); - addTarget(playerA, "Bottomless Pool // Locker Room"); + addTarget(playerA, bottomlessPoolLockerRoom); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}: Unlock the right half."); @@ -848,7 +1020,7 @@ public class RoomCardTest extends CardTestPlayerBase { execute(); // Verify unlocked Bottomless pool - assertPermanentCount(playerA, "Bottomless Pool // Locker Room", 1); + assertPermanentCount(playerA, bottomlessPoolLockerRoom, 1); // Verify unlocked Surgical Suite assertPermanentCount(playerA, "Surgical Suite", 1); // Verify mirage mirror is Hospital Room @@ -873,32 +1045,33 @@ public class RoomCardTest extends CardTestPlayerBase { // other types, // and it has "{2}{U}{U}: Return Sakashima the Impostor to its owner's hand at // the beginning of the next end step." + String sakashimaTheImpostor = "Sakashima the Impostor"; - addCard(Zone.HAND, playerA, "Bottomless Pool // Locker Room"); + addCard(Zone.HAND, playerA, bottomlessPoolLockerRoom); addCard(Zone.BATTLEFIELD, playerA, "Island", 10); + addCard(Zone.BATTLEFIELD, playerA, "Opalescence"); + addCard(Zone.HAND, playerA, sakashimaTheImpostor); - addCard(Zone.HAND, playerB, "Sakashima the Impostor"); - addCard(Zone.BATTLEFIELD, playerB, "Island", 10); // Cast Bottomless Pool (unlocked left half only) - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bottomless Pool"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bottomlessPool); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); addTarget(playerA, TestPlayer.TARGET_SKIP); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); // Cast Sakashima copying the room - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Sakashima the Impostor"); - setChoice(playerB, "Yes"); // Choose to copy - waitStackResolved(2, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sakashimaTheImpostor); + setChoice(playerA, "Yes"); // Choose to copy + setChoice(playerA, bottomlessPool); - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}: Unlock the right half."); - waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); - - setStopAt(2, PhaseStep.END_TURN); + setStopAt(1, PhaseStep.END_TURN); setStrictChooseMode(true); execute(); - // Verify Sakashima entered and is copying the room - assertPermanentCount(playerB, "Sakashima the Impostor", 1); + // Verify Sakashima dies to state-based actions + // copies room and because sakashima has no unlocked designations, its mana value is 0 + // opalescence makes it a 0/0 and it dies + assertPermanentCount(playerA, sakashimaTheImpostor, 0); + assertGraveyardCount(playerA, sakashimaTheImpostor, 1); } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/khc/EtherealValkyrieTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/khc/EtherealValkyrieTest.java index dcc3b7670fd..94bd1b50a90 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/khc/EtherealValkyrieTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/khc/EtherealValkyrieTest.java @@ -36,6 +36,8 @@ public class EtherealValkyrieTest extends CardTestPlayerBase { private static final String alloyMyr = "Alloy Myr"; // Land private static final String exoticOrchard = "Exotic Orchard"; + // {U} Creature-Planeswalker TDFC + private static final String tamiyo = "Tamiyo, Inquisitive Student"; /** * Test that a regular card is playable. @@ -202,4 +204,27 @@ public class EtherealValkyrieTest extends CardTestPlayerBase { setStopAt(3, PhaseStep.PRECOMBAT_MAIN); execute(); } + + /** + * Test a TDFC, which should be castable. + */ + @Test + public void testTransformingDoubleFacedCard() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + addCard(Zone.HAND, playerA, etherealValkyrie); + addCard(Zone.HAND, playerA, tamiyo); + + setStrictChooseMode(true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, etherealValkyrie); + addTarget(playerA, tamiyo); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell {U}"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + + execute(); + assertPermanentCount(playerA, tamiyo, 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/GwenStacyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/GwenStacyTest.java index 223f35b68c7..b6cf20ff645 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/GwenStacyTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/GwenStacyTest.java @@ -3,7 +3,6 @@ package org.mage.test.cards.single.spm; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -33,7 +32,6 @@ public class GwenStacyTest extends CardTestPlayerBase { private static final String ghostSpider = "Ghost-Spider"; @Test - @Ignore("Enable after transform mdfc rework") public void testGhostSpider() { setStrictChooseMode(true); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/PeterParkerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/PeterParkerTest.java index 38eeace4a37..38fe6fa6e9f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/PeterParkerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/PeterParkerTest.java @@ -7,7 +7,6 @@ import mage.constants.PhaseStep; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; -import org.junit.Ignore; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -18,19 +17,20 @@ import org.mage.test.serverside.base.CardTestPlayerBase; public class PeterParkerTest extends CardTestPlayerBase { /* - Peter Parker - {1}{W} - Legendary Creature - Human Scientist Hero - When Peter Parker enters, create a 2/1 green Spider creature token with reach. - {1}{G}{W}{U}: Transform Peter Parker. Activate only as a sorcery. - Amazing Spider-Man - {1}{G}{W}{U} - Legendary Creature - Spider Human Hero - Vigilance, reach - Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}. - 4/4 - */ + Peter Parker + {1}{W} + Legendary Creature - Human Scientist Hero + When Peter Parker enters, create a 2/1 green Spider creature token with reach. + {1}{G}{W}{U}: Transform Peter Parker. Activate only as a sorcery. + Amazing Spider-Man + {1}{G}{W}{U} + Legendary Creature - Spider Human Hero + Vigilance, reach + Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}. + 4/4 + */ private static final String peterParker = "Peter Parker"; + public static final String amazingSpiderMan = "Amazing Spider-Man"; /* @@ -73,8 +73,16 @@ public class PeterParkerTest extends CardTestPlayerBase { */ private static final String balduvianBears = "Balduvian Bears"; + /* + Unsummon + {U} + Instant + Return target creature to its owner's hand. + */ + private static final String unsummon = "Unsummon"; + + @Test - @Ignore("Enable after MDFC rework") public void testAmazingSpiderMan() { setStrictChooseMode(true); @@ -91,7 +99,7 @@ public class PeterParkerTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 8); addCard(Zone.BATTLEFIELD, playerA, "Tundra", 8); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Amazing Spider-Man", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, amazingSpiderMan, true); activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "tap all"); // tap bears, addCard command isn't working to set tapped waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); @@ -112,4 +120,131 @@ public class PeterParkerTest extends CardTestPlayerBase { execute(); } + + @Test + public void testTransform() { + setStrictChooseMode(true); + + addCustomCardWithAbility("tap all creatures", playerA, new SimpleActivatedAbility( + new TapAllEffect(new FilterCreaturePermanent(SubType.BEAR, "bears")), + new ManaCostsImpl<>("") + )); + + addCard(Zone.HAND, playerA, peterParker); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 8); + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 8); + addCard(Zone.BATTLEFIELD, playerA, balduvianBears); + addCard(Zone.HAND, playerA, adelbertSteiner); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "tap all"); // tap bears, addCard command isn't working to set tapped + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, peterParker, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{G}{W}{U}: Transform"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, adelbertSteiner + " with Web-slinging"); + setChoice(playerA, balduvianBears); // return to hand + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, amazingSpiderMan, 1); + assertPermanentCount(playerA, adelbertSteiner, 1); + assertPermanentCount(playerA, balduvianBears, 0); + assertHandCount(playerA, balduvianBears, 1); + } + + /** + * test that MDFC doesn't have static ability from one side after transforming + */ + @Test + public void testTransformLosesWebSlinging() { + setStrictChooseMode(true); + + addCustomCardWithAbility("tap all creatures", playerA, new SimpleActivatedAbility( + new TapAllEffect(new FilterCreaturePermanent(SubType.BEAR, "bears")), + new ManaCostsImpl<>("") + )); + addCustomEffect_TargetTransform(playerA); + + addCard(Zone.HAND, playerA, peterParker); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 8); + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 8); + addCard(Zone.BATTLEFIELD, playerA, balduvianBears); + addCard(Zone.HAND, playerA, adelbertSteiner); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "tap all"); // tap bears, addCard command isn't working to set tapped + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, peterParker, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{G}{W}{U}: Transform"); // transform to Spider-Man + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + checkPlayableAbility("card in hand has web-slinging", 1, PhaseStep.PRECOMBAT_MAIN, + playerA, "Cast " + adelbertSteiner + " with Web-slinging", true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target transform", amazingSpiderMan); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + checkPlayableAbility("card in hand does not have web-slinging", 1, PhaseStep.PRECOMBAT_MAIN, + playerA, "Cast " + adelbertSteiner + " with Web-slinging", false); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, amazingSpiderMan, 0); + assertPermanentCount(playerA, peterParker, 1); + assertHandCount(playerA, adelbertSteiner, 1); + assertPermanentCount(playerA, balduvianBears, 1); + } + + /** + * test showing if a transformed MDFC gets re-cast, it won't trigger effects from the other face + */ + @Test + public void testTransformCastSecondSideDoesntTriggerFront() { + setStrictChooseMode(true); + + addCustomCardWithAbility("tap all creatures", playerA, new SimpleActivatedAbility( + new TapAllEffect(new FilterCreaturePermanent(SubType.BEAR, "bears")), + new ManaCostsImpl<>("") + )); + addCustomEffect_TargetTransform(playerA); + + addCard(Zone.HAND, playerA, peterParker); + addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 8); + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 8); + addCard(Zone.BATTLEFIELD, playerA, balduvianBears); + addCard(Zone.HAND, playerA, adelbertSteiner); + addCard(Zone.HAND, playerA, unsummon); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "tap all"); // tap bears, addCard command isn't working to set tapped + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, amazingSpiderMan, true); + + checkPlayableAbility("card in hand has web-slinging", 1, PhaseStep.PRECOMBAT_MAIN, + playerA, "Cast " + adelbertSteiner + " with Web-slinging", true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target transform", amazingSpiderMan); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + checkPlayableAbility("card in hand does not have web-slinging", 1, PhaseStep.PRECOMBAT_MAIN, + playerA, "Cast " + adelbertSteiner + " with Web-slinging", false); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, unsummon, peterParker, true); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, amazingSpiderMan); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, amazingSpiderMan, 1); + assertPermanentCount(playerA, peterParker, 0); + assertHandCount(playerA, adelbertSteiner, 1); + assertPermanentCount(playerA, balduvianBears, 1); + assertPermanentCount(playerA, "Spider Token", 0); + currentGame.getState().getTriggers().forEach( + (key, value) -> logger.info(key + " - " + value) + ); + } } \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ReturnToHandEffectsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ReturnToHandEffectsTest.java index 2fc31b9d548..6f34ecedda6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ReturnToHandEffectsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ReturnToHandEffectsTest.java @@ -213,14 +213,12 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wind Zendikon", "Tangled Vale"); - // TODO: investigate why MDFC zcc moves separatedly. runCode("1: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, - (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Vale", 2, 1, 1, 2)); + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Vale", 2, 2, 2, 2)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Tangled Vale"); - // TODO: investigate why MDFC zcc moves separatedly. runCode("2: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, - (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 2, 2, 4)); + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 4, 4, 4)); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -244,14 +242,12 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wind Zendikon", "Riverglide Pathway"); - // TODO: investigate why MDFC zcc moves separatedly. runCode("1: check zcc pre disfigure", 1, PhaseStep.BEGIN_COMBAT, playerA, - (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Riverglide Pathway", 2, 1, 2, 1)); + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Riverglide Pathway", 2, 2, 2, 2)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Riverglide Pathway"); - // TODO: investigate why MDFC zcc moves separatedly. runCode("2: check zcc post disfigure", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, - (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Riverglide Pathway", 2, 4, 2)); + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Riverglide Pathway", 4, 4, 4)); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -275,14 +271,12 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wind Zendikon", "Lavaglide Pathway"); - // TODO: investigate why MDFC zcc moves separatedly. runCode("1: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, - (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Lavaglide Pathway", 2, 1, 1, 2)); + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Lavaglide Pathway", 2, 2, 2, 2)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Lavaglide Pathway"); - // TODO: investigate why MDFC zcc moves separatedly. runCode("2: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, - (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Riverglide Pathway", 2, 2, 4)); + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Riverglide Pathway", 4, 4, 4)); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -335,14 +329,12 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Demonic Vigor", "Tangled Florahedron"); - // TODO: investigate why MDFC zcc moves separatedly. runCode("1: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, - (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Florahedron", 3, 2, 3, 2)); + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Florahedron", 3, 3, 3, 3)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Tangled Florahedron"); - // TODO: investigate why MDFC zcc moves separatedly. runCode("2: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, - (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 3, 5, 3)); + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 5, 5, 5)); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -403,25 +395,25 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tangled Florahedron"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Demonic Vigor", "Tangled Florahedron"); - // TODO: investigate why MDFC zcc moves separatedly. + runCode("1: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, - (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Florahedron", 3, 2, 3, 2)); + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Florahedron", 3, 3, 3, 3)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Disfigure", "Tangled Florahedron", true); - // TODO: investigate why MDFC zcc moves separatedly. + runCode("2: check zcc card", 1, PhaseStep.BEGIN_COMBAT, playerA, - (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 3, 5, 3)); + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 5, 5, 5)); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Tangled Florahedron"); waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Demonic Vigor", "Tangled Florahedron", true); - // TODO: investigate why MDFC zcc moves separatedly. + runCode("3: check zcc", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, - (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Florahedron", 7, 4, 7, 4)); + (String info, Player player, Game game) -> checkZCCMDFCPermanent(info, player, game, "Tangled Florahedron", 7, 7, 7, 7)); waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Disfigure", "Tangled Florahedron", true); - // TODO: investigate why MDFC zcc moves separatedly. + runCode("4: check zcc card", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, - (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 5, 9, 5)); + (String info, Player player, Game game) -> checkZCCMDFCCardInHand(info, player, game, "Tangled Florahedron", 9, 9, 9)); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); @@ -458,20 +450,19 @@ public class ReturnToHandEffectsTest extends CardTestPlayerBase { runCode("3: check zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, (String info, Player player, Game game) -> checkZCCNormalPermanent(info, player, game, "Carrion Feeder", 5, 5)); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Coat with Venom", "Carrion Feeder", true); - runCode("4: check graveyard zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, - (String info, Player player, Game game) -> checkZCCCardInGraveyard(info, player, game, "Carrion Feeder", 6)); + runCode("4: check hand zcc", 1, PhaseStep.BEGIN_COMBAT, playerA, + (String info, Player player, Game game) -> checkZCCNormalCardInHand(info, player, game, "Carrion Feeder", 7)); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // Vigor tries to return the Carrion Feeder card with zcc 4, so 6 doesn't return. assertGraveyardCount(playerA, "Disfigure", 1); assertGraveyardCount(playerA, "Demonic Vigor", 1); assertGraveyardCount(playerA, "Makeshift Mannequin", 1); - assertGraveyardCount(playerA, "Carrion Feeder", 1); + assertGraveyardCount(playerA, "Carrion Feeder", 0); assertPermanentCount(playerA, "Carrion Feeder", 0); - assertHandCount(playerA, "Carrion Feeder", 0); + assertHandCount(playerA, "Carrion Feeder", 1); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index 3a849814cb5..133d92dd28a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -7,6 +7,8 @@ import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectsList; import mage.cards.Card; +import mage.cards.DoubleFacedCard; +import mage.cards.DoubleFacedCardHalf; import mage.cards.decks.Deck; import mage.cards.decks.DeckCardLists; import mage.cards.decks.importer.DeckImporter; @@ -773,6 +775,12 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement if (gameZone == Zone.BATTLEFIELD) { for (int i = 0; i < count; i++) { Card newCard = cardInfo.createCard(); + if (newCard instanceof DoubleFacedCard) { + DoubleFacedCardHalf rightHalf = ((DoubleFacedCard) newCard).getRightHalfCard(); + if (rightHalf.getName().equals(cardName) && rightHalf.isPermanent()) { + newCard = rightHalf; + } + } getBattlefieldCards(player).add(new PutToBattlefieldInfo( newCard, tapped @@ -781,7 +789,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement // TODO: is it bugged with double faced cards (wrong ref)? // add to all players String aliasId = player.generateAliasName(aliasName, useAliasMultiNames, i + 1); - currentGame.getPlayers().values().forEach(pl -> ((TestPlayer) pl).addAlias(aliasId, newCard.getId())); + Card finalNewCard = newCard; + currentGame.getPlayers().values().forEach(pl -> ((TestPlayer) pl).addAlias(aliasId, finalNewCard.getId())); } } } else { diff --git a/Mage.Tests/src/test/java/org/mage/test/utils/CardUtilTest.java b/Mage.Tests/src/test/java/org/mage/test/utils/CardUtilTest.java index 99e6845c60c..cd9f1eeda17 100644 --- a/Mage.Tests/src/test/java/org/mage/test/utils/CardUtilTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/utils/CardUtilTest.java @@ -21,7 +21,7 @@ public class CardUtilTest extends CardTestPlayerBase { // MDFC where both sides should be playable private static final String birgi = "Birgi, God of Storytelling"; // {2}{R}, frontside of Harnfel private static final String harnfel = "Harnfel, Horn of Bounty"; // {4}{R}, backside of Birgi - + private static final String tamiyo = "Tamiyo, Inquisitive Student"; // {U}, TDFC /** * Test that it will for trigger for discarding a MDFC but will only let you cast the nonland side. */ @@ -122,4 +122,29 @@ public class CardUtilTest extends CardTestPlayerBase { Assert.assertEquals("12345", CardUtil.substring(str, 8, ending)); Assert.assertEquals("12345", CardUtil.substring(str, 9, ending)); } + + /** + * Test that it will for trigger for discarding a TDFC but will only let you cast the front side. + */ + @Test + public void cantPlayTDFCBackSide() { + addCard(Zone.HAND, playerA, changeOfFortune); + addCard(Zone.HAND, playerA, tamiyo); + + addCard(Zone.BATTLEFIELD, playerA, oskar); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + + skipInitShuffling(); + setStrictChooseMode(true); + + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}", 4); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, changeOfFortune); + setChoice(playerA, "Yes"); + // only option is to cast front side, so auto chosen + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertPermanentCount(playerA, tamiyo, 1); + } } diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index b809a69f574..85c4c3c07c2 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -650,6 +650,9 @@ public class VerifyCardDataTest { CardInfo cardInfo = CardRepository.instance.findCardsByClass(info.getCardClass().getCanonicalName()).stream().findFirst().orElse(null); Assert.assertNotNull(cardInfo); + if (cardInfo.isDoubleFacedCard()) { + break; + } Card card = cardInfo.createCard(); Card secondCard = card.getSecondCardFace(); if (secondCard != null) { @@ -1224,7 +1227,8 @@ public class VerifyCardDataTest { cardInfo.getCardNumber(), cardInfo.getRarity(), cardInfo.getGraphicInfo())); Assert.assertNotNull(card); - if (card.getSecondCardFace() != null) { + //TODO: do we need this check after tdfc rework? + if (card.getSecondCardFace() != null && !(card instanceof DoubleFacedCard)) { containsDoubleSideCards = true; } @@ -2288,10 +2292,10 @@ public class VerifyCardDataTest { } // special check: Werewolves front ability should only be on front and vice versa - if (card.getAbilities().containsClass(WerewolfFrontTriggeredAbility.class) && card.isNightCard()) { + if (card.getAbilities().containsClass(WerewolfFrontTriggeredAbility.class) && (card.isNightCard() || (card instanceof DoubleFacedCardHalf && ((DoubleFacedCardHalf) card).isBackSide()))) { fail(card, "abilities", "card is a back face werewolf with a front face ability"); } - if (card.getAbilities().containsClass(WerewolfBackTriggeredAbility.class) && !card.isNightCard()) { + if (card.getAbilities().containsClass(WerewolfBackTriggeredAbility.class) && (!card.isNightCard() && (card instanceof DoubleFacedCardHalf && !((DoubleFacedCardHalf) card).isBackSide()))) { fail(card, "abilities", "card is a front face werewolf with a back face ability"); } @@ -2309,7 +2313,7 @@ public class VerifyCardDataTest { } // special check: siege ability must be used in double faced cards only - if (card.getAbilities().containsClass(SiegeAbility.class) && card.getSecondCardFace() == null) { + if (card.getAbilities().containsClass(SiegeAbility.class) && (card.getSecondCardFace() == null && (card instanceof DoubleFacedCardHalf && ((DoubleFacedCardHalf) card).getOtherSide() == null))) { fail(card, "abilities", "miss second side settings in card with siege ability"); } diff --git a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldTriggeredAbility.java index 0a8aeb373d6..10df3b1f3dd 100644 --- a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldTriggeredAbility.java @@ -23,7 +23,7 @@ public class EntersBattlefieldTriggeredAbility extends TriggeredAbilityImpl { } public EntersBattlefieldTriggeredAbility(Effect effect, boolean optional) { - super(Zone.ALL, effect, optional); // Zone.All because a creature with trigger can be put into play and be sacrificed during the resolution of an effect (discard Obstinate Baloth with Smallpox) + super(Zone.BATTLEFIELD, effect, optional); // Zone.All doesn't appear to be necessary anymore (discard Obstinate Baloth with Smallpox still works) this.withRuleTextReplacement(true); // default true to replace "{this}" with "it" or "this creature" // warning, it's impossible to add text auto-replacement for creatures here (When this creature enters), diff --git a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceAbility.java b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceAbility.java index e36231d922b..5703d94fe60 100644 --- a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceAbility.java @@ -17,6 +17,9 @@ import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.game.stack.Spell; import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; /** * @author LevelX2 @@ -99,11 +102,10 @@ class PutIntoGraveFromAnywhereEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { + UUID cardId = CardUtil.getMainCardId(game, source.getSourceId()); // for split cards if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD - && event.getTargetId().equals(source.getSourceId())) { - if (condition == null || condition.apply(game, source)) { - return true; - } + && (event.getTargetId().equals(cardId) || event.getTargetId().equals(source.getSourceId()))) { + return condition == null || condition.apply(game, source); } return false; } diff --git a/Mage/src/main/java/mage/abilities/common/RoomAbility.java b/Mage/src/main/java/mage/abilities/common/RoomAbility.java new file mode 100644 index 00000000000..36c689f10a5 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/RoomAbility.java @@ -0,0 +1,39 @@ +package mage.abilities.common; + +import mage.abilities.effects.common.RoomCharacteristicsEffect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentImpl; + +// For the overall Room card flavor text and mana value effect. +public class RoomAbility extends SimpleStaticAbility { + public RoomAbility() { + super(Zone.BATTLEFIELD, new RoomCharacteristicsEffect()); + this.setRuleVisible(true); + this.setRuleAtTheTop(true); + } + + protected RoomAbility(final RoomAbility ability) { + super(ability); + } + + @Override + public String getRule() { + return "(You may cast either half. That door unlocks on the battlefield. " + + "As a sorcery, you may pay the mana cost of a locked door to unlock it.)"; + } + + @Override + public RoomAbility copy() { + return new RoomAbility(this); + } + + public void applyCharacteristics(Game game, Permanent permanent) { + ((RoomCharacteristicsEffect) this.getEffects().get(0)).removeCharacteristics(game, permanent); + } + + public void restoreUnlockedStats(Game game, PermanentImpl permanent) { + ((RoomCharacteristicsEffect) this.getEffects().get(0)).restoreUnlockedStats(game, permanent); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/RoomUnlockAbility.java b/Mage/src/main/java/mage/abilities/common/RoomUnlockAbility.java index acf23c18761..6c4786fcd39 100644 --- a/Mage/src/main/java/mage/abilities/common/RoomUnlockAbility.java +++ b/Mage/src/main/java/mage/abilities/common/RoomUnlockAbility.java @@ -12,12 +12,11 @@ import mage.game.Game; import mage.game.permanent.Permanent; /** + * Special action for Room cards to unlock a locked half by paying its + * mana cost. + * This ability is only present if the corresponding half is currently + * locked. * @author oscscull - * Special action for Room cards to unlock a locked half by paying its - * mana - * cost. - * This ability is only present if the corresponding half is currently - * locked. */ public class RoomUnlockAbility extends SpecialAction { @@ -61,6 +60,10 @@ public class RoomUnlockAbility extends SpecialAction { sb.append(isLeftHalf ? "left" : "right").append(" half is locked.)"); return sb.toString(); } + + public boolean isLeftHalf() { + return isLeftHalf; + } } /** diff --git a/Mage/src/main/java/mage/abilities/common/SpellTransformedAbility.java b/Mage/src/main/java/mage/abilities/common/SpellTransformedAbility.java index 5adbff755f5..8b3dfd4eeff 100644 --- a/Mage/src/main/java/mage/abilities/common/SpellTransformedAbility.java +++ b/Mage/src/main/java/mage/abilities/common/SpellTransformedAbility.java @@ -7,6 +7,7 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.keyword.TransformAbility; import mage.cards.Card; +import mage.cards.TransformingDoubleFacedCard; import mage.constants.*; import mage.game.Game; import mage.game.stack.Spell; @@ -20,6 +21,7 @@ import java.util.UUID; public class SpellTransformedAbility extends SpellAbility { protected final String manaCost; //This variable is only used for rules text + private boolean ignoreTransformEffect; // TODO: temporary while converting tdfc public SpellTransformedAbility(Card card, String manaCost) { super(card.getSecondFaceSpellAbility()); @@ -35,7 +37,11 @@ public class SpellTransformedAbility extends SpellAbility { this.clearManaCosts(); this.clearManaCostsToPay(); this.addCost(new ManaCostsImpl<>(manaCost)); - this.addSubAbility(new TransformAbility()); + if (!(card instanceof TransformingDoubleFacedCard)) { + this.addSubAbility(new TransformAbility()); + } else { + ignoreTransformEffect = true; + } } public SpellTransformedAbility(final SpellAbility ability) { @@ -54,6 +60,7 @@ public class SpellTransformedAbility extends SpellAbility { protected SpellTransformedAbility(final SpellTransformedAbility ability) { super(ability); this.manaCost = ability.manaCost; + this.ignoreTransformEffect = ability.ignoreTransformEffect; } @Override @@ -65,6 +72,9 @@ public class SpellTransformedAbility extends SpellAbility { public boolean activate(Game game, Set allowedIdentifiers, boolean noMana) { if (super.activate(game, allowedIdentifiers, noMana)) { game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getSourceId(), Boolean.TRUE); + if (ignoreTransformEffect) { + return true; + } // TODO: must be removed after transform cards (one side) migrated to MDF engine (multiple sides) TransformedEffect effect = new TransformedEffect(); game.addEffect(effect, this); diff --git a/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java index 6d8e2d6fea4..2707c3a7a90 100644 --- a/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java +++ b/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java @@ -3,10 +3,7 @@ package mage.abilities.effects; import mage.MageIdentifier; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; -import mage.cards.Card; -import mage.cards.ModalDoubleFacedCard; -import mage.cards.SplitCard; -import mage.cards.CardWithSpellOption; +import mage.cards.*; import mage.constants.*; import mage.game.Game; import mage.players.Player; @@ -92,9 +89,9 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts(), identifier); Card rightCard = ((SplitCard) card).getRightHalfCard(); player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts(), identifier); - } else if (card instanceof ModalDoubleFacedCard) { - Card leftCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - Card rightCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + } else if (card instanceof DoubleFacedCard) { + Card leftCard = ((DoubleFacedCard) card).getLeftHalfCard(); + Card rightCard = ((DoubleFacedCard) card).getRightHalfCard(); // some MDFC's are land. IE: sea gate restoration if (!leftCard.isLand(game)) { player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts(), identifier); diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java index f464355448e..bbb9bbfa828 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CopyEffect.java @@ -2,9 +2,11 @@ package mage.abilities.effects.common; import mage.MageObject; import mage.MageObjectReference; +import mage.abilities.Abilities; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.Card; +import mage.abilities.common.RoomAbility; import mage.constants.*; import mage.game.Game; import mage.game.permanent.Permanent; @@ -63,6 +65,16 @@ public class CopyEffect extends ContinuousEffectImpl { permanent = game.getPermanentEntering(copyToObjectId); if (permanent != null) { copyToPermanent(permanent, game, source); + // Apply Room characteristics since effects aren't applied to entering permanents yet + if (permanent.hasSubtype(SubType.ROOM, game)) { + Abilities abilities = permanent.getAbilities(); + for (Ability ability : abilities) { + if (ability instanceof RoomAbility) { + ((RoomAbility) ability).applyCharacteristics(game, permanent); + break; + } + } + } // set reference to the permanent later on the battlefield so we have to add already one (if no token) to the zone change counter int ZCCDiff = 1; if (permanent instanceof PermanentToken) { diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java index eed991c7797..7f91a769dcc 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java @@ -216,6 +216,28 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { // create token and modify all attributes permanently (without game usage) Token token = CopyTokenFunction.createTokenCopy(copyFrom, game); // needed so that entersBattlefield triggered abilities see the attributes (e.g. Master Biomancer) applier.apply(game, token, source, targetId); + // the active face should have the modified attributes + if (token.isEntersTransformed()) { + applyAdditionsToToken(token.getBackFace()); + } else { + applyAdditionsToToken(token); + } + + token.putOntoBattlefield(number, game, source, playerId == null ? source.getControllerId() : playerId, tapped, attacking, attackedPlayer, attachedTo); + for (UUID tokenId : token.getLastAddedTokenIds()) { // by cards like Doubling Season multiple tokens can be added to the battlefield + Permanent tokenPermanent = game.getPermanent(tokenId); + if (tokenPermanent != null) { + addedTokenPermanents.add(tokenPermanent); + // TODO: Workaround to add counters to all created tokens, necessary for correct interactions with cards like Chatterfang, Squirrel General and Ochre Jelly / Printlifter Ooze. See #10786 + if (counter != null && numberOfCounters > 0) { + tokenPermanent.addCounters(counter.createInstance(numberOfCounters), source.getControllerId(), source, game); + } + } + } + return true; + } + + private void applyAdditionsToToken(Token token) { if (becomesArtifact) { token.addCardType(CardType.ARTIFACT); } @@ -281,19 +303,6 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect { token.removeAbility(ability); } } - - token.putOntoBattlefield(number, game, source, playerId == null ? source.getControllerId() : playerId, tapped, attacking, attackedPlayer, attachedTo); - for (UUID tokenId : token.getLastAddedTokenIds()) { // by cards like Doubling Season multiple tokens can be added to the battlefield - Permanent tokenPermanent = game.getPermanent(tokenId); - if (tokenPermanent != null) { - addedTokenPermanents.add(tokenPermanent); - // TODO: Workaround to add counters to all created tokens, necessary for correct interactions with cards like Chatterfang, Squirrel General and Ochre Jelly / Printlifter Ooze. See #10786 - if (counter != null && numberOfCounters > 0) { - tokenPermanent.addCounters(counter.createInstance(numberOfCounters), source.getControllerId(), source, game); - } - } - } - return true; } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandAttachedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandAttachedEffect.java index 87875eb4962..6e5cbb61c2b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandAttachedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandAttachedEffect.java @@ -3,7 +3,6 @@ package mage.abilities.effects.common; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; -import mage.cards.ModalDoubleFacedCard; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; @@ -37,10 +36,6 @@ public class ReturnToHandAttachedEffect extends OneShotEffect { return false; } Card card = permanent.getMainCard(); - // TODO: Once MDFC ZCC increments are fixed properly, can remove this special case. For now must allow so effect works. - if (permanent.getZoneChangeCounter(game) + 1 != card.getZoneChangeCounter(game) && !(card instanceof ModalDoubleFacedCard)) { - return false; - } return player.moveCards(card, Zone.HAND, source, game); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/RoomCharacteristicsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RoomCharacteristicsEffect.java index 05e47216f61..a7d598bf893 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/RoomCharacteristicsEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/RoomCharacteristicsEffect.java @@ -1,8 +1,12 @@ package mage.abilities.effects.common; import mage.MageObject; -import mage.Mana; +import mage.abilities.Abilities; +import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.RoomUnlockAbility; +import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; @@ -15,12 +19,13 @@ import mage.constants.SubLayer; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; +import mage.util.CardUtil; + +import java.util.UUID; /** - * @author oscscull * Continuous effect that sets the name and mana value of a Room permanent based * on its unlocked halves. - * * Functions as a characteristic-defining ability. * 709.5. Some split cards are permanent cards with a single shared type line. * A shared type line on such an object represents two static abilities that @@ -33,11 +38,13 @@ import mage.game.permanent.PermanentCard; * object's right half." * These abilities, as well as which half of that permanent a characteristic is * in, are part of that object's copiable values. + * @author oscscull */ public class RoomCharacteristicsEffect extends ContinuousEffectImpl { + public RoomCharacteristicsEffect() { - super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.CharacteristicDefining_7a, + super(Duration.WhileOnBattlefield, Layer.TextChangingEffects_3, SubLayer.NA, Outcome.Neutral); staticText = ""; } @@ -59,6 +66,107 @@ public class RoomCharacteristicsEffect extends ContinuousEffectImpl { return false; } + return removeCharacteristics(game, permanent); + } + + public boolean removeCharacteristics(Game game, Permanent permanent) { + Card roomCardBlueprint = getCard(permanent); + + if (!(roomCardBlueprint instanceof SplitCard)) { + return false; + } + + SplitCard roomCard = (SplitCard) roomCardBlueprint; + + // Remove the name based on unlocked halves + String newName = permanent.getName(); + + boolean isLeftUnlocked = permanent.isLeftDoorUnlocked(); + if (!isLeftUnlocked && roomCard.getLeftHalfCard() != null) { + newName = newName.replace(roomCard.getLeftHalfCard().getName() + " // ", ""); + } + + boolean isRightUnlocked = permanent.isRightDoorUnlocked(); + if (!isRightUnlocked && roomCard.getRightHalfCard() != null) { + newName = newName + .replace(" // " + roomCard.getRightHalfCard().getName(), "") + .replace(roomCard.getRightHalfCard().getName(), ""); + } + + permanent.setName(newName); + + // Set the mana value based on unlocked halves + // Create a new Mana object to accumulate the costs + SpellAbility roomCardSpellAbility = roomCard.getSpellAbility().copy(); + // Remove the mana from the left half's cost to our total Mana object + if (!isLeftUnlocked) { + ManaCosts leftHalfManaCost = null; + if (roomCard.getLeftHalfCard() != null && roomCard.getLeftHalfCard().getSpellAbility() != null) { + leftHalfManaCost = roomCard.getLeftHalfCard().getSpellAbility().getManaCosts(); + } + if (leftHalfManaCost != null) { + CardUtil.adjustCost(roomCardSpellAbility, leftHalfManaCost, true); + } + } + + // Remove the mana from the right half's cost to our total Mana object + if (!isRightUnlocked) { + ManaCosts rightHalfManaCost = null; + if (roomCard.getRightHalfCard() != null && roomCard.getRightHalfCard().getSpellAbility() != null) { + rightHalfManaCost = roomCard.getRightHalfCard().getSpellAbility().getManaCosts(); + } + if (rightHalfManaCost != null) { + CardUtil.adjustCost(roomCardSpellAbility, rightHalfManaCost, true); + } + } + + ManaCosts roomCardManaCosts = roomCardSpellAbility.getManaCostsToPay(); + if (roomCardManaCosts.getText().equals("{0}")) { + roomCardManaCosts = new ManaCostsImpl<>(); + } + permanent.setManaCost(roomCardManaCosts); + + + // Remove abilities from locked halves and add unlock abilities + Abilities removedLeftAbilities = new AbilitiesImpl<>(); + Abilities removedRightAbilities = new AbilitiesImpl<>(); + Card abilitySource = permanent; + if (permanent.isCopy()) { + abilitySource = (Card) permanent.getCopyFrom(); + } + for (Ability ability : abilitySource.getAbilities(game)) { + if (!isLeftUnlocked) { + if (roomCard.getLeftHalfCard() != null && roomCard.getLeftHalfCard().getAbilities().contains(ability)) { + if (!removedLeftAbilities.contains(ability)) { + removedLeftAbilities.add(ability); + } + permanent.removeAbility(ability, null, game); + continue; + } + } + if (!isRightUnlocked) { + if (roomCard.getRightHalfCard() != null && roomCard.getRightHalfCard().getAbilities().contains(ability)) { + if (!removedRightAbilities.contains(ability)) { + removedRightAbilities.add(ability); + } + permanent.removeAbility(ability, null, game); + } + } + } + // Add the Special Action to unlock doors. + // These will ONLY be active if the corresponding half is LOCKED! + if (!removedLeftAbilities.isEmpty()) { + RoomUnlockAbility leftUnlockAbility = new RoomUnlockAbility(roomCard.getLeftHalfCard().getManaCost(), true); + permanent.addAbility(leftUnlockAbility, roomCard.getLeftHalfCard().getId(), game); + } + if (!removedRightAbilities.isEmpty()) { + RoomUnlockAbility rightUnlockAbility = new RoomUnlockAbility(roomCard.getRightHalfCard().getManaCost(), false); + permanent.addAbility(rightUnlockAbility, roomCard.getRightHalfCard().getId(), game); + } + return true; + } + + private static Card getCard(Permanent permanent) { Card roomCardBlueprint; // Handle copies @@ -74,69 +182,34 @@ public class RoomCharacteristicsEffect extends ContinuousEffectImpl { } else { roomCardBlueprint = permanent.getMainCard(); } + return roomCardBlueprint; + } - if (!(roomCardBlueprint instanceof SplitCard)) { - return false; - } - - SplitCard roomCard = (SplitCard) roomCardBlueprint; - - // Set the name based on unlocked halves - String newName = ""; - - boolean isLeftUnlocked = permanent.isLeftDoorUnlocked(); - if (isLeftUnlocked && roomCard.getLeftHalfCard() != null) { - newName += roomCard.getLeftHalfCard().getName(); - } - - boolean isRightUnlocked = permanent.isRightDoorUnlocked(); - if (isRightUnlocked && roomCard.getRightHalfCard() != null) { - if (!newName.isEmpty()) { - newName += " // "; // Split card name separator - } - newName += roomCard.getRightHalfCard().getName(); - } - - permanent.setName(newName); - - // Set the mana value based on unlocked halves - // Create a new Mana object to accumulate the costs - Mana totalManaCost = new Mana(); - - // Add the mana from the left half's cost to our total Mana object - if (isLeftUnlocked) { - ManaCosts leftHalfManaCost = null; - if (roomCard.getLeftHalfCard() != null && roomCard.getLeftHalfCard().getSpellAbility() != null) { - leftHalfManaCost = roomCard.getLeftHalfCard().getSpellAbility().getManaCosts(); - } - if (leftHalfManaCost != null) { - totalManaCost.add(leftHalfManaCost.getMana()); + public void restoreUnlockedStats(Game game, Permanent permanent) { + // remove unlock abilities + for (Ability ability : permanent.getAbilities(game)) { + if (ability instanceof RoomUnlockAbility) { + if (((RoomUnlockAbility) ability).isLeftHalf() && permanent.isLeftDoorUnlocked()) { + permanent.removeAbility(ability, null, game); + } else if (!((RoomUnlockAbility) ability).isLeftHalf() && permanent.isRightDoorUnlocked()) { + permanent.removeAbility(ability, null, game); + } } } - - // Add the mana from the right half's cost to our total Mana object - if (isRightUnlocked) { - ManaCosts rightHalfManaCost = null; - if (roomCard.getRightHalfCard() != null && roomCard.getRightHalfCard().getSpellAbility() != null) { - rightHalfManaCost = roomCard.getRightHalfCard().getSpellAbility().getManaCosts(); - } - if (rightHalfManaCost != null) { - totalManaCost.add(rightHalfManaCost.getMana()); + // restore removed abilities + // copies need abilities to be added back to game state for triggers + SplitCard roomCard = (SplitCard) getCard(permanent); + UUID sourceId = permanent.isCopy() ? permanent.getId() : null; + Game gameParam = permanent.isCopy() ? game : null; + if (permanent.isLeftDoorUnlocked()) { + for (Ability ability : roomCard.getLeftHalfCard().getAbilities()) { + permanent.addAbility(ability, sourceId, gameParam, true); } } - - String newManaCostString = totalManaCost.toString(); - ManaCostsImpl newManaCosts; - - // If both halves are locked or total 0, it's 0mv. - if (newManaCostString.isEmpty() || totalManaCost.count() == 0) { - newManaCosts = new ManaCostsImpl<>(""); - } else { - newManaCosts = new ManaCostsImpl<>(newManaCostString); + if (permanent.isRightDoorUnlocked()) { + for (Ability ability : roomCard.getRightHalfCard().getAbilities()) { + permanent.addAbility(ability, sourceId, gameParam, true); + } } - - permanent.setManaCost(newManaCosts); - - return true; } } \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java index 099abc9d1ac..e6e00c1372c 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesFaceDownCreatureEffect.java @@ -16,7 +16,7 @@ import mage.abilities.effects.common.InfoEffect; import mage.abilities.keyword.WardAbility; import mage.cards.Card; import mage.cards.CardImpl; -import mage.cards.ModalDoubleFacedCard; +import mage.cards.DoubleFacedCard; import mage.cards.repository.TokenInfo; import mage.cards.repository.TokenRepository; import mage.constants.*; @@ -375,9 +375,9 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl { // it can't transform. If the front face of the card is a creature card, you can turn it face up by paying // its mana cost. If you do, its front face will be up. - if (card instanceof ModalDoubleFacedCard) { + if (card instanceof DoubleFacedCard) { // only MDFC uses independent card sides on 2024 - return ((ModalDoubleFacedCard) card).getLeftHalfCard(); + return ((DoubleFacedCard) card).getLeftHalfCard(); } else { return card; } diff --git a/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java b/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java index 70f36cad717..1a1900d55fe 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CraftAbility.java @@ -9,6 +9,7 @@ import mage.abilities.costs.common.ExileSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; +import mage.cards.TransformingDoubleFacedCardHalf; import mage.constants.*; import mage.filter.FilterCard; import mage.filter.FilterPermanent; diff --git a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java index 421a25858d3..c033bc5e8b5 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java @@ -300,6 +300,7 @@ class ForetellAddCostEffect extends ContinuousEffectImpl { if (game.getState().getZone(mainCardId) == Zone.EXILED) { String foretellCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Cost"); String foretellSplitCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Split Cost"); + // TODO: clean this up if (card instanceof SplitCard) { if (foretellCost != null) { SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard(); @@ -363,6 +364,14 @@ class ForetellAddCostEffect extends ContinuousEffectImpl { ability.setAbilityName(spellCard.getName()); game.getState().addOtherAbility(spellCard, ability); } + } else if (card instanceof TransformingDoubleFacedCard && foretellCost != null) { + Card frontCard = ((TransformingDoubleFacedCard) card).getLeftHalfCard(); + ForetellCostAbility ability = new ForetellCostAbility(foretellCost); + ability.setSourceId(frontCard.getId()); + ability.setControllerId(source.getControllerId()); + ability.setSpellAbilityType(frontCard.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(frontCard.getName()); + game.getState().addOtherAbility(frontCard, ability); } else if (foretellCost != null) { ForetellCostAbility ability = new ForetellCostAbility(foretellCost); ability.setSourceId(card.getId()); diff --git a/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java b/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java index f69518cf516..47408ef3f6b 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SuspendAbility.java @@ -18,7 +18,7 @@ import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.Card; import mage.cards.CardsImpl; -import mage.cards.ModalDoubleFacedCard; +import mage.cards.DoubleFacedCard; import mage.constants.*; import mage.counters.CounterType; import mage.filter.StaticFilters; @@ -177,10 +177,10 @@ public class SuspendAbility extends SpecialAction { * or added by Jhoira of the Ghitu */ public static void addSuspendTemporaryToCard(Card card, Ability source, Game game) { - if (card instanceof ModalDoubleFacedCard) { + if (card instanceof DoubleFacedCard) { // Need to ensure the suspend ability gets put on the left side card // since counters get added to this card. - card = ((ModalDoubleFacedCard) card).getLeftHalfCard(); + card = ((DoubleFacedCard) card).getLeftHalfCard(); } SuspendAbility ability = new SuspendAbility(0, null, card, false); ability.setSourceId(card.getId()); diff --git a/Mage/src/main/java/mage/cards/Card.java b/Mage/src/main/java/mage/cards/Card.java index 86a4a7eba29..b78ff8d133a 100644 --- a/Mage/src/main/java/mage/cards/Card.java +++ b/Mage/src/main/java/mage/cards/Card.java @@ -1,5 +1,6 @@ package mage.cards; +import mage.MageInt; import mage.MageObject; import mage.Mana; import mage.abilities.Abilities; @@ -72,6 +73,7 @@ public interface Card extends MageObject, Ownerable { SpellAbility getSecondFaceSpellAbility(); + //TODO: remove after tdfc rework boolean isNightCard(); default boolean meldsWith(Card card) { @@ -250,6 +252,10 @@ public interface Card extends MageObject, Ownerable { List getAttachments(); + void setPT(int power, int toughness); + + void setPT(MageInt power, MageInt toughness); + /** * @param attachment can be any object: card, permanent, token * @param source can be null for default checks like state base diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index e9664d69b97..966363ae338 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -1,5 +1,6 @@ package mage.cards; +import mage.MageInt; import mage.MageObject; import mage.MageObjectImpl; import mage.Mana; @@ -126,6 +127,11 @@ public abstract class CardImpl extends MageObjectImpl implements Card { nightCard = card.nightCard; secondSideCardClazz = card.secondSideCardClazz; secondSideCard = null; // will be set on first getSecondCardFace call if card has one + // TODO: temporary until cards tdfc cards are converted + // can do normal copy after + if (card.secondSideCard instanceof DoubleFacedCardHalf) { + secondSideCard = card.secondSideCard.copy(); + } if (card.secondSideCard instanceof MockableCard) { // workaround to support gui's mock cards secondSideCard = card.secondSideCard.copy(); @@ -393,6 +399,17 @@ public abstract class CardImpl extends MageObjectImpl implements Card { this.abilities.setControllerId(ownerId); } + @Override + public void setPT(int power, int toughness) { + this.setPT(new MageInt(power), new MageInt(toughness)); + } + + @Override + public void setPT(MageInt power, MageInt toughness) { + this.power = power; + this.toughness = toughness; + } + @Override public UUID getControllerOrOwnerId() { return getOwnerId(); @@ -517,13 +534,13 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } } - // handle half of Modal Double Faces Cards on stack - if (stackObject == null && (this instanceof ModalDoubleFacedCard)) { - stackObject = game.getStack().getSpell(((ModalDoubleFacedCard) this).getLeftHalfCard().getId(), + // handle half of Double Faces Cards on stack + if (stackObject == null && (this instanceof DoubleFacedCard)) { + stackObject = game.getStack().getSpell(((DoubleFacedCard) this).getLeftHalfCard().getId(), false); if (stackObject == null) { stackObject = game.getStack() - .getSpell(((ModalDoubleFacedCard) this).getRightHalfCard().getId(), false); + .getSpell(((DoubleFacedCard) this).getRightHalfCard().getId(), false); } } @@ -650,7 +667,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { // 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; + return this.secondSideCardClazz != null || this.nightCard || this.secondSideCard != null; } @Override @@ -947,7 +964,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } } } - if (controller != null && spellAbility != null && !spellAbility.getTargets().isEmpty()){ + if (controller != null && spellAbility != null && !spellAbility.getTargets().isEmpty()) { // Line of code below functionally gets the target of the aura's Enchant ability, then compares to this permanent. Enchant improperly implemented in XMage, see #9583 // Note: stillLegalTarget used exclusively to account for Dream Leash. Can be made canTarget in the event that that card is rewritten (and "stillLegalTarget" removed from TargetImpl). canAttach &= spellAbility.getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(controller, this.getId(), source, game); diff --git a/Mage/src/main/java/mage/cards/DoubleFacedCard.java b/Mage/src/main/java/mage/cards/DoubleFacedCard.java new file mode 100644 index 00000000000..f9c752e0d5a --- /dev/null +++ b/Mage/src/main/java/mage/cards/DoubleFacedCard.java @@ -0,0 +1,413 @@ +package mage.cards; + +import mage.MageInt; +import mage.MageObject; +import mage.ObjectColor; +import mage.abilities.*; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.mana.ManaCosts; +import mage.constants.*; +import mage.counters.Counter; +import mage.counters.Counters; +import mage.game.Game; +import mage.game.GameState; +import mage.game.events.ZoneChangeEvent; +import mage.util.CardUtil; +import mage.util.SubTypes; + +import java.util.List; +import java.util.UUID; + +/** + * @author JayDi85 - originally from ModalDoubleFaceCard + */ +public abstract class DoubleFacedCard extends CardImpl implements CardWithHalves { + + protected DoubleFacedCardHalf leftHalfCard; // main card in all zone + protected DoubleFacedCardHalf rightHalfCard; // second side card, can be only in stack and battlefield zones + + protected DoubleFacedCard(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs, SpellAbilityType spellAbilityType) { + super(ownerId, setInfo, cardTypes, costs, spellAbilityType); + } + + public DoubleFacedCard(DoubleFacedCard card) { + super(card); + // make sure all parts created and parent ref added + this.leftHalfCard = (DoubleFacedCardHalf) card.getLeftHalfCard().copy(); + leftHalfCard.setParentCard(this); + this.rightHalfCard = (DoubleFacedCardHalf) card.getRightHalfCard().copy(); + rightHalfCard.setParentCard(this); + } + + public DoubleFacedCardHalf getLeftHalfCard() { + return leftHalfCard; + } + + public DoubleFacedCardHalf getRightHalfCard() { + return leftHalfCard; + } + + public void setParts(DoubleFacedCardHalf leftHalfCard, DoubleFacedCardHalf rightHalfCard) { + // for card copy only - set new parts + this.leftHalfCard = leftHalfCard; + leftHalfCard.setParentCard(this); + this.rightHalfCard = rightHalfCard; + rightHalfCard.setParentCard(this); + } + + @Override + public void assignNewId() { + super.assignNewId(); + leftHalfCard.assignNewId(); + rightHalfCard.assignNewId(); + } + + @Override + public void setCopy(boolean isCopy, MageObject copiedFrom) { + super.setCopy(isCopy, copiedFrom); + leftHalfCard.setCopy(isCopy, copiedFrom); + rightHalfCard.setCopy(isCopy, copiedFrom); + } + + private void setSideZones(Zone mainZone, Game game) { + switch (mainZone) { + case BATTLEFIELD: + case STACK: + throw new IllegalArgumentException("Wrong code usage: you must put to battlefield/stack only real side card (half), not main"); + default: + game.setZone(leftHalfCard.getId(), mainZone); + game.setZone(rightHalfCard.getId(), mainZone); + break; + } + checkGoodZones(game, this); + } + + @Override + public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { + if (super.moveToZone(toZone, source, game, flag, appliedEffects)) { + Zone currentZone = game.getState().getZone(getId()); + setSideZones(currentZone, game); + return true; + } + return false; + } + + @Override + public void setZone(Zone zone, Game game) { + super.setZone(zone, game); + setSideZones(zone, game); + } + + @Override + public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { + if (super.moveToExile(exileId, name, source, game, appliedEffects)) { + Zone currentZone = game.getState().getZone(getId()); + setSideZones(currentZone, game); + return true; + } + return false; + } + + /** + * Runtime check for good zones and other MDF data + */ + public static void checkGoodZones(Game game, DoubleFacedCard card) { + Card leftPart = card.getLeftHalfCard(); + Card rightPart = card.getRightHalfCard(); + + Zone zoneMain = game.getState().getZone(card.getId()); + Zone zoneLeft = game.getState().getZone(leftPart.getId()); + Zone zoneRight = game.getState().getZone(rightPart.getId()); + + // runtime check: + // * in battlefield and stack - card + one of the sides (another side in outside zone) + // * in other zones - card + both sides (need both sides due cost reductions, spell and other access before put to stack) + // + // 712.8a While a double-faced card is outside the game or in a zone other than the battlefield or stack, + // it has only the characteristics of its front face. + // + // 712.8f While a modal double-faced spell is on the stack or a modal double-faced permanent is on the battlefield, + // it has only the characteristics of the face that’s up. + Zone needZoneLeft; + Zone needZoneRight; + switch (zoneMain) { + case BATTLEFIELD: + case STACK: + if (zoneMain == zoneLeft) { + needZoneLeft = zoneMain; + needZoneRight = Zone.OUTSIDE; + } else if (zoneMain == zoneRight) { + needZoneLeft = Zone.OUTSIDE; + needZoneRight = zoneMain; + } else { + // impossible + needZoneLeft = zoneMain; + needZoneRight = Zone.OUTSIDE; + } + break; + default: + needZoneLeft = zoneMain; + needZoneRight = zoneMain; + break; + } + + if (zoneLeft != needZoneLeft || zoneRight != needZoneRight) { + throw new IllegalStateException("Wrong code usage: MDF card uses wrong zones - " + card + + "\r\n" + String.format("* main zone: %s", zoneMain) + + "\r\n" + String.format("* left side: need %s, actual %s", needZoneLeft, zoneLeft) + + "\r\n" + String.format("* right side: need %s, actual %s", needZoneRight, zoneRight)); + } + } + + @Override + public boolean removeFromZone(Game game, Zone fromZone, Ability source) { + // zone contains only one main card + return super.removeFromZone(game, fromZone, source); + } + + @Override + public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { + if (isCopy()) { // same as meld cards + super.updateZoneChangeCounter(game, event); + return; + } + super.updateZoneChangeCounter(game, event); + game.getState().updateZoneChangeCounter(leftHalfCard.getId()); + game.getState().updateZoneChangeCounter(rightHalfCard.getId()); + } + + @Override + public Counters getCounters(Game game) { + return getCounters(game.getState()); + } + + @Override + public Counters getCounters(GameState state) { + return state.getCardState(leftHalfCard.getId()).getCounters(); + } + + @Override + public boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List appliedEffects, boolean isEffect, int maxCounters) { + return leftHalfCard.addCounters(counter, playerAddingCounters, source, game, appliedEffects, isEffect, maxCounters); + } + + @Override + public void removeCounters(String counterName, int amount, Ability source, Game game) { + leftHalfCard.removeCounters(counterName, amount, source, game); + } + + @Override + public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { + if (this.leftHalfCard.getSpellAbility() != null) { + this.leftHalfCard.getSpellAbility().setControllerId(controllerId); + } + if (this.rightHalfCard.getSpellAbility() != null) { + this.rightHalfCard.getSpellAbility().setControllerId(controllerId); + } + return super.cast(game, fromZone, ability, controllerId); + } + + + @Override + public List getSuperType(Game game) { + // CardImpl's constructor can call some code on init, so you must check left/right before + // it's a bad workaround + return leftHalfCard != null ? leftHalfCard.getSuperType(game) : supertype; + } + + @Override + public List getCardType(Game game) { + // CardImpl's constructor can call some code on init, so you must check left/right before + // it's a bad workaround + return leftHalfCard != null ? leftHalfCard.getCardType(game) : cardType; + } + + @Override + public SubTypes getSubtype() { + // rules: While a double-faced card isn’t on the stack or battlefield, consider only the characteristics of its front face. + // CardImpl's constructor can call some code on init, so you must check left/right before + return leftHalfCard != null ? leftHalfCard.getSubtype() : subtype; + } + + @Override + public SubTypes getSubtype(Game game) { + // rules: While a double-faced card isn’t on the stack or battlefield, consider only the characteristics of its front face. + // CardImpl's constructor can call some code on init, so you must check left/right before + return leftHalfCard != null ? leftHalfCard.getSubtype(game) : subtype; + } + + @Override + public boolean hasSubtype(SubType subtype, Game game) { + return leftHalfCard.hasSubtype(subtype, game); + } + + @Override + public Abilities getAbilities() { + return getInnerAbilities(true, true); + } + + @Override + public Abilities getInitAbilities() { + // must init only parent related abilities, spell card must be init separately + return getInnerAbilities(false, false); + } + + public Abilities getSharedAbilities(Game game) { + // no shared abilities for mdf cards (e.g. must be left or right only) + return new AbilitiesImpl<>(); + } + + @Override + public Abilities getAbilities(Game game) { + return getInnerAbilities(game, true, true); + } + + private boolean isIgnoreDefaultAbility(Ability ability) { + // ignore default play/spell ability from main card (only halves are actual) + // default abilities added on card creation from card type and can't be skipped + + // skip cast spell + if (ability instanceof SpellAbility) { + SpellAbilityType type = ((SpellAbility) ability).getSpellAbilityType(); + return type == SpellAbilityType.MODAL || type == SpellAbilityType.TRANSFORMED; + } + + // skip play land + return ability instanceof PlayLandAbility; + } + + private boolean isIgnoreTransformSpellAbility(Ability ability) { + return ability instanceof SpellAbility && ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.TRANSFORMED_RIGHT; + } + + private Abilities getInnerAbilities(Game game, boolean showLeftSide, boolean showRightSide) { + Abilities allAbilites = new AbilitiesImpl<>(); + + for (Ability ability : super.getAbilities(game)) { + if (isIgnoreDefaultAbility(ability)) { + continue; + } + allAbilites.add(ability); + } + + if (showLeftSide) { + allAbilites.addAll(leftHalfCard.getAbilities(game)); + } + if (showRightSide) { + for (Ability ability: rightHalfCard.getAbilities(game)) { + if (isIgnoreTransformSpellAbility(ability)) { + continue; + } + allAbilites.add(ability); + } + } + + return allAbilites; + } + + private Abilities getInnerAbilities(boolean showLeftSide, boolean showRightSide) { + Abilities allAbilites = new AbilitiesImpl<>(); + + for (Ability ability : super.getAbilities()) { + if (isIgnoreDefaultAbility(ability)) { + continue; + } + allAbilites.add(ability); + } + + if (showLeftSide) { + allAbilites.addAll(leftHalfCard.getAbilities()); + } + + if (showRightSide) { + for (Ability ability: rightHalfCard.getAbilities()) { + if (isIgnoreTransformSpellAbility(ability)) { + continue; + } + allAbilites.add(ability); + } + } + + return allAbilites; + } + + @Override + public List getRules() { + // rules must show only main side (another side visible by toggle/transform button in GUI) + // card hints from both sides + return CardUtil.getCardRulesWithAdditionalInfo( + this, + this.getInnerAbilities(true, false), + this.getInnerAbilities(true, true) + ); + } + + @Override + public List getRules(Game game) { + // rules must show only main side (another side visible by toggle/transform button in GUI) + // card hints from both sides + return CardUtil.getCardRulesWithAdditionalInfo( + game, + this, + this.getInnerAbilities(game, true, false), + this.getInnerAbilities(game, true, true) + ); + } + + @Override + public boolean hasAbility(Ability ability, Game game) { + return super.hasAbility(ability, game); + } + + @Override + public ObjectColor getColor() { + return leftHalfCard.getColor(); + } + + @Override + public ObjectColor getColor(Game game) { + return leftHalfCard.getColor(game); + } + + @Override + public ObjectColor getFrameColor(Game game) { + return leftHalfCard.getFrameColor(game); + } + + @Override + public void setOwnerId(UUID ownerId) { + super.setOwnerId(ownerId); + abilities.setControllerId(ownerId); + leftHalfCard.getAbilities().setControllerId(ownerId); + leftHalfCard.setOwnerId(ownerId); + rightHalfCard.getAbilities().setControllerId(ownerId); + rightHalfCard.setOwnerId(ownerId); + } + + @Override + public ManaCosts getManaCost() { + return leftHalfCard.getManaCost(); + } + + @Override + public int getManaValue() { + // Rules: + // The converted mana cost of a modal double-faced card is based on the characteristics of the + // face that’s being considered. On the stack and battlefield, consider whichever face is up. + // In all other zones, consider only the front face. This is different than how the converted + // mana cost of a transforming double-faced card is determined. + + // on stack or battlefield it must be half card with own cost + return leftHalfCard.getManaValue(); + } + + @Override + public MageInt getPower() { + return leftHalfCard.getPower(); + } + + @Override + public MageInt getToughness() { + return leftHalfCard.getToughness(); + } +} diff --git a/Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalfImpl.java b/Mage/src/main/java/mage/cards/DoubleFacedCardHalf.java similarity index 67% rename from Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalfImpl.java rename to Mage/src/main/java/mage/cards/DoubleFacedCardHalf.java index 2521b78aee8..c6bd6a390c3 100644 --- a/Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalfImpl.java +++ b/Mage/src/main/java/mage/cards/DoubleFacedCardHalf.java @@ -1,25 +1,25 @@ package mage.cards; -import mage.MageInt; import mage.abilities.Ability; import mage.constants.*; import mage.game.Game; +import mage.game.events.ZoneChangeEvent; import java.util.Arrays; import java.util.List; import java.util.UUID; /** - * @author JayDi85 + * @author JayDi85 - originally from ModalDoubleFaceCardHalf */ -public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubleFacedCardHalf { +public abstract class DoubleFacedCardHalf extends CardImpl implements SubCard { - ModalDoubleFacedCard parentCard; + protected DoubleFacedCard parentCard; - public ModalDoubleFacedCardHalfImpl( + public DoubleFacedCardHalf( UUID ownerId, CardSetInfo setInfo, SuperType[] cardSuperTypes, CardType[] cardTypes, SubType[] cardSubTypes, - String costs, ModalDoubleFacedCard parentCard, SpellAbilityType spellAbilityType + String costs, DoubleFacedCard parentCard, SpellAbilityType spellAbilityType ) { super(ownerId, setInfo, cardTypes, costs, spellAbilityType); this.supertype.addAll(Arrays.asList(cardSuperTypes)); @@ -27,7 +27,7 @@ public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubl this.parentCard = parentCard; } - protected ModalDoubleFacedCardHalfImpl(final ModalDoubleFacedCardHalfImpl card) { + protected DoubleFacedCardHalf(final DoubleFacedCardHalf card) { super(card); this.parentCard = card.parentCard; } @@ -49,6 +49,11 @@ public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubl return parentCard.getCardNumber(); } + @Override + public boolean isTransformable() { + return getOtherSide().isPermanent(); + } + @Override public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { return parentCard.moveToZone(toZone, source, game, flag, appliedEffects); @@ -65,25 +70,23 @@ public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubl } @Override - public ModalDoubleFacedCard getMainCard() { + public Card getMainCard() { return parentCard; } + @Override + public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { + parentCard.updateZoneChangeCounter(game, event); + } + @Override public void setZone(Zone zone, Game game) { - // see ModalDoubleFacedCard.checkGoodZones for details + // see DoubleFacedCard.checkGoodZones for details game.setZone(parentCard.getId(), zone); game.setZone(this.getId(), zone); // find another side to sync - ModalDoubleFacedCardHalf otherSide; - if (!parentCard.getLeftHalfCard().getId().equals(this.getId())) { - otherSide = parentCard.getLeftHalfCard(); - } else if (!parentCard.getRightHalfCard().getId().equals(this.getId())) { - otherSide = parentCard.getRightHalfCard(); - } else { - throw new IllegalStateException("Wrong code usage: MDF halves must use different ids"); - } + Card otherSide = getOtherSide(); switch (zone) { case STACK: @@ -96,33 +99,39 @@ public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubl break; } - ModalDoubleFacedCard.checkGoodZones(game, parentCard); + DoubleFacedCard.checkGoodZones(game, parentCard); + } + + public Card getOtherSide() { + Card otherSide; + if (!parentCard.getLeftHalfCard().getId().equals(this.getId())) { + otherSide = parentCard.getLeftHalfCard(); + } else if (!parentCard.getRightHalfCard().getId().equals(this.getId())) { + otherSide = parentCard.getRightHalfCard(); + } else { + throw new IllegalStateException("Wrong code usage: MDF halves must use different ids"); + } + return otherSide; + } + + public boolean isBackSide() { + if (parentCard.getLeftHalfCard().getId().equals(this.getId())) { + return false; + } else if (parentCard.getRightHalfCard().getId().equals(this.getId())) { + return true; + } else { + throw new IllegalStateException("Wrong code usage: MDF halves must use different ids"); + } } @Override - public ModalDoubleFacedCardHalfImpl copy() { - return new ModalDoubleFacedCardHalfImpl(this); - } - - @Override - public void setParentCard(ModalDoubleFacedCard card) { + public void setParentCard(DoubleFacedCard card) { this.parentCard = card; } @Override - public ModalDoubleFacedCard getParentCard() { - return this.parentCard; - } - - @Override - public void setPT(int power, int toughness) { - this.setPT(new MageInt(power), new MageInt(toughness)); - } - - @Override - public void setPT(MageInt power, MageInt toughness) { - this.power = power; - this.toughness = toughness; + public DoubleFacedCard getParentCard() { + return parentCard; } @Override diff --git a/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java b/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java index eccfab302f2..cce1911e1bb 100644 --- a/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java +++ b/Mage/src/main/java/mage/cards/ModalDoubleFacedCard.java @@ -1,30 +1,15 @@ package mage.cards; -import mage.MageInt; -import mage.MageObject; -import mage.ObjectColor; import mage.abilities.*; -import mage.abilities.costs.mana.ManaCost; -import mage.abilities.costs.mana.ManaCosts; import mage.constants.*; -import mage.counters.Counter; -import mage.counters.Counters; import mage.game.Game; -import mage.game.GameState; -import mage.game.events.ZoneChangeEvent; -import mage.util.CardUtil; -import mage.util.SubTypes; -import java.util.List; import java.util.UUID; /** * @author JayDi85 */ -public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithHalves { - - protected Card leftHalfCard; // main card in all zone - protected Card rightHalfCard; // second side card, can be only in stack and battlefield zones +public abstract class ModalDoubleFacedCard extends DoubleFacedCard { public ModalDoubleFacedCard( UUID ownerId, CardSetInfo setInfo, @@ -48,184 +33,21 @@ public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithH ) { super(ownerId, setInfo, typesLeft, costsLeft + costsRight, SpellAbilityType.MODAL); // main card name must be same as left side - leftHalfCard = new ModalDoubleFacedCardHalfImpl( + leftHalfCard = new ModalDoubleFacedCardHalf( this.getOwnerId(), setInfo.copy(), superTypesLeft, typesLeft, subTypesLeft, costsLeft, this, SpellAbilityType.MODAL_LEFT ); - rightHalfCard = new ModalDoubleFacedCardHalfImpl( + rightHalfCard = new ModalDoubleFacedCardHalf( this.getOwnerId(), new CardSetInfo(secondSideName, setInfo), superTypesRight, typesRight, subTypesRight, costsRight, this, SpellAbilityType.MODAL_RIGHT ); + this.secondSideCard = rightHalfCard; } - public ModalDoubleFacedCard(ModalDoubleFacedCard card) { + public ModalDoubleFacedCard(final ModalDoubleFacedCard card) { super(card); - // make sure all parts created and parent ref added - this.leftHalfCard = card.getLeftHalfCard().copy(); - ((ModalDoubleFacedCardHalf) leftHalfCard).setParentCard(this); - this.rightHalfCard = card.rightHalfCard.copy(); - ((ModalDoubleFacedCardHalf) rightHalfCard).setParentCard(this); - } - - public ModalDoubleFacedCardHalf getLeftHalfCard() { - return (ModalDoubleFacedCardHalf) leftHalfCard; - } - - public ModalDoubleFacedCardHalf getRightHalfCard() { - return (ModalDoubleFacedCardHalf) rightHalfCard; - } - - public void setParts(ModalDoubleFacedCardHalf leftHalfCard, ModalDoubleFacedCardHalf rightHalfCard) { - // for card copy only - set new parts - this.leftHalfCard = leftHalfCard; - leftHalfCard.setParentCard(this); - this.rightHalfCard = rightHalfCard; - rightHalfCard.setParentCard(this); - } - - @Override - public void assignNewId() { - super.assignNewId(); - leftHalfCard.assignNewId(); - rightHalfCard.assignNewId(); - } - - @Override - public void setCopy(boolean isCopy, MageObject copiedFrom) { - super.setCopy(isCopy, copiedFrom); - leftHalfCard.setCopy(isCopy, copiedFrom); // TODO: must check copiedFrom and assign sides? (??? related to #8476 ???) - rightHalfCard.setCopy(isCopy, copiedFrom); - } - - private void setSideZones(Zone mainZone, Game game) { - switch (mainZone) { - case BATTLEFIELD: - case STACK: - throw new IllegalArgumentException("Wrong code usage: you must put to battlefield/stack only real side card (half), not main"); - default: - // must keep both sides in same zone cause xmage need access to cost reduction, spell - // and other abilities before put it to stack (in playable calcs) - game.setZone(leftHalfCard.getId(), mainZone); - game.setZone(rightHalfCard.getId(), mainZone); - break; - } - checkGoodZones(game, this); - } - - @Override - public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { - if (super.moveToZone(toZone, source, game, flag, appliedEffects)) { - Zone currentZone = game.getState().getZone(getId()); - setSideZones(currentZone, game); - return true; - } - return false; - } - - @Override - public void setZone(Zone zone, Game game) { - super.setZone(zone, game); - setSideZones(zone, game); - } - - @Override - public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { - if (super.moveToExile(exileId, name, source, game, appliedEffects)) { - Zone currentZone = game.getState().getZone(getId()); - setSideZones(currentZone, game); - return true; - } - return false; - } - - /** - * Runtime check for good zones and other MDF data - */ - public static void checkGoodZones(Game game, ModalDoubleFacedCard card) { - Card leftPart = card.getLeftHalfCard(); - Card rightPart = card.getRightHalfCard(); - - Zone zoneMain = game.getState().getZone(card.getId()); - Zone zoneLeft = game.getState().getZone(leftPart.getId()); - Zone zoneRight = game.getState().getZone(rightPart.getId()); - - // runtime check: - // * in battlefield and stack - card + one of the sides (another side in outside zone) - // * in other zones - card + both sides (need both sides due cost reductions, spell and other access before put to stack) - // - // 712.8a While a double-faced card is outside the game or in a zone other than the battlefield or stack, - // it has only the characteristics of its front face. - // - // 712.8f While a modal double-faced spell is on the stack or a modal double-faced permanent is on the battlefield, - // it has only the characteristics of the face that’s up. - Zone needZoneLeft; - Zone needZoneRight; - switch (zoneMain) { - case BATTLEFIELD: - case STACK: - if (zoneMain == zoneLeft) { - needZoneLeft = zoneMain; - needZoneRight = Zone.OUTSIDE; - } else if (zoneMain == zoneRight) { - needZoneLeft = Zone.OUTSIDE; - needZoneRight = zoneMain; - } else { - // impossible - needZoneLeft = zoneMain; - needZoneRight = Zone.OUTSIDE; - } - break; - default: - needZoneLeft = zoneMain; - needZoneRight = zoneMain; - break; - } - - if (zoneLeft != needZoneLeft || zoneRight != needZoneRight) { - throw new IllegalStateException("Wrong code usage: MDF card uses wrong zones - " + card - + "\r\n" + String.format("* main zone: %s", zoneMain) - + "\r\n" + String.format("* left side: need %s, actual %s", needZoneLeft, zoneLeft) - + "\r\n" + String.format("* right side: need %s, actual %s", needZoneRight, zoneRight)); - } - } - - @Override - public boolean removeFromZone(Game game, Zone fromZone, Ability source) { - // zone contains only one main card - return super.removeFromZone(game, fromZone, source); - } - - @Override - public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { - if (isCopy()) { // same as meld cards - super.updateZoneChangeCounter(game, event); - return; - } - super.updateZoneChangeCounter(game, event); - leftHalfCard.updateZoneChangeCounter(game, event); - rightHalfCard.updateZoneChangeCounter(game, event); - } - - @Override - public Counters getCounters(Game game) { - return getCounters(game.getState()); - } - - @Override - public Counters getCounters(GameState state) { - return state.getCardState(leftHalfCard.getId()).getCounters(); - } - - @Override - public boolean addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List appliedEffects, boolean isEffect, int maxCounters) { - return leftHalfCard.addCounters(counter, playerAddingCounters, source, game, appliedEffects, isEffect, maxCounters); - } - - @Override - public void removeCounters(String counterName, int amount, Ability source, Game game) { - leftHalfCard.removeCounters(counterName, amount, source, game); } @Override @@ -236,201 +58,22 @@ public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithH case MODAL_RIGHT: return this.rightHalfCard.cast(game, fromZone, ability, controllerId); default: - if (this.leftHalfCard.getSpellAbility() != null) { - this.leftHalfCard.getSpellAbility().setControllerId(controllerId); - } - if (this.rightHalfCard.getSpellAbility() != null) { - this.rightHalfCard.getSpellAbility().setControllerId(controllerId); - } return super.cast(game, fromZone, ability, controllerId); } } @Override - public List getSuperType(Game game) { - // CardImpl's constructor can call some code on init, so you must check left/right before - // it's a bad workaround - return leftHalfCard != null ? leftHalfCard.getSuperType(game) : supertype; + public boolean isTransformable() { + return this.getLeftHalfCard().isPermanent() && this.getRightHalfCard().isPermanent(); } @Override - public List getCardType(Game game) { - // CardImpl's constructor can call some code on init, so you must check left/right before - // it's a bad workaround - return leftHalfCard != null ? leftHalfCard.getCardType(game) : cardType; + public ModalDoubleFacedCardHalf getLeftHalfCard() { + return (ModalDoubleFacedCardHalf) leftHalfCard; } @Override - public SubTypes getSubtype() { - // rules: While a double-faced card isn’t on the stack or battlefield, consider only the characteristics of its front face. - // CardImpl's constructor can call some code on init, so you must check left/right before - return leftHalfCard != null ? leftHalfCard.getSubtype() : subtype; - } - - @Override - public SubTypes getSubtype(Game game) { - // rules: While a double-faced card isn’t on the stack or battlefield, consider only the characteristics of its front face. - // CardImpl's constructor can call some code on init, so you must check left/right before - return leftHalfCard != null ? leftHalfCard.getSubtype(game) : subtype; - } - - @Override - public boolean hasSubtype(SubType subtype, Game game) { - return leftHalfCard.hasSubtype(subtype, game); - } - - @Override - public Abilities getAbilities() { - return getInnerAbilities(true, true); - } - - @Override - public Abilities getInitAbilities() { - // must init only parent related abilities, spell card must be init separately - return getInnerAbilities(false, false); - } - - public Abilities getSharedAbilities(Game game) { - // no shared abilities for mdf cards (e.g. must be left or right only) - return new AbilitiesImpl<>(); - } - - @Override - public Abilities getAbilities(Game game) { - return getInnerAbilities(game, true, true); - } - - private boolean isIgnoreDefaultAbility(Ability ability) { - // ignore default play/spell ability from main card (only halfes are actual) - // default abilities added on card creation from card type and can't be skipped - - // skip cast spell - if (ability instanceof SpellAbility && ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.MODAL) { - return true; - } - - // skip play land - return ability instanceof PlayLandAbility; - } - - private Abilities getInnerAbilities(Game game, boolean showLeftSide, boolean showRightSide) { - Abilities allAbilites = new AbilitiesImpl<>(); - - for (Ability ability : super.getAbilities(game)) { - if (isIgnoreDefaultAbility(ability)) { - continue; - } - allAbilites.add(ability); - } - - if (showLeftSide) { - allAbilites.addAll(leftHalfCard.getAbilities(game)); - } - if (showRightSide) { - allAbilites.addAll(rightHalfCard.getAbilities(game)); - } - - return allAbilites; - } - - private Abilities getInnerAbilities(boolean showLeftSide, boolean showRightSide) { - Abilities allAbilites = new AbilitiesImpl<>(); - - for (Ability ability : super.getAbilities()) { - if (isIgnoreDefaultAbility(ability)) { - continue; - } - allAbilites.add(ability); - } - - if (showLeftSide) { - allAbilites.addAll(leftHalfCard.getAbilities()); - } - - if (showRightSide) { - allAbilites.addAll(rightHalfCard.getAbilities()); - } - - return allAbilites; - } - - @Override - public List getRules() { - // rules must show only main side (another side visible by toggle/transform button in GUI) - // card hints from both sides - return CardUtil.getCardRulesWithAdditionalInfo( - this, - this.getInnerAbilities(true, false), - this.getInnerAbilities(true, true) - ); - } - - @Override - public List getRules(Game game) { - // rules must show only main side (another side visible by toggle/transform button in GUI) - // card hints from both sides - return CardUtil.getCardRulesWithAdditionalInfo( - game, - this, - this.getInnerAbilities(game, true, false), - this.getInnerAbilities(game, true, true) - ); - } - - @Override - public boolean hasAbility(Ability ability, Game game) { - return super.hasAbility(ability, game); - } - - @Override - public ObjectColor getColor() { - return leftHalfCard.getColor(); - } - - @Override - public ObjectColor getColor(Game game) { - return leftHalfCard.getColor(game); - } - - @Override - public ObjectColor getFrameColor(Game game) { - return leftHalfCard.getFrameColor(game); - } - - @Override - public void setOwnerId(UUID ownerId) { - super.setOwnerId(ownerId); - abilities.setControllerId(ownerId); - leftHalfCard.getAbilities().setControllerId(ownerId); - leftHalfCard.setOwnerId(ownerId); - rightHalfCard.getAbilities().setControllerId(ownerId); - rightHalfCard.setOwnerId(ownerId); - } - - @Override - public ManaCosts getManaCost() { - return leftHalfCard.getManaCost(); - } - - @Override - public int getManaValue() { - // Rules: - // The converted mana cost of a modal double-faced card is based on the characteristics of the - // face that’s being considered. On the stack and battlefield, consider whichever face is up. - // In all other zones, consider only the front face. This is different than how the converted - // mana cost of a transforming double-faced card is determined. - - // on stack or battlefield it must be half card with own cost - return leftHalfCard.getManaValue(); - } - - @Override - public MageInt getPower() { - return leftHalfCard.getPower(); - } - - @Override - public MageInt getToughness() { - return leftHalfCard.getToughness(); + public ModalDoubleFacedCardHalf getRightHalfCard() { + return (ModalDoubleFacedCardHalf) rightHalfCard; } } diff --git a/Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalf.java b/Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalf.java index 78cba6bd606..0626b54807b 100644 --- a/Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalf.java +++ b/Mage/src/main/java/mage/cards/ModalDoubleFacedCardHalf.java @@ -1,16 +1,34 @@ package mage.cards; -import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SpellAbilityType; +import mage.constants.SubType; +import mage.constants.SuperType; -/** - * @author JayDi85 - */ -public interface ModalDoubleFacedCardHalf extends SubCard { +import java.util.UUID; + +public class ModalDoubleFacedCardHalf extends DoubleFacedCardHalf { + + public ModalDoubleFacedCardHalf( + UUID ownerId, CardSetInfo setInfo, + SuperType[] cardSuperTypes, CardType[] cardTypes, SubType[] cardSubTypes, + String costs, ModalDoubleFacedCard parentCard, SpellAbilityType spellAbilityType + ) { + super(ownerId, setInfo, cardSuperTypes, cardTypes, cardSubTypes, costs, parentCard, spellAbilityType); + } + + protected ModalDoubleFacedCardHalf(final ModalDoubleFacedCardHalf card) { + super(card); + this.parentCard = card.parentCard; + } @Override - ModalDoubleFacedCardHalf copy(); + public ModalDoubleFacedCardHalf copy() { + return new ModalDoubleFacedCardHalf(this); + } - void setPT(int power, int toughness); - - void setPT(MageInt power, MageInt toughness); + @Override + public ModalDoubleFacedCard getParentCard() { + return (ModalDoubleFacedCard) parentCard; + } } diff --git a/Mage/src/main/java/mage/cards/RoomCard.java b/Mage/src/main/java/mage/cards/RoomCard.java index 2b44bcad48e..8685b9c84f1 100644 --- a/Mage/src/main/java/mage/cards/RoomCard.java +++ b/Mage/src/main/java/mage/cards/RoomCard.java @@ -1,27 +1,20 @@ package mage.cards; -import java.util.UUID; - +import mage.ObjectColor; import mage.abilities.Abilities; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; -import mage.abilities.common.RoomUnlockAbility; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.common.UnlockThisDoorTriggeredAbility; -import mage.abilities.condition.common.RoomHalfLockedCondition; -import mage.abilities.costs.mana.ManaCosts; -import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.common.RoomAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.RoomCharacteristicsEffect; import mage.constants.CardType; -import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SpellAbilityType; import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; -import mage.abilities.effects.common.continuous.LoseAbilitySourceEffect; + +import java.util.UUID; /** * @author oscscull @@ -43,6 +36,13 @@ public abstract class RoomCard extends SplitCard { this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), types, costsRight, this, SpellAbilityType.SPLIT_RIGHT); + + // Add the one-shot effect to unlock a door on cast -> ETB + Ability entersAbility = new EntersBattlefieldAbility(new RoomEnterUnlockEffect()); + entersAbility.setRuleVisible(false); + this.addAbility(entersAbility); + + this.addAbility(new RoomAbility()); } protected RoomCard(RoomCard card) { @@ -58,56 +58,6 @@ public abstract class RoomCard extends SplitCard { this.lastCastHalf = lastCastHalf; } - protected void addRoomAbilities(Ability leftAbility, Ability rightAbility) { - getLeftHalfCard().addAbility(leftAbility); - getRightHalfCard().addAbility(rightAbility); - this.addAbility(leftAbility.copy()); - this.addAbility(rightAbility.copy()); - - // Add the one-shot effect to unlock a door on cast -> ETB - Ability entersAbility = new EntersBattlefieldAbility(new RoomEnterUnlockEffect()); - entersAbility.setRuleVisible(false); - this.addAbility(entersAbility); - - // Remove locked door abilities - keeping unlock triggers (or they won't trigger - // when unlocked) - if (leftAbility != null && !(leftAbility instanceof UnlockThisDoorTriggeredAbility)) { - Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( - new LoseAbilitySourceEffect(leftAbility, Duration.WhileOnBattlefield), - RoomHalfLockedCondition.LEFT, "")).setRuleVisible(false); - this.addAbility(ability); - } - - if (rightAbility != null && !(rightAbility instanceof UnlockThisDoorTriggeredAbility)) { - Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect( - new LoseAbilitySourceEffect(rightAbility, Duration.WhileOnBattlefield), - RoomHalfLockedCondition.RIGHT, "")).setRuleVisible(false); - this.addAbility(ability); - } - - // Add the Special Action to unlock doors. - // These will ONLY be active if the corresponding half is LOCKED! - if (leftAbility != null) { - ManaCosts leftHalfManaCost = null; - if (this.getLeftHalfCard() != null && this.getLeftHalfCard().getSpellAbility() != null) { - leftHalfManaCost = this.getLeftHalfCard().getSpellAbility().getManaCosts(); - } - RoomUnlockAbility leftUnlockAbility = new RoomUnlockAbility(leftHalfManaCost, true); - this.addAbility(leftUnlockAbility.setRuleAtTheTop(true)); - } - - if (rightAbility != null) { - ManaCosts rightHalfManaCost = null; - if (this.getRightHalfCard() != null && this.getRightHalfCard().getSpellAbility() != null) { - rightHalfManaCost = this.getRightHalfCard().getSpellAbility().getManaCosts(); - } - RoomUnlockAbility rightUnlockAbility = new RoomUnlockAbility(rightHalfManaCost, false); - this.addAbility(rightUnlockAbility.setRuleAtTheTop(true)); - } - - this.addAbility(new RoomAbility()); - } - @Override public Abilities getAbilities() { return this.abilities; @@ -131,6 +81,41 @@ public abstract class RoomCard extends SplitCard { game.setZone(getLeftHalfCard().getId(), zone); game.setZone(getRightHalfCard().getId(), zone); } + + public static void setRoomCharacteristics(Permanent permanent, Game game) { + if (!(permanent.getMainCard() instanceof RoomCard)) { + return; + } + setRoomCharacteristics(permanent, (RoomCard) permanent.getMainCard(), game, permanent.isLeftDoorUnlocked(), permanent.isRightDoorUnlocked()); + } + + // Static method for setting room characteristics on permanents + public static void setRoomCharacteristics(Permanent permanent, RoomCard roomCard, Game game, boolean isLeftUnlocked, boolean isRightUnlocked) { + permanent.setName(roomCard.name); + + permanent.setManaCost(roomCard.getManaCost()); + + // Set color indicator based on unlocked halves + ObjectColor newColor = new ObjectColor(); + if (isLeftUnlocked && roomCard.getLeftHalfCard() != null) { + newColor.addColor(roomCard.getLeftHalfCard().getColor()); + } + if (isRightUnlocked && roomCard.getRightHalfCard() != null) { + newColor.addColor(roomCard.getRightHalfCard().getColor()); + } + permanent.getColor().setColor(roomCard.getColor()); + + // Get abilities from each half + Abilities leftAbilities = roomCard.getLeftHalfCard().getAbilities(); + for (Ability ability : leftAbilities) { + permanent.addAbility(ability, roomCard.getLeftHalfCard().getId(), game, true); + } + + Abilities rightAbilities = roomCard.getRightHalfCard().getAbilities(); + for (Ability ability : rightAbilities) { + permanent.addAbility(ability, roomCard.getRightHalfCard().getId(), game,true); + } + } } class RoomEnterUnlockEffect extends OneShotEffect { @@ -189,27 +174,3 @@ class RoomEnterUnlockEffect extends OneShotEffect { } } -// For the overall Room card flavor text and mana value effect. -class RoomAbility extends SimpleStaticAbility { - public RoomAbility() { - super(Zone.ALL, null); - this.setRuleVisible(true); - this.setRuleAtTheTop(true); - this.addEffect(new RoomCharacteristicsEffect()); - } - - protected RoomAbility(final RoomAbility ability) { - super(ability); - } - - @Override - public String getRule() { - return "(You may cast either half. That door unlocks on the battlefield. " + - "As a sorcery, you may pay the mana cost of a locked door to unlock it.)"; - } - - @Override - public RoomAbility copy() { - return new RoomAbility(this); - } -} \ No newline at end of file diff --git a/Mage/src/main/java/mage/cards/TransformingDoubleFacedCard.java b/Mage/src/main/java/mage/cards/TransformingDoubleFacedCard.java new file mode 100644 index 00000000000..aa56e519844 --- /dev/null +++ b/Mage/src/main/java/mage/cards/TransformingDoubleFacedCard.java @@ -0,0 +1,66 @@ +package mage.cards; + +import mage.abilities.SpellAbility; +import mage.constants.*; +import mage.game.Game; + +import java.util.UUID; + +public abstract class TransformingDoubleFacedCard extends DoubleFacedCard { + + public TransformingDoubleFacedCard( + UUID ownerId, CardSetInfo setInfo, + CardType[] typesLeft, SubType[] subTypesLeft, String costsLeft, + String secondSideName, + CardType[] typesRight, SubType[] subTypesRight, String colorRight + ) { + this( + ownerId, setInfo, + new SuperType[]{}, typesLeft, subTypesLeft, costsLeft, + secondSideName, + new SuperType[]{}, typesRight, subTypesRight, colorRight + ); + } + + public TransformingDoubleFacedCard( + UUID ownerId, CardSetInfo setInfo, + SuperType[] superTypesLeft, CardType[] typesLeft, SubType[] subTypesLeft, String costsLeft, + String secondSideName, + SuperType[] superTypesRight, CardType[] typesRight, SubType[] subTypesRight, String colorRight + ) { + super(ownerId, setInfo, typesLeft, costsLeft, SpellAbilityType.TRANSFORMED); + // main card name must be same as left side + leftHalfCard = new TransformingDoubleFacedCardHalf( + this.getOwnerId(), setInfo.copy(), + superTypesLeft, typesLeft, subTypesLeft, costsLeft, + this, SpellAbilityType.TRANSFORMED_LEFT + ); + rightHalfCard = new TransformingDoubleFacedCardHalf( + this.getOwnerId(), new CardSetInfo(secondSideName, setInfo), + superTypesRight, typesRight, subTypesRight, colorRight, this + ); + this.secondSideCard = rightHalfCard; + } + + public TransformingDoubleFacedCard(final TransformingDoubleFacedCard card) { + super(card); + } + + @Override + public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { + if (ability.getSpellAbilityType() == SpellAbilityType.BASE) { + return this.leftHalfCard.cast(game, fromZone, ability, controllerId); + } + return super.cast(game, fromZone, ability, controllerId); + } + + @Override + public TransformingDoubleFacedCardHalf getLeftHalfCard() { + return (TransformingDoubleFacedCardHalf) leftHalfCard; + } + + @Override + public TransformingDoubleFacedCardHalf getRightHalfCard() { + return (TransformingDoubleFacedCardHalf) rightHalfCard; + } +} diff --git a/Mage/src/main/java/mage/cards/TransformingDoubleFacedCardHalf.java b/Mage/src/main/java/mage/cards/TransformingDoubleFacedCardHalf.java new file mode 100644 index 00000000000..f82a910d852 --- /dev/null +++ b/Mage/src/main/java/mage/cards/TransformingDoubleFacedCardHalf.java @@ -0,0 +1,49 @@ +package mage.cards; + +import mage.ObjectColor; +import mage.abilities.SpellAbility; +import mage.constants.*; +import mage.game.Game; + +import java.util.UUID; + +public class TransformingDoubleFacedCardHalf extends DoubleFacedCardHalf { + + public TransformingDoubleFacedCardHalf( + UUID ownerId, CardSetInfo setInfo, + SuperType[] cardSuperTypes, CardType[] cardTypes, SubType[] cardSubTypes, + String costs, TransformingDoubleFacedCard parentCard, SpellAbilityType spellAbilityType + ) { + super(ownerId, setInfo, cardSuperTypes, cardTypes, cardSubTypes, costs, parentCard, spellAbilityType); + } + + protected TransformingDoubleFacedCardHalf(final TransformingDoubleFacedCardHalf card) { + super(card); + this.parentCard = card.parentCard; + } + + public TransformingDoubleFacedCardHalf( + UUID ownerId, CardSetInfo setInfo, + SuperType[] superTypesRight, CardType[] typesRight, SubType[] subTypesRight, String colorRight, TransformingDoubleFacedCard parentCard) { + super(ownerId, setInfo, superTypesRight, typesRight, subTypesRight, "", parentCard, SpellAbilityType.TRANSFORMED_RIGHT); + this.getColor().setColor(new ObjectColor(colorRight)); + } + + @Override + public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { + if (ability.getSpellAbilityCastMode() == SpellAbilityCastMode.DISTURB && !isBackSide()) { + return getOtherSide().cast(game, fromZone, ability, controllerId); + } + return super.cast(game, fromZone, ability, controllerId); + } + + @Override + public TransformingDoubleFacedCardHalf copy() { + return new TransformingDoubleFacedCardHalf(this); + } + + @Override + public TransformingDoubleFacedCard getParentCard() { + return (TransformingDoubleFacedCard) parentCard; + } +} diff --git a/Mage/src/main/java/mage/cards/mock/MockCard.java b/Mage/src/main/java/mage/cards/mock/MockCard.java index 6d768ef8b67..04e28b30a09 100644 --- a/Mage/src/main/java/mage/cards/mock/MockCard.java +++ b/Mage/src/main/java/mage/cards/mock/MockCard.java @@ -5,7 +5,7 @@ import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.cards.CardImpl; -import mage.cards.ModalDoubleFacedCard; +import mage.cards.DoubleFacedCard; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.util.CardUtil; @@ -35,7 +35,7 @@ public class MockCard extends CardImpl implements MockableCard { protected List manaCostRightStr; protected List manaCostStr; protected String spellOptionName; // adventure/omen spell name - protected boolean isModalDoubleFacedCard; + protected boolean isDoubleFacedCard; protected int manaValue; public MockCard(CardInfo card) { @@ -67,7 +67,7 @@ public class MockCard extends CardImpl implements MockableCard { this.nightCard = card.isNightCard(); - if (card.getSecondSideName() != null && !card.getSecondSideName().isEmpty()) { + if (card.getSecondSideName() != null && !card.getSecondSideName().isEmpty() && !card.isDoubleFacedCard()) { this.secondSideCard = new MockCard(CardRepository.instance.findCardWithPreferredSetAndNumber(card.getSecondSideName(), card.getSetCode(), card.getCardNumber())); } @@ -75,11 +75,11 @@ public class MockCard extends CardImpl implements MockableCard { this.spellOptionName = card.getSpellOptionCardName(); } - if (card.isModalDoubleFacedCard()) { - ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card.createCard(); + if (card.isDoubleFacedCard()) { + DoubleFacedCard mdfCard = (DoubleFacedCard) card.createCard(); CardInfo mdfSecondSide = new CardInfo(mdfCard.getRightHalfCard()); this.secondSideCard = new MockCard(mdfSecondSide); - this.isModalDoubleFacedCard = true; + this.isDoubleFacedCard = true; } this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty()); @@ -102,7 +102,7 @@ public class MockCard extends CardImpl implements MockableCard { this.manaCostRightStr = new ArrayList<>(card.manaCostRightStr); this.manaCostStr = new ArrayList<>(card.manaCostStr); this.spellOptionName = card.spellOptionName; - this.isModalDoubleFacedCard = card.isModalDoubleFacedCard; + this.isDoubleFacedCard = card.isDoubleFacedCard; this.manaValue = card.manaValue; } @@ -157,7 +157,7 @@ public class MockCard extends CardImpl implements MockableCard { if (spellOptionName != null) { return getName() + CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + spellOptionName; - } else if (isModalDoubleFacedCard) { + } else if (isDoubleFacedCard) { return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + this.getSecondCardFace().getName(); } else { return getName(); @@ -181,6 +181,6 @@ public class MockCard extends CardImpl implements MockableCard { @Override public boolean isTransformable() { // must enable toggle mode in deck editor (switch between card sides); - return super.isTransformable() || this.isModalDoubleFacedCard || this.secondSideCard != null; + return super.isTransformable() || this.isDoubleFacedCard || this.secondSideCard != null; } } diff --git a/Mage/src/main/java/mage/cards/repository/CardInfo.java b/Mage/src/main/java/mage/cards/repository/CardInfo.java index e9b8a2aab24..ecf3225f6fc 100644 --- a/Mage/src/main/java/mage/cards/repository/CardInfo.java +++ b/Mage/src/main/java/mage/cards/repository/CardInfo.java @@ -110,9 +110,9 @@ public class CardInfo { @DatabaseField protected String spellOptionCardName; @DatabaseField - protected boolean modalDoubleFacedCard; + protected boolean doubleFacedCard; @DatabaseField - protected String modalDoubleFacedSecondSideName; + protected String doubleFacedSecondSideName; @DatabaseField protected String meldsToCardName; @DatabaseField @@ -162,9 +162,9 @@ public class CardInfo { this.spellOptionCardName = ((CardWithSpellOption) card).getSpellCard().getName(); } - if (card instanceof ModalDoubleFacedCard) { - this.modalDoubleFacedCard = true; - this.modalDoubleFacedSecondSideName = ((ModalDoubleFacedCard) card).getRightHalfCard().getName(); + if (card instanceof DoubleFacedCard) { + this.doubleFacedCard = true; + this.doubleFacedSecondSideName = ((DoubleFacedCard) card).getRightHalfCard().getName(); } if (card.getFrameStyle() != null) { @@ -483,12 +483,12 @@ public class CardInfo { return spellOptionCardName; } - public boolean isModalDoubleFacedCard() { - return modalDoubleFacedCard; + public boolean isDoubleFacedCard() { + return doubleFacedCard; } - public String getModalDoubleFacedSecondSideName() { - return modalDoubleFacedSecondSideName; + public String getDoubleFacedSecondSideName() { + return doubleFacedSecondSideName; } @Override diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 9f6e39d6a75..d3ebbcfbc35 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -126,7 +126,7 @@ public enum CardRepository { } private void addNewNames(CardInfo card, Set namesList) { - // require before call: qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName"...); + // require before call: qb.distinct().selectColumns("name", "doubleFacedSecondSideName"...); // normal names int result = card.getName().indexOf(" // "); @@ -141,8 +141,8 @@ public enum CardRepository { if (card.getSecondSideName() != null && !card.getSecondSideName().isEmpty()) { namesList.add(card.getSecondSideName()); } - if (card.getModalDoubleFacedSecondSideName() != null && !card.getModalDoubleFacedSecondSideName().isEmpty()) { - namesList.add(card.getModalDoubleFacedSecondSideName()); + if (card.getDoubleFacedSecondSideName() != null && !card.getDoubleFacedSecondSideName().isEmpty()) { + namesList.add(card.getDoubleFacedSecondSideName()); } if (card.getFlipCardName() != null && !card.getFlipCardName().isEmpty()) { namesList.add(card.getFlipCardName()); @@ -166,7 +166,7 @@ public enum CardRepository { } try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); + qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { addNewNames(card, names); @@ -185,7 +185,7 @@ public enum CardRepository { } try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); + qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().not().like("types", new SelectArg('%' + CardType.LAND.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -205,7 +205,7 @@ public enum CardRepository { } try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); + qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); Where where = qb.where(); where.and( where.not().like("supertypes", '%' + SuperType.BASIC.name() + '%'), @@ -229,7 +229,7 @@ public enum CardRepository { } try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); + qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().not().like("supertypes", new SelectArg('%' + SuperType.BASIC.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -249,7 +249,7 @@ public enum CardRepository { } try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); + qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().like("types", new SelectArg('%' + CardType.CREATURE.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -269,7 +269,7 @@ public enum CardRepository { } try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); + qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().like("types", new SelectArg('%' + CardType.ARTIFACT.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -289,7 +289,7 @@ public enum CardRepository { } try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); + qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); Where where = qb.where(); where.and( where.not().like("types", '%' + CardType.CREATURE.name() + '%'), @@ -313,7 +313,7 @@ public enum CardRepository { } try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); + qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); Where where = qb.where(); where.and( where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'), @@ -539,7 +539,7 @@ public enum CardRepository { .eq("flipCardName", new SelectArg(name)).or() .eq("secondSideName", new SelectArg(name)).or() .eq("spellOptionCardName", new SelectArg(name)).or() - .eq("modalDoubleFacedSecondSideName", new SelectArg(name)); + .eq("doubleFacedSecondSideName", new SelectArg(name)); results = cardsDao.query(queryBuilder.prepare()); } else { // Check that a full card was found and not a SplitCardHalf diff --git a/Mage/src/main/java/mage/constants/PutCards.java b/Mage/src/main/java/mage/constants/PutCards.java index 59ffc721e5f..2d93243ff52 100644 --- a/Mage/src/main/java/mage/constants/PutCards.java +++ b/Mage/src/main/java/mage/constants/PutCards.java @@ -2,9 +2,7 @@ package mage.constants; import mage.abilities.Ability; import mage.abilities.keyword.TransformAbility; -import mage.cards.Card; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.cards.*; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -92,6 +90,9 @@ public enum PutCards { case SHUFFLE: return player.shuffleCardsToLibrary(card, game, source); case BATTLEFIELD_TRANSFORMED: + if (card instanceof TransformingDoubleFacedCard) { + card = ((TransformingDoubleFacedCard) card).getRightHalfCard(); + } game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId(), Boolean.TRUE); case BATTLEFIELD: case EXILED: diff --git a/Mage/src/main/java/mage/constants/SpellAbilityType.java b/Mage/src/main/java/mage/constants/SpellAbilityType.java index fac7a218aef..a0da5bcd762 100644 --- a/Mage/src/main/java/mage/constants/SpellAbilityType.java +++ b/Mage/src/main/java/mage/constants/SpellAbilityType.java @@ -11,6 +11,9 @@ public enum SpellAbilityType { SPLIT_FUSED("Split SpellAbility"), SPLIT_LEFT("LeftSplit SpellAbility"), SPLIT_RIGHT("RightSplit SpellAbility"), + TRANSFORMED("Transformed SpellAbility"), + TRANSFORMED_LEFT("TransformFront SpellAbility"), + TRANSFORMED_RIGHT("TransformBack SpellAbility"), MODAL("Modal SpellAbility"), // used for modal double faces cards MODAL_LEFT("LeftModal SpellAbility"), MODAL_RIGHT("RightModal SpellAbility"), diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 82d0ad5ce63..ff45386549a 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -56,6 +56,7 @@ import mage.game.mulligan.Mulligan; import mage.game.permanent.Battlefield; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; +import mage.game.permanent.PermanentToken; import mage.game.stack.Spell; import mage.game.stack.SpellStack; import mage.game.stack.StackAbility; @@ -129,8 +130,6 @@ public abstract class GameImpl implements Game { // For checking "becomes the target" triggers accurately. Cleared on short living LKI reset protected Map>> targetedMap = new HashMap<>(); - // Permanents entering the Battlefield while handling replacement effects before they are added to the battlefield - protected Map permanentsEntering = new HashMap<>(); // used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist) protected Map enterWithCounters = new HashMap<>(); @@ -214,7 +213,6 @@ public abstract class GameImpl implements Game { this.lkiShortLiving = CardUtil.deepCopyObject(game.lkiShortLiving); this.targetedMap = CardUtil.deepCopyObject(game.targetedMap); - this.permanentsEntering = CardUtil.deepCopyObject(game.permanentsEntering); this.enterWithCounters = CardUtil.deepCopyObject(game.enterWithCounters); this.state = game.state.copy(); @@ -341,13 +339,13 @@ public abstract class GameImpl implements Game { Card rightCard = ((SplitCard) card).getRightHalfCard(); rightCard.setOwnerId(ownerId); addCardToState(rightCard); - } else if (card instanceof ModalDoubleFacedCard) { + } else if (card instanceof DoubleFacedCard) { // left - Card leftCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); + Card leftCard = ((DoubleFacedCard) card).getLeftHalfCard(); leftCard.setOwnerId(ownerId); addCardToState(leftCard); // right - Card rightCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + Card rightCard = ((DoubleFacedCard) card).getRightHalfCard(); rightCard.setOwnerId(ownerId); addCardToState(rightCard); } else if (card instanceof CardWithSpellOption) { @@ -764,12 +762,12 @@ public abstract class GameImpl implements Game { @Override public Permanent getPermanentEntering(UUID permanentId) { - return permanentsEntering.get(permanentId); + return state.getBattlefield().getPermanentsEntering().get(permanentId); } @Override public Map getPermanentsEntering() { - return permanentsEntering; + return state.getBattlefield().getPermanentsEntering(); } @Override @@ -2109,6 +2107,7 @@ public abstract class GameImpl implements Game { newBluePrint = copyFromPermanent.copy(); // reset to original characteristics + newBluePrint.resetLockedStatus(); // reset locked status so room characteristics are correct newBluePrint.reset(this); // workaround to find real copyable characteristics of transformed/facedown/etc permanents @@ -2118,7 +2117,9 @@ public abstract class GameImpl implements Game { BecomesFaceDownCreatureEffect.makeFaceDownObject(this, null, newBluePrint, faceDownType, null); } newBluePrint.assignNewId(); - if (copyFromPermanent.isTransformed()) { + // TODO: should be able to remove after tdfc rework + if (copyFromPermanent.isTransformed() && (copyFromPermanent instanceof PermanentToken || ((copyFromPermanent instanceof PermanentCard) && + !(((PermanentCard) copyFromPermanent).getCard() instanceof DoubleFacedCardHalf)))) { TransformAbility.transformPermanent(newBluePrint, this, source); } if (copyFromPermanent.isPrototyped()) { @@ -3818,7 +3819,7 @@ public abstract class GameImpl implements Game { loadCards(ownerId, hand); loadCards(ownerId, battlefield .stream() - .map(PutToBattlefieldInfo::getCard) + .map(PutToBattlefieldInfo::getMainCard) .collect(Collectors.toList()) ); loadCards(ownerId, graveyard); diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index c9963203f9e..86b38e24117 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -670,9 +670,9 @@ public class GameState implements Serializable, Copyable { for (Player player : players.values()) { player.reset(); } + this.reset(); battlefield.reset(game); combat.reset(game); - this.reset(); effects.apply(game); combat.checkForRemoveFromCombat(game); } @@ -1627,19 +1627,19 @@ public class GameState implements Serializable, Copyable { copiedParts.add(rightCopied); // sync parts ((SplitCard) copiedCard).setParts(leftCopied, rightCopied); - } else if (copiedCard instanceof ModalDoubleFacedCard) { + } else if (copiedCard instanceof DoubleFacedCard) { // left - ModalDoubleFacedCardHalf leftOriginal = ((ModalDoubleFacedCard) copiedCard).getLeftHalfCard(); - ModalDoubleFacedCardHalf leftCopied = leftOriginal.copy(); + DoubleFacedCardHalf leftOriginal = ((DoubleFacedCard) copiedCard).getLeftHalfCard(); + DoubleFacedCardHalf leftCopied = (DoubleFacedCardHalf) leftOriginal.copy(); prepareCardForCopy(leftOriginal, leftCopied, newController); copiedParts.add(leftCopied); // right - ModalDoubleFacedCardHalf rightOriginal = ((ModalDoubleFacedCard) copiedCard).getRightHalfCard(); - ModalDoubleFacedCardHalf rightCopied = rightOriginal.copy(); + DoubleFacedCardHalf rightOriginal = ((DoubleFacedCard) copiedCard).getRightHalfCard(); + DoubleFacedCardHalf rightCopied = (DoubleFacedCardHalf) rightOriginal.copy(); prepareCardForCopy(rightOriginal, rightCopied, newController); copiedParts.add(rightCopied); // sync parts - ((ModalDoubleFacedCard) copiedCard).setParts(leftCopied, rightCopied); + ((DoubleFacedCard) copiedCard).setParts(leftCopied, rightCopied); } else if (copiedCard instanceof CardWithSpellOption) { // right SpellOptionCard rightOriginal = ((CardWithSpellOption) copiedCard).getSpellCard(); diff --git a/Mage/src/main/java/mage/game/PutToBattlefieldInfo.java b/Mage/src/main/java/mage/game/PutToBattlefieldInfo.java index 404cde602e6..912157935a8 100644 --- a/Mage/src/main/java/mage/game/PutToBattlefieldInfo.java +++ b/Mage/src/main/java/mage/game/PutToBattlefieldInfo.java @@ -21,6 +21,10 @@ public class PutToBattlefieldInfo { return card; } + public Card getMainCard() { + return card.getMainCard(); + } + public boolean isTapped() { return tapped; } diff --git a/Mage/src/main/java/mage/game/ZonesHandler.java b/Mage/src/main/java/mage/game/ZonesHandler.java index 9a1c26c8154..ad215f21baf 100644 --- a/Mage/src/main/java/mage/game/ZonesHandler.java +++ b/Mage/src/main/java/mage/game/ZonesHandler.java @@ -89,21 +89,31 @@ public final class ZonesHandler { ZoneChangeInfo info = itr.next(); if (info.event.getToZone().equals(Zone.BATTLEFIELD)) { Card card = game.getCard(info.event.getTargetId()); - if (card instanceof ModalDoubleFacedCard || card instanceof ModalDoubleFacedCardHalf) { + if (card instanceof DoubleFacedCard || card instanceof DoubleFacedCardHalf) { boolean forceToMainSide = false; + // TODO: move transform key or have some other identifier after tdfc rework + Boolean enterTransformed = (Boolean) game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId()); + if (enterTransformed == null) { + enterTransformed = false; + } // if effect put half mdf card to battlefield then it must be the main side only (example: return targeted half card to battle) - if (card instanceof ModalDoubleFacedCardHalf && !source.getAbilityType().isPlayCardAbility()) { + if (card instanceof DoubleFacedCardHalf && !source.getAbilityType().isPlayCardAbility() && !enterTransformed) { forceToMainSide = true; } // if effect put mdf card to battlefield then it must be main side only - if (card instanceof ModalDoubleFacedCard) { + if (card instanceof DoubleFacedCard) { forceToMainSide = true; } if (forceToMainSide) { - info.event.setTargetId(((ModalDoubleFacedCard) card.getMainCard()).getLeftHalfCard().getId()); + info.event.setTargetId(((DoubleFacedCard) card.getMainCard()).getLeftHalfCard().getId()); + } + + // if left half is being moved, but entering transformed, change to transformed side + if (enterTransformed && card instanceof DoubleFacedCardHalf && !((DoubleFacedCardHalf) card).isBackSide()) { + info.event.setTargetId(((DoubleFacedCardHalf) card).getOtherSide().getId()); } } } @@ -154,10 +164,10 @@ public final class ZonesHandler { // meld/group cards must be independent (use can choose order) cardsToMove = ((MeldCard) targetCard).getHalves(); cardsToUpdate.get(toZone).addAll(cardsToMove); - } else if (targetCard instanceof ModalDoubleFacedCard - || targetCard instanceof ModalDoubleFacedCardHalf) { + } else if (targetCard instanceof DoubleFacedCard + || targetCard instanceof DoubleFacedCardHalf) { // mdf cards must be moved as single object, but each half must be updated separately - ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) targetCard.getMainCard(); + DoubleFacedCard mdfCard = (DoubleFacedCard) targetCard.getMainCard(); cardsToMove = new CardsImpl(mdfCard); cardsToUpdate.get(toZone).add(mdfCard); // example: cast left side @@ -296,7 +306,7 @@ public final class ZonesHandler { } else { game.setZone(event.getTargetId(), event.getToZone()); } - + // update zone in other parts (meld cards, mdf half cards) cardsToUpdate.entrySet().forEach(entry -> { for (Card card : entry.getValue().getCards(game)) { @@ -373,8 +383,9 @@ public final class ZonesHandler { * that isn't a transforming double-faced card onto the battlefield transformed or converted, that card stays in * its current zone. */ + // TODO: remove after tdfc rework boolean wantToTransform = Boolean.TRUE.equals(game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId())); - if (wantToTransform) { + if (wantToTransform && !(card instanceof DoubleFacedCardHalf)) { isGoodToMove = card.isTransformable() && card.getSecondCardFace().isPermanent(game); } else { isGoodToMove = card.isPermanent(game); @@ -407,8 +418,7 @@ public final class ZonesHandler { } else if (card instanceof RoomCardHalf) { // Only the main room card can etb permanent = new PermanentCard(card.getMainCard(), event.getPlayerId(), game); - } - else if (card instanceof ModalDoubleFacedCard) { + } else if (card instanceof DoubleFacedCard) { // main mdf card must be processed before that call (e.g. only halves can be moved to battlefield) throw new IllegalStateException("Unexpected trying of move mdf card to battlefield instead half"); } else if (card instanceof Permanent) { @@ -533,4 +543,4 @@ public final class ZonesHandler { return card; } -} \ No newline at end of file +} diff --git a/Mage/src/main/java/mage/game/permanent/Battlefield.java b/Mage/src/main/java/mage/game/permanent/Battlefield.java index 7a57d75d044..094c03eecad 100644 --- a/Mage/src/main/java/mage/game/permanent/Battlefield.java +++ b/Mage/src/main/java/mage/game/permanent/Battlefield.java @@ -18,6 +18,7 @@ import java.util.stream.Collectors; public class Battlefield implements Serializable { private final Map field = new LinkedHashMap<>(); + private final Map permanentsEntering = new LinkedHashMap<>(); public Battlefield() { } @@ -26,6 +27,9 @@ public class Battlefield implements Serializable { for (Entry entry : battlefield.field.entrySet()) { field.put(entry.getKey(), entry.getValue().copy()); } + for (Entry entry : battlefield.permanentsEntering.entrySet()) { + permanentsEntering.put(entry.getKey(), entry.getValue().copy()); + } } public Battlefield copy() { @@ -36,10 +40,14 @@ public class Battlefield implements Serializable { for (Permanent perm : field.values()) { perm.reset(game); } + for (Permanent perm : permanentsEntering.values()) { + perm.reset(game); + } } public void clear() { field.clear(); + permanentsEntering.clear(); } /** @@ -156,6 +164,11 @@ public class Battlefield implements Serializable { return field.containsKey(key); } + public Map getPermanentsEntering() { + return permanentsEntering; + } + + public void beginningOfTurn(Game game) { for (Permanent perm : field.values()) { perm.beginningOfTurn(game); diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index d043c659e5c..97ef2ba3491 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -479,6 +479,13 @@ public interface Permanent extends Card, Controllable { boolean wasRoomUnlockedOnCast(); + /** + * used to reset the locked status of a room. Only used when copying a room + * or creating a token copy of a room permanent. Could most likely be removed + * after a designation class added. + */ + void resetLockedStatus(); + boolean isLeftDoorUnlocked(); boolean isRightDoorUnlocked(); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index 2d58da03e3f..9139caea669 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -1,8 +1,10 @@ package mage.game.permanent; import mage.MageObject; +import mage.ObjectColor; import mage.abilities.Abilities; import mage.abilities.Ability; +import mage.abilities.common.RoomAbility; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.keyword.NightboundAbility; @@ -30,6 +32,8 @@ public class PermanentCard extends PermanentImpl { protected int maxLevelCounters; protected int zoneChangeCounter; + protected ObjectColor originalColor; + protected ObjectColor originalFrameColor; public PermanentCard(Card card, UUID controllerId, Game game) { super(card.getId(), card.getOwnerId(), controllerId, card.getName()); // card id @@ -46,7 +50,7 @@ public class PermanentCard extends PermanentImpl { // if you use it in test code or for permanent's copy effects then call CardUtil.getDefaultCardSideForBattlefield for default side // it's a basic check and still allows to create permanent from instant or sorcery boolean goodForBattlefield = true; - if (card instanceof ModalDoubleFacedCard) { + if (card instanceof DoubleFacedCard) { goodForBattlefield = false; } else if (card instanceof SplitCard) { // fused spells allowed (it uses main card) @@ -65,9 +69,24 @@ public class PermanentCard extends PermanentImpl { throw new IllegalArgumentException("Wrong code usage: can't create permanent card from split or mdf: " + card.getName()); } - this.card = card; + // if two permanent sides, set front and second side + if (card instanceof DoubleFacedCardHalf && card.isPermanent() && ((DoubleFacedCardHalf) card).getOtherSide().isPermanent()) { + if (((DoubleFacedCardHalf) card).isBackSide()) { + secondSideCard = card; + this.card = ((DoubleFacedCardHalf) card).getOtherSide().copy(); + this.transformed = true; + init(secondSideCard, game); + } else { + secondSideCard = ((DoubleFacedCardHalf) card).getOtherSide().copy(); + this.card = card; + init(card, game); + } + } else { + this.card = card; + init(card, game); + } + this.zoneChangeCounter = card.getZoneChangeCounter(game); // local value already set to the raised number - init(card, game); } private void init(Card card, Game game) { @@ -75,7 +94,7 @@ public class PermanentCard extends PermanentImpl { toughness = card.getToughness().copy(); startingLoyalty = card.getStartingLoyalty(); startingDefense = card.getStartingDefense(); - copyFromCard(card, game); + copyFromCard(card, game, false); // if temporary added abilities to the spell/card exist, you need to add it to the permanent derived from that card Abilities otherAbilities = game.getState().getAllOtherAbilities(card.getId()); if (otherAbilities != null) { @@ -86,10 +105,11 @@ public class PermanentCard extends PermanentImpl { } // if transformed on ETB - if (card.isTransformable()) { - if (game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId()) != null + // TODO: remove after tdfc rework + if (card.isTransformable() && !(card instanceof DoubleFacedCardHalf)) { + if (game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId()) != null || NightboundAbility.checkCard(this, game)) { - game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId(), null); + game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId(), null); TransformAbility.transformPermanent(this, game, null); } } @@ -100,19 +120,37 @@ public class PermanentCard extends PermanentImpl { this.card = permanent.card.copy(); this.maxLevelCounters = permanent.maxLevelCounters; this.zoneChangeCounter = permanent.zoneChangeCounter; + this.originalColor = permanent.originalColor.copy(); + this.originalFrameColor = permanent.originalFrameColor.copy(); } @Override public void reset(Game game) { // when the permanent is reset, copy all original values from the card // must copy card each reset so that the original values don't get modified - copyFromCard(card, game); + if (transformed && secondSideCard != null && getCard() instanceof DoubleFacedCardHalf) { + copyFromCard(secondSideCard, game, true); + } else { + copyFromCard(card, game, true); + } power.resetToBaseValue(); toughness.resetToBaseValue(); super.reset(game); } - protected void copyFromCard(final Card card, final Game game) { + @Override + protected void initOtherFace(Game game) { + if (!(secondSideCard instanceof DoubleFacedCardHalf)) { + return; + } + if (transformed) { + copyFromCard(secondSideCard, game, false); + } else { + copyFromCard(card, game, false); + } + } + + protected void copyFromCard(final Card card, final Game game, boolean isReset) { // TODO: must research - is it copy all fields or something miss this.name = card.getName(); this.abilities.clear(); @@ -122,6 +160,11 @@ public class PermanentCard extends PermanentImpl { this.abilities.add(ability.copy()); } } + } else if (card.getId() != this.getId()) { + // if different id, abilities need to be added to game state for continuous/triggers + for (Ability ability : card.getAbilities()) { + this.addAbility(ability, card.getId(), game, true); + } } else { // copy only own abilities; all dynamic added abilities must be added in the parent call this.abilities = card.getAbilities().copy(); @@ -131,13 +174,23 @@ public class PermanentCard extends PermanentImpl { this.abilities.setSourceId(objectId); this.cardType.clear(); this.cardType.addAll(card.getCardType()); - this.color = card.getColor(game).copy(); - this.frameColor = card.getFrameColor(game).copy(); + if (!isReset) { + // save color from game state on first creation + this.color = card.getColor(game).copy(); + this.frameColor = card.getFrameColor(game).copy(); + this.originalColor = card.getColor(game).copy(); + this.originalFrameColor = card.getFrameColor(game).copy(); + } else { + this.color = originalColor.copy(); + this.frameColor = originalFrameColor.copy(); + } this.frameStyle = card.getFrameStyle(); this.manaCost = card.getManaCost().copy(); if (card instanceof PermanentCard) { this.maxLevelCounters = ((PermanentCard) card).maxLevelCounters; } + this.power = card.getPower().copy(); + this.toughness = card.getToughness().copy(); this.subtype.copyFrom(card.getSubtype()); this.supertype.clear(); this.supertype.addAll(card.getSuperType()); @@ -149,7 +202,7 @@ public class PermanentCard extends PermanentImpl { this.setImageFileName(card.getImageFileName()); this.setImageNumber(card.getImageNumber()); - if (card.getSecondCardFace() != null) { + if (card.getSecondCardFace() != null && !(card instanceof DoubleFacedCardHalf)) { this.secondSideCardClazz = card.getSecondCardFace().getClass(); } if (card.getMeldsToCard() != null) { @@ -158,6 +211,22 @@ public class PermanentCard extends PermanentImpl { this.nightCard = card.isNightCard(); this.flipCard = card.isFlipCard(); this.flipCardName = card.getFlipCardName(); + // Rooms set characteristics at the end so nothing gets overwritten + if (card instanceof RoomCard) { + RoomCard.setRoomCharacteristics(this, game); + if (!isReset) { + RoomAbility roomAbility = null; + for (Ability ability : this.abilities) { + if (ability instanceof RoomAbility) { + roomAbility = (RoomAbility) ability; + break; + } + } + if (roomAbility != null) { + roomAbility.applyCharacteristics(game, this); + } + } + } } @Override diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index e76497c4a9c..1b096877cdc 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -17,6 +17,7 @@ import mage.abilities.hint.HintUtils; import mage.abilities.keyword.*; import mage.cards.Card; import mage.cards.CardImpl; +import mage.abilities.common.RoomAbility; import mage.constants.*; import mage.counters.Counter; import mage.counters.CounterType; @@ -706,12 +707,15 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { + CardUtil.getSourceLogName(game, source, this.getId())); this.setTransformed(!this.transformed); this.transformCount++; + initOtherFace(game); game.applyEffects(); // not process action - no firing of simultaneous events yet this.replaceEvent(EventType.TRANSFORMING, game); game.addSimultaneousEvent(GameEvent.getEvent(EventType.TRANSFORMED, this.getId(), this.getControllerId())); return true; } + protected abstract void initOtherFace(Game game); + @Override public int getTransformCount() { return transformCount; @@ -2099,6 +2103,12 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return roomWasUnlockedOnCast; } + @Override + public void resetLockedStatus() { + leftHalfUnlocked = false; + rightHalfUnlocked = false; + } + @Override public boolean isLeftDoorUnlocked() { return leftHalfUnlocked; @@ -2141,15 +2151,27 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { rightHalfUnlocked = true; } - // Fire door unlock event + // Update intrinsic stats/abilities from unlocking + // find the RoomCharacteristicsEffect applied by this permanent's ability + Abilities abilities = this.getAbilities(game); + for (Ability ability : abilities) { + if (ability instanceof RoomAbility) { + ((RoomAbility) ability).restoreUnlockedStats(game, this); + break; + } + } + + // Create door unlock event GameEvent event = new GameEvent(GameEvent.EventType.DOOR_UNLOCKED, getId(), source, source.getControllerId()); event.setFlag(isLeftDoor); - game.fireEvent(event); // Check if room is now fully unlocked boolean otherDoorUnlocked = isLeftDoor ? rightHalfUnlocked : leftHalfUnlocked; if (otherDoorUnlocked) { - game.fireEvent(new GameEvent(EventType.ROOM_FULLY_UNLOCKED, getId(), source, source.getControllerId())); + game.addSimultaneousEvent(event); + game.addSimultaneousEvent(new GameEvent(EventType.ROOM_FULLY_UNLOCKED, getId(), source, source.getControllerId())); + } else { + game.fireEvent(event); } return true; diff --git a/Mage/src/main/java/mage/game/permanent/PermanentToken.java b/Mage/src/main/java/mage/game/permanent/PermanentToken.java index 6ef20aff4de..024be2a2275 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentToken.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentToken.java @@ -5,8 +5,8 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCost; import mage.abilities.keyword.ChangelingAbility; -import mage.abilities.keyword.TransformAbility; import mage.cards.Card; +import mage.cards.RoomCard; import mage.constants.EmptyNames; import mage.game.Game; import mage.game.events.ZoneChangeEvent; @@ -30,13 +30,13 @@ public class PermanentToken extends PermanentImpl { this.token = token.copy(); this.token.getAbilities().newOriginalId(); // neccessary if token has ability like DevourAbility() this.token.getAbilities().setSourceId(objectId); - 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, game, null); + this.setTransformed(true); + this.copyFromToken(this.token.getBackFace(), game, false); + } else { + this.copyFromToken(this.token, game, false); // needed to have at this time (e.g. for subtypes for entersTheBattlefield replacement effects) } // token's ZCC must be synced with original token to keep abilities settings @@ -53,7 +53,11 @@ public class PermanentToken extends PermanentImpl { @Override public void reset(Game game) { - copyFromToken(token, game, true); + if (this.isTransformed()) { + copyFromToken(token.getBackFace(), game, true); + } else { + copyFromToken(token, game, true); + } super.reset(game); // Because the P/T objects have there own base value for reset we have to take it from there instead of from the basic token object this.power.resetToBaseValue(); @@ -110,8 +114,12 @@ public class PermanentToken extends PermanentImpl { if (this.abilities.containsClass(ChangelingAbility.class)) { this.subtype.setIsAllCreatureTypes(true); } - + this.power = new MageInt(token.getPower().getModifiedBaseValue()); + this.toughness = new MageInt(token.getToughness().getModifiedBaseValue()); CardUtil.copySetAndCardNumber(this, token); + if (token.getCopySourceCard() instanceof RoomCard) { + RoomCard.setRoomCharacteristics(this, game); + } } @Override @@ -161,4 +169,13 @@ public class PermanentToken extends PermanentImpl { public MageObject getOtherFace() { return this.transformed ? token : this.token.getBackFace(); } + + @Override + protected void initOtherFace(Game game) { + if (transformed) { + copyFromToken(token.getBackFace(), game, false); + } else { + copyFromToken(token, game, false); + } + } } diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index b18d326117b..e9b54a60375 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -2,6 +2,7 @@ package mage.game.stack; import mage.*; import mage.abilities.*; +import mage.abilities.common.SpellTransformedAbility; import mage.abilities.costs.mana.ActivationManaAbilityStep; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCosts; @@ -102,6 +103,11 @@ public class Spell extends StackObjectImpl implements Card { this.ability = ability; this.ability.setControllerId(controllerId); + // 712.8c TDFC spell "Its mana value is calculated using the mana cost of its front face" + if(ability instanceof SpellTransformedAbility && manaCost.isEmpty()) { + this.manaCost = card.getMainCard().getManaCost().copy(); + this.ability.setSourceId(affectedCard.getId()); // Maybe wrong? Permanent has incorrect id otherwise + } if (ability.getSpellAbilityCastMode().isFaceDown()) { // TODO: need research: // - why it use game param for color and subtype (possible bug?) @@ -1187,6 +1193,16 @@ public class Spell extends StackObjectImpl implements Card { throw new UnsupportedOperationException("Not supported."); } + @Override + public void setPT(int power, int toughness) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void setPT(MageInt power, MageInt toughness) { + throw new UnsupportedOperationException("Not supported."); + } + @Override public boolean cantBeAttachedBy(MageObject attachment, Ability source, Game game, boolean silentMode) { throw new UnsupportedOperationException("Not supported."); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index ef0b43ea920..e2c6348cbaa 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -4106,7 +4106,11 @@ public abstract class PlayerImpl implements Player, Serializable { getPlayableFromObjectSingle(game, fromZone, mainCard.getLeftHalfCard(), mainCard.getLeftHalfCard().getAbilities(game), availableMana, output); getPlayableFromObjectSingle(game, fromZone, mainCard.getRightHalfCard(), mainCard.getRightHalfCard().getAbilities(game), availableMana, output); getPlayableFromObjectSingle(game, fromZone, mainCard, mainCard.getSharedAbilities(game), availableMana, output); - } else if (object instanceof CardWithSpellOption) { + } else if (object instanceof TransformingDoubleFacedCard) { + TransformingDoubleFacedCard mainCard = (TransformingDoubleFacedCard) object; + getPlayableFromObjectSingle(game, fromZone, mainCard.getLeftHalfCard(), mainCard.getLeftHalfCard().getAbilities(game), availableMana, output); + getPlayableFromObjectSingle(game, fromZone, mainCard, mainCard.getSharedAbilities(game), availableMana, output); + } else if (object instanceof CardWithSpellOption) { // adventure must use different card characteristics for different spells (main or adventure) CardWithSpellOption cardWithSpellOption = (CardWithSpellOption) object; getPlayableFromObjectSingle(game, fromZone, cardWithSpellOption.getSpellCard(), cardWithSpellOption.getSpellCard().getAbilities(game), availableMana, output); @@ -4258,6 +4262,12 @@ public abstract class PlayerImpl implements Player, Serializable { boolean isPlaySpell = (ability instanceof SpellAbility); boolean isPlayLand = (ability instanceof PlayLandAbility); + // ignore backside of TDFC + // TODO: maybe better way to ignore + if (isPlaySpell && ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.TRANSFORMED_RIGHT) { + continue; + } + // play land restrictions if (isPlayLand && game.getContinuousEffects().preventedByRuleModification( GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(), @@ -4946,8 +4956,9 @@ public abstract class PlayerImpl implements Player, Serializable { // or "converted," it enters the battlefield with its back face up. If a player is instructed to put a card // that isn't a transforming double-faced card onto the battlefield transformed or converted, that card stays in // its current zone. + // TODO: can probably remove/change after tdfc rework, should only be sending transformed side Boolean enterTransformed = (Boolean) game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId()); - if (enterTransformed != null && enterTransformed && !card.isTransformable()) { + if (enterTransformed != null && enterTransformed && !card.isTransformable() && !(card instanceof TransformingDoubleFacedCardHalf)) { continue; } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 7f1c1d05df9..2dcb5501ee3 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1261,8 +1261,8 @@ public final class CardUtil { permCard = card; } else if (card instanceof CardWithSpellOption) { permCard = card; - } else if (card instanceof ModalDoubleFacedCard) { - permCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); + } else if (card instanceof DoubleFacedCard) { + permCard = ((DoubleFacedCard) card).getLeftHalfCard(); } else { permCard = card; } @@ -1294,8 +1294,8 @@ public final class CardUtil { // it's ok to return one name only cause NamePredicate can find same card by first name if (card instanceof SplitCard) { return ((SplitCard) card).getLeftHalfCard().getName(); - } else if (card instanceof ModalDoubleFacedCard) { - return ((ModalDoubleFacedCard) card).getLeftHalfCard().getName(); + } else if (card instanceof DoubleFacedCard) { + return ((DoubleFacedCard) card).getLeftHalfCard().getName(); } else { return card.getName(); } @@ -1669,6 +1669,22 @@ public final class CardUtil { game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), Boolean.TRUE); } + // handle TDFC + if (card instanceof TransformingDoubleFacedCard) { + TransformingDoubleFacedCardHalf frontFace = ((TransformingDoubleFacedCard) card).getLeftHalfCard(); + TransformingDoubleFacedCardHalf backFace = ((TransformingDoubleFacedCard) card).getRightHalfCard(); + + if (manaCost != null) { + // get additional cost if any + Costs additionalCostsMDFCLeft = frontFace.getSpellAbility().getCosts(); + // set alternative cost and any additional cost + player.setCastSourceIdWithAlternateMana(frontFace.getId(), manaCost, additionalCostsMDFCLeft, MageIdentifier.Default); + } + + // allow just the front face + game.getState().setValue("PlayFromNotOwnHandZone" + frontFace.getId(), Boolean.TRUE); + } + // handle adventure cards if (card instanceof CardWithSpellOption) { Card creatureCard = card.getMainCard(); @@ -1706,9 +1722,9 @@ public final class CardUtil { game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null); game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null); } - if (card instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + if (card instanceof DoubleFacedCard) { + DoubleFacedCardHalf leftHalfCard = ((DoubleFacedCard) card).getLeftHalfCard(); + DoubleFacedCardHalf rightHalfCard = ((DoubleFacedCard) card).getRightHalfCard(); game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null); game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null); } @@ -2100,8 +2116,8 @@ public final class CardUtil { res.add(mainCard); res.add(mainCard.getLeftHalfCard()); res.add(mainCard.getRightHalfCard()); - } else if (object instanceof ModalDoubleFacedCard || object instanceof ModalDoubleFacedCardHalf) { - ModalDoubleFacedCard mainCard = (ModalDoubleFacedCard) ((Card) object).getMainCard(); + } else if (object instanceof DoubleFacedCard || object instanceof DoubleFacedCardHalf) { + DoubleFacedCard mainCard = (DoubleFacedCard) ((Card) object).getMainCard(); res.add(mainCard); res.add(mainCard.getLeftHalfCard()); res.add(mainCard.getRightHalfCard()); diff --git a/Mage/src/main/java/mage/util/ManaUtil.java b/Mage/src/main/java/mage/util/ManaUtil.java index f86f376a1d0..c232e4687fb 100644 --- a/Mage/src/main/java/mage/util/ManaUtil.java +++ b/Mage/src/main/java/mage/util/ManaUtil.java @@ -643,8 +643,8 @@ public final class ManaUtil { secondSide = ((SplitCard) card).getRightHalfCard(); } else if (card instanceof CardWithSpellOption) { secondSide = ((CardWithSpellOption) card).getSpellCard(); - } else if (card instanceof ModalDoubleFacedCard) { - secondSide = ((ModalDoubleFacedCard) card).getRightHalfCard(); + } else if (card instanceof DoubleFacedCard) { + secondSide = ((DoubleFacedCard) card).getRightHalfCard(); } else { secondSide = card.getSecondCardFace(); } diff --git a/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java b/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java index 3b159398037..a53bf0c135f 100644 --- a/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java +++ b/Mage/src/main/java/mage/util/functions/CopyTokenFunction.java @@ -5,7 +5,7 @@ import mage.abilities.Abilities; import mage.abilities.Ability; import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.keyword.PrototypeAbility; -import mage.cards.Card; +import mage.cards.*; import mage.constants.CardType; import mage.constants.SuperType; import mage.game.Game; @@ -91,9 +91,13 @@ public class CopyTokenFunction { copyToToken(target, sourceObj, game); CardUtil.copySetAndCardNumber(target, sourceObj); // second side - if (sourceObj.isTransformable()) { + if (sourceObj.isTransformable() && !(sourceObj instanceof DoubleFacedCardHalf)) { copyToToken(target.getBackFace(), sourceObj.getSecondCardFace(), game); CardUtil.copySetAndCardNumber(target.getBackFace(), sourceObj.getSecondCardFace()); + } else if (sourceObj.isTransformable() && sourceObj instanceof DoubleFacedCardHalf) { + // double faced card + copyToToken(target.getBackFace(), ((DoubleFacedCardHalf) sourceObj).getOtherSide(), game); + CardUtil.copySetAndCardNumber(target.getBackFace(), ((DoubleFacedCardHalf) sourceObj).getOtherSide()); } // apply prototyped status @@ -108,6 +112,35 @@ public class CopyTokenFunction { return; } + // from double faced card spell + if (source instanceof DoubleFacedCardHalf) { + DoubleFacedCardHalf sourceCard = (DoubleFacedCardHalf) source; + Card frontSide; + Card backSide = null; + if (sourceCard.isTransformable()) { + if (sourceCard.isBackSide()) { + target.setEntersTransformed(true); + frontSide = sourceCard.getOtherSide(); + backSide = sourceCard; + } else { + frontSide = sourceCard; + backSide = sourceCard.getOtherSide(); + } + } else { + frontSide = sourceCard; + } + // main side + copyToToken(target, frontSide, game); + target.setCopySourceCard(sourceCard); + CardUtil.copySetAndCardNumber(target, frontSide); + // second side + if (backSide != null) { + copyToToken(target.getBackFace(), backSide, game); + CardUtil.copySetAndCardNumber(target, backSide); + } + return; + } + // from another card (example: Embalm ability) Card sourceObj = CardUtil.getDefaultCardSideForBattlefield(game, source.getMainCard()); target.setCopySourceCard(sourceObj); @@ -121,8 +154,14 @@ public class CopyTokenFunction { // must create back face?? throw new IllegalStateException("Wrong code usage: back face must be non null: " + target.getName() + " - " + target.getClass().getSimpleName()); } - copyToToken(target.getBackFace(), source.getSecondCardFace(), game); - CardUtil.copySetAndCardNumber(target.getBackFace(), source.getSecondCardFace()); + Card secondFace; + if (source instanceof DoubleFacedCard) { + secondFace = ((DoubleFacedCard) source).getRightHalfCard(); + } else { + secondFace = source.getSecondCardFace(); + } + copyToToken(target.getBackFace(), secondFace, game); + CardUtil.copySetAndCardNumber(target.getBackFace(), secondFace); } }