Merge pull request #14061

* move setPT to Card

* Create DoubleFacedCard and DoubleFacedCardHalf to share code between …

* Create Transforming Double Face Card class

* allow putting either permanent side of a double faced card to the bat…

* refactor exile and return transforming card

* update ModalDoubleFacedCard references to DoubleFacedCard where relev…

* update for GUI

* refactor a disturb card

* refactor more disturb cards for test coverage

* refactor a transform card

* refactor more transform cards for test coverage

* fix Archangel Avacyn

* fix cantPlayTDFCBackSide inconsistency

* fix Double Faced Cards having triggers and static abilities when tran…

* fix Double Faced Cards card view erroring when flipping in client

* fix test_Copy_AsSpell_Backside inconsistency

* enable Spider-Man MDFC

* convert TDFC with saga as the front and add card references to Transf…

* refactor More Than Meets the Eye Card

* refactor a battle

* refactor a craft card

* update comment on PeterParkerTest

* Merge branch 'master' into rework-dfc

* fix Saga TDFC Azusa's Many Journeys

* fix double faced cards adding permanent triggers / effects to game

* move permanents entering map into Battlefield

* convert Room cards for new Permanent structure

* fix disturb not exiling

* Merge branch 'master' into rework-dfc

* fix Eddie Brock Power/Toughness

* fix Miles Morales ability on main card

* fix verify conditions for siege and day/night cards

* change room characteristics to text effect to match game rules

* update verify test to skip DoubleFacedCard in missing card test

* accidentally removed transform condition

* Merge branch 'master' into rework-dfc

* fix verify

* CardUtil - remove unnecessary line from castSingle method
This commit is contained in:
Jmlundeen 2025-11-27 09:24:03 -06:00 committed by GitHub
parent 29557f4334
commit 69e20b1061
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
121 changed files with 3020 additions and 2225 deletions

View file

@ -940,8 +940,13 @@ public abstract class CardPanel extends MagePermanent implements ComponentListen
private void setGameCardSides(CardView gameCard) {
if (this.cardSideMain == null) {
// new card
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())) {

View file

@ -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(),

View file

@ -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<String> 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,11 +429,17 @@ 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 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);
@ -531,7 +538,9 @@ public class CardView extends SimpleCardView {
this.extraDeckCard = card.isExtraDeckCard();
// TODO: can probably remove this after tdfc rework
// transformable, double faces cards
if (!(sourceCard.getMainCard() instanceof DoubleFacedCard)) {
this.transformable = card.isTransformable();
Card secondSideCard = card.getSecondCardFace();
@ -539,17 +548,23 @@ public class CardView extends SimpleCardView {
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 = secondSideCard.getName();
}
this.flipCard = card.isFlipCard();
if (card.isFlipCard() && card.getFlipCardName() != null) {
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();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> 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);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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. <i>(Only double-faced cards can be transformed.)</i>
*/
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.
// <i>Delirium</i> &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());
}
}

View file

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

View file

@ -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<String> rules = permanent.getRules(game);
Assert.assertTrue("server must ignore side 2 - untap ability", rules.stream().noneMatch(r -> r.contains("Untap")));

View file

@ -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.)
<strong>Locker Room</strong>
{4}{U}
<strong>Enchantment -- Room</strong>
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 owners 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 owners 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 owners 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 owners 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 owners 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 owners 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 owners 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);
}
}

View file

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

View file

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

View file

@ -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;
@ -31,6 +30,7 @@ public class PeterParkerTest extends CardTestPlayerBase {
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)
);
}
}

View file

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

View file

@ -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 {

View file

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

View file

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

View file

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

View file

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

View file

@ -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 "<i>(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.)</i>";
}
@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);
}
}

View file

@ -12,12 +12,11 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author oscscull
* Special action for Room cards to unlock a locked half by paying its
* mana
* cost.
* mana cost.
* This ability is only present if the corresponding half is currently
* locked.
* @author oscscull
*/
public class RoomUnlockAbility extends SpecialAction {
@ -61,6 +60,10 @@ public class RoomUnlockAbility extends SpecialAction {
sb.append(isLeftHalf ? "left" : "right").append(" half is locked.)</i>");
return sb.toString();
}
public boolean isLeftHalf() {
return isLeftHalf;
}
}
/**

View file

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

View file

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

View file

@ -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<Ability> 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) {

View file

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

View file

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

View file

@ -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<ManaCost> 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<ManaCost> rightHalfManaCost = null;
if (roomCard.getRightHalfCard() != null && roomCard.getRightHalfCard().getSpellAbility() != null) {
rightHalfManaCost = roomCard.getRightHalfCard().getSpellAbility().getManaCosts();
}
if (rightHalfManaCost != null) {
CardUtil.adjustCost(roomCardSpellAbility, rightHalfManaCost, true);
}
}
ManaCosts<ManaCost> roomCardManaCosts = roomCardSpellAbility.getManaCostsToPay();
if (roomCardManaCosts.getText().equals("{0}")) {
roomCardManaCosts = new ManaCostsImpl<>();
}
permanent.setManaCost(roomCardManaCosts);
// Remove abilities from locked halves and add unlock abilities
Abilities<Ability> removedLeftAbilities = new AbilitiesImpl<>();
Abilities<Ability> 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();
}
if (!(roomCardBlueprint instanceof SplitCard)) {
return false;
return roomCardBlueprint;
}
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);
}
}
if (permanent.isRightDoorUnlocked()) {
for (Ability ability : roomCard.getRightHalfCard().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);
}
permanent.setManaCost(newManaCosts);
return true;
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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<UUID> 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

View file

@ -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
// isnt 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

View file

@ -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<UUID> 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<UUID> 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 thats 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<UUID> 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<SuperType> 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<CardType> 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 isnt 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 isnt 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<Ability> getAbilities() {
return getInnerAbilities(true, true);
}
@Override
public Abilities<Ability> getInitAbilities() {
// must init only parent related abilities, spell card must be init separately
return getInnerAbilities(false, false);
}
public Abilities<Ability> getSharedAbilities(Game game) {
// no shared abilities for mdf cards (e.g. must be left or right only)
return new AbilitiesImpl<>();
}
@Override
public Abilities<Ability> 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<Ability> getInnerAbilities(Game game, boolean showLeftSide, boolean showRightSide) {
Abilities<Ability> 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<Ability> getInnerAbilities(boolean showLeftSide, boolean showRightSide) {
Abilities<Ability> 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<String> 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<String> 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<ManaCost> 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 thats 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();
}
}

View file

@ -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<DoubleFacedCard> {
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<UUID> 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

View file

@ -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<UUID> 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<UUID> 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 thats 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<UUID> 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<SuperType> 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<CardType> 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 isnt 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 isnt 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<Ability> getAbilities() {
return getInnerAbilities(true, true);
}
@Override
public Abilities<Ability> getInitAbilities() {
// must init only parent related abilities, spell card must be init separately
return getInnerAbilities(false, false);
}
public Abilities<Ability> getSharedAbilities(Game game) {
// no shared abilities for mdf cards (e.g. must be left or right only)
return new AbilitiesImpl<>();
}
@Override
public Abilities<Ability> 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<Ability> getInnerAbilities(Game game, boolean showLeftSide, boolean showRightSide) {
Abilities<Ability> 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<Ability> getInnerAbilities(boolean showLeftSide, boolean showRightSide) {
Abilities<Ability> 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<String> 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<String> 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<ManaCost> 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 thats 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;
}
}

View file

@ -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<ModalDoubleFacedCard> {
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();
void setPT(int power, int toughness);
void setPT(MageInt power, MageInt toughness);
public ModalDoubleFacedCardHalf copy() {
return new ModalDoubleFacedCardHalf(this);
}
@Override
public ModalDoubleFacedCard getParentCard() {
return (ModalDoubleFacedCard) parentCard;
}
}

View file

@ -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<Ability> 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<Ability> leftAbilities = roomCard.getLeftHalfCard().getAbilities();
for (Ability ability : leftAbilities) {
permanent.addAbility(ability, roomCard.getLeftHalfCard().getId(), game, true);
}
Abilities<Ability> 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 "<i>(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.)</i>";
}
@Override
public RoomAbility copy() {
return new RoomAbility(this);
}
}

Some files were not shown because too many files have changed in this diff Show more