diff --git a/Mage.Sets/src/mage/cards/a/AangsJourney.java b/Mage.Sets/src/mage/cards/a/AangsJourney.java new file mode 100644 index 00000000000..c43bd803446 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AangsJourney.java @@ -0,0 +1,64 @@ +package mage.cards.a; + +import mage.abilities.condition.common.KickedCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.keyword.KickerAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; +import mage.target.common.TargetCardAndOrCardInLibrary; +import mage.target.common.TargetCardInLibrary; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AangsJourney extends CardImpl { + + private static final Predicate predicate = Predicates.and( + SuperType.BASIC.getPredicate(), + CardType.LAND.getPredicate() + ); + + public AangsJourney(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}"); + + this.subtype.add(SubType.LESSON); + + // Kicker {2} + this.addAbility(new KickerAbility("{2}")); + + // Search your library for a basic land card. If this spell was kicked, instead search your library for a basic land card and a Shrine card. Reveal those cards, put them into your hand, then shuffle. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new SearchLibraryPutInHandEffect(new TargetCardAndOrCardInLibrary( + predicate, SubType.SHRINE.getPredicate(), + "a basic land card and/or a Shrine card" + ), true), + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND), true), + KickedCondition.ONCE, "search your library for a basic land card. If this spell was kicked, " + + "instead search your library for a basic land card and a Shrine card. " + + "Reveal those cards, put them into your hand, then shuffle" + )); + + // You gain 2 life. + this.getSpellAbility().addEffect(new GainLifeEffect(2).concatBy("
")); + } + + private AangsJourney(final AangsJourney card) { + super(card); + } + + @Override + public AangsJourney copy() { + return new AangsJourney(this); + } +} diff --git a/Mage.Sets/src/mage/cards/a/AlpineMoon.java b/Mage.Sets/src/mage/cards/a/AlpineMoon.java index da13a2bff44..6c4ab3c4649 100644 --- a/Mage.Sets/src/mage/cards/a/AlpineMoon.java +++ b/Mage.Sets/src/mage/cards/a/AlpineMoon.java @@ -57,6 +57,12 @@ class AlpineMoonEffect extends ContinuousEffectImpl { this.staticText = "lands your opponents control with the chosen name " + "lose all land types and abilities, " + "and they gain \"{T}: Add one mana of any color.\""; + addDependedToType(DependencyType.BecomeMountain); + addDependedToType(DependencyType.BecomeForest); + addDependedToType(DependencyType.BecomeIsland); + addDependedToType(DependencyType.BecomeSwamp); + addDependedToType(DependencyType.BecomePlains); + addDependedToType(DependencyType.BecomeNonbasicLand); } private AlpineMoonEffect(final AlpineMoonEffect effect) { @@ -84,9 +90,6 @@ class AlpineMoonEffect extends ContinuousEffectImpl { for (Permanent land : game.getBattlefield().getActivePermanents(filter2, source.getControllerId(), game)) { switch (layer) { case TypeChangingEffects_4: - // 305.7 Note that this doesn't remove any abilities that were granted to the land by other effects - // So the ability removing has to be done before Layer 6 - land.removeAllAbilities(source.getSourceId(), game); land.removeAllSubTypes(game, SubTypeSet.NonBasicLandType); break; case AbilityAddingRemovingEffects_6: diff --git a/Mage.Sets/src/mage/cards/a/AnHavvaConstable.java b/Mage.Sets/src/mage/cards/a/AnHavvaConstable.java index f077d739041..8de16885a89 100644 --- a/Mage.Sets/src/mage/cards/a/AnHavvaConstable.java +++ b/Mage.Sets/src/mage/cards/a/AnHavvaConstable.java @@ -1,7 +1,6 @@ package mage.cards.a; -import java.util.UUID; import mage.MageInt; import mage.MageObject; import mage.ObjectColor; @@ -16,6 +15,8 @@ import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; import mage.players.Player; +import java.util.UUID; + /** * * @author fireshoes @@ -29,7 +30,7 @@ public final class AnHavvaConstable extends CardImpl { this.toughness = new MageInt(1); // An-Havva Constable's toughness is equal to 1 plus the number of green creatures on the battlefield. - this.addAbility(new SimpleStaticAbility(new AnHavvaConstableEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new AnHavvaConstableEffect())); } private AnHavvaConstable(final AnHavvaConstable card) { @@ -72,7 +73,7 @@ class AnHavvaConstableEffect extends ContinuousEffectImpl { FilterCreaturePermanent filter = new FilterCreaturePermanent("green creatures"); filter.add(new ColorPredicate(ObjectColor.GREEN)); - int numberOfGreenCreatures = game.getBattlefield().count(filter, source.getSourceId(), source, game); + int numberOfGreenCreatures = game.getBattlefield().count(filter, source.getControllerId(), source, game); mageObject.getToughness().setModifiedBaseValue(1 + numberOfGreenCreatures); diff --git a/Mage.Sets/src/mage/cards/b/BottleCapBlast.java b/Mage.Sets/src/mage/cards/b/BottleCapBlast.java index 6d9d536f495..1b19927a649 100644 --- a/Mage.Sets/src/mage/cards/b/BottleCapBlast.java +++ b/Mage.Sets/src/mage/cards/b/BottleCapBlast.java @@ -62,8 +62,8 @@ class BottleCapBlastEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - UUID target = getTargetPointer().getFirst(game, source); - Player player = game.getPlayer(target); + UUID targetId = getTargetPointer().getFirst(game, source); + Player player = game.getPlayer(targetId); if (player != null) { player.damage(5, source, game); return true; @@ -72,11 +72,10 @@ class BottleCapBlastEffect extends OneShotEffect { if (permanent == null) { return false; } - int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), 5); - permanent.damage(5, source.getSourceId(), source, game); - if (lethal < 5) { + int excess = permanent.damageWithExcess(5, source, game); + if (excess > 0) { new TreasureToken().putOntoBattlefield( - 5 - lethal, game, source, source.getControllerId(), true, false + excess, game, source, source.getControllerId(), true, false ); } return true; diff --git a/Mage.Sets/src/mage/cards/c/ContestOfClaws.java b/Mage.Sets/src/mage/cards/c/ContestOfClaws.java index 530b4a9847e..afa5f2dc6d4 100644 --- a/Mage.Sets/src/mage/cards/c/ContestOfClaws.java +++ b/Mage.Sets/src/mage/cards/c/ContestOfClaws.java @@ -1,7 +1,5 @@ package mage.cards.c; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.keyword.DiscoverEffect; @@ -9,34 +7,29 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.StaticFilters; +import mage.game.Controllable; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.Target; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; + +import java.util.Optional; +import java.util.UUID; import static mage.filter.StaticFilters.FILTER_ANOTHER_CREATURE_TARGET_2; /** - * * @author jimga150 */ public final class ContestOfClaws extends CardImpl { public ContestOfClaws(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); - // Target creature you control deals damage equal to its power to another target creature. If excess damage was dealt this way, discover X, where X is that excess damage. this.getSpellAbility().addEffect(new ContestOfClawsDamageEffect()); - Target target = new TargetControlledCreaturePermanent().setTargetTag(1); - this.getSpellAbility().addTarget(target); - - Target target2 = new TargetPermanent(FILTER_ANOTHER_CREATURE_TARGET_2).setTargetTag(2); - this.getSpellAbility().addTarget(target2); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent().setTargetTag(1)); + this.getSpellAbility().addTarget(new TargetPermanent(FILTER_ANOTHER_CREATURE_TARGET_2).setTargetTag(2)); } private ContestOfClaws(final ContestOfClaws card) { @@ -74,22 +67,13 @@ class ContestOfClawsDamageEffect extends OneShotEffect { if (ownCreature == null || targetCreature == null) { return false; } - int damage = ownCreature.getPower().getValue(); - int lethalDamage = targetCreature.getLethalDamage(source.getSourceId(), game); - targetCreature.damage(damage, ownCreature.getId(), source, game, false, true); - - if (damage < lethalDamage){ - return true; + int excess = targetCreature.damageWithExcess( ownCreature.getPower().getValue(), ownCreature.getId(), source, game); + if (excess > 0) { + Optional.ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> DiscoverEffect.doDiscover(player, excess, game, source)); } - int discoverValue = damage - lethalDamage; - Player player = game.getPlayer(source.getControllerId()); - - if (player == null){ - // If somehow this case is hit, the damage still technically happened, so i guess it applied? - return true; - } - DiscoverEffect.doDiscover(player, discoverValue, game, source); - return true; } } diff --git a/Mage.Sets/src/mage/cards/d/DarkImpostor.java b/Mage.Sets/src/mage/cards/d/DarkImpostor.java index 34ca02b5c1d..cc24ed51e4e 100644 --- a/Mage.Sets/src/mage/cards/d/DarkImpostor.java +++ b/Mage.Sets/src/mage/cards/d/DarkImpostor.java @@ -102,7 +102,7 @@ class DarkImpostorContinuousEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { Permanent permanent = source.getSourcePermanentIfItStillExists(game); - ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source, 1)); if (permanent == null || exileZone == null || exileZone.isEmpty()) { return false; } diff --git a/Mage.Sets/src/mage/cards/d/DeathriteShaman.java b/Mage.Sets/src/mage/cards/d/DeathriteShaman.java index efa2186a081..88b91646ca8 100644 --- a/Mage.Sets/src/mage/cards/d/DeathriteShaman.java +++ b/Mage.Sets/src/mage/cards/d/DeathriteShaman.java @@ -1,6 +1,5 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -16,14 +15,14 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterLandCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInGraveyard; +import java.util.UUID; + /** * * @author LevelX2 diff --git a/Mage.Sets/src/mage/cards/e/EvraHalcyonWitness.java b/Mage.Sets/src/mage/cards/e/EvraHalcyonWitness.java index 09ef0580bc4..bce8344342e 100644 --- a/Mage.Sets/src/mage/cards/e/EvraHalcyonWitness.java +++ b/Mage.Sets/src/mage/cards/e/EvraHalcyonWitness.java @@ -64,7 +64,7 @@ class EvraHalcyonWitnessEffect extends OneShotEffect { Player player = game.getPlayer(source.getControllerId()); if (player != null && player.isLifeTotalCanChange()) { Permanent perm = game.getPermanent(source.getSourceId()); - if (perm != null) { + if (perm != null && perm.isCreature(game)) { int amount = perm.getPower().getValue(); int life = player.getLife(); if (life == amount) { diff --git a/Mage.Sets/src/mage/cards/f/FireLordSozin.java b/Mage.Sets/src/mage/cards/f/FireLordSozin.java new file mode 100644 index 00000000000..5074b2310fa --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FireLordSozin.java @@ -0,0 +1,158 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCosts; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.FirebendingAbility; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.card.OwnerIdPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FireLordSozin extends CardImpl { + + public FireLordSozin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + this.color.setBlack(true); + this.nightCard = true; + + // Menace + this.addAbility(new MenaceAbility()); + + // Firebending 3 + this.addAbility(new FirebendingAbility(3)); + + // Whenever Fire Lord Sozin deals combat damage to a player, you may pay {X}. When you do, put any number of target creature cards with total mana value X or less from that player's graveyard onto the battlefield under your control. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new FireLordSozinEffect())); + } + + private FireLordSozin(final FireLordSozin card) { + super(card); + } + + @Override + public FireLordSozin copy() { + return new FireLordSozin(this); + } +} + +class FireLordSozinEffect extends OneShotEffect { + + FireLordSozinEffect() { + super(Outcome.Benefit); + staticText = "you may pay {X}. When you do, put any number of target creature cards with " + + "total mana value X or less from that player's graveyard onto the battlefield under your control"; + } + + private FireLordSozinEffect(final FireLordSozinEffect effect) { + super(effect); + } + + @Override + public FireLordSozinEffect copy() { + return new FireLordSozinEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + if (controller == null || !controller.chooseUse(Outcome.BoostCreature, "Pay {X}?", source, game)) { + return false; + } + int xValue = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source, true); + ManaCosts cost = new ManaCostsImpl<>("{X}"); + cost.add(new GenericManaCost(xValue)); + if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { + return false; + } + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), false); + ability.addTarget(new FireLordSozinTarget((UUID) getValue("damagedPlayer"), xValue)); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} + +class FireLordSozinTarget extends TargetCardInGraveyard { + + private final int xValue; + + private static final FilterCard makeFilter(UUID ownerId, int xValue) { + FilterCard filter = new FilterCreatureCard("creature cards with total mana value " + xValue + " or less from that player's graveyard"); + filter.add(new OwnerIdPredicate(ownerId)); + return filter; + } + + FireLordSozinTarget(UUID ownerId, int xValue) { + super(0, Integer.MAX_VALUE, makeFilter(ownerId, xValue), false); + this.xValue = xValue; + } + + private FireLordSozinTarget(final FireLordSozinTarget target) { + super(target); + this.xValue = target.xValue; + } + + @Override + public FireLordSozinTarget copy() { + return new FireLordSozinTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + return super.canTarget(playerId, id, source, game) + && CardUtil.checkCanTargetTotalValueLimit(this.getTargets(), id, MageObject::getManaValue, xValue, game); + } + + @Override + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + return CardUtil.checkPossibleTargetsTotalValueLimit( + this.getTargets(), + super.possibleTargets(sourceControllerId, source, game), + MageObject::getManaValue, xValue, game + ); + } + + @Override + public String getMessage(Game game) { + // shows selected total + int selectedValue = this.getTargets().stream() + .map(game::getObject) + .filter(Objects::nonNull) + .mapToInt(MageObject::getManaValue) + .sum(); + return super.getMessage(game) + " (selected total mana value " + selectedValue + ")"; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GoblinNegotiation.java b/Mage.Sets/src/mage/cards/g/GoblinNegotiation.java index 368eb608eab..538d9976e60 100644 --- a/Mage.Sets/src/mage/cards/g/GoblinNegotiation.java +++ b/Mage.Sets/src/mage/cards/g/GoblinNegotiation.java @@ -15,7 +15,6 @@ import mage.util.CardUtil; import java.util.UUID; /** - * * @author ciaccona007 */ public final class GoblinNegotiation extends CardImpl { @@ -62,14 +61,12 @@ class GoblinNegotiationEffect extends OneShotEffect { if (permanent == null) { return false; } - int damage = CardUtil.getSourceCostsTag(game, source, "X", 0); - int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), damage); - permanent.damage(damage, source.getSourceId(), source, game); - if (damage > lethal) { - new GoblinToken().putOntoBattlefield( - damage - lethal, game, source, source.getControllerId() - ); + int excess = permanent.damageWithExcess( + CardUtil.getSourceCostsTag(game, source, "X", 0), source, game + ); + if (excess > 0) { + new GoblinToken().putOntoBattlefield(excess, game, source); } return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/h/HakodaSelflessCommander.java b/Mage.Sets/src/mage/cards/h/HakodaSelflessCommander.java new file mode 100644 index 00000000000..09523f531aa --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HakodaSelflessCommander.java @@ -0,0 +1,72 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HakodaSelflessCommander extends CardImpl { + + private static final FilterCard filter = new FilterCard(SubType.ALLY, "Ally spells"); + + public HakodaSelflessCommander(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // You may cast Ally spells from the top of your library. + this.addAbility(new SimpleStaticAbility(new PlayFromTopOfLibraryEffect(filter))); + + // Sacrifice Hakoda: Creatures you control get +0/+5 and gain indestructible until end of turn. + Ability ability = new SimpleActivatedAbility( + new BoostControlledEffect(0, 5, Duration.EndOfTurn) + .setText("creatures you control get +0/+5"), + new SacrificeSourceCost() + ); + ability.addEffect(new GainAbilityAllEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURE + ).setText("and gain indestructible until end of turn")); + this.addAbility(ability); + } + + private HakodaSelflessCommander(final HakodaSelflessCommander card) { + super(card); + } + + @Override + public HakodaSelflessCommander copy() { + return new HakodaSelflessCommander(this); + } +} diff --git a/Mage.Sets/src/mage/cards/h/HaruHiddenTalent.java b/Mage.Sets/src/mage/cards/h/HaruHiddenTalent.java index a7a1f7f82a5..bad79858e09 100644 --- a/Mage.Sets/src/mage/cards/h/HaruHiddenTalent.java +++ b/Mage.Sets/src/mage/cards/h/HaruHiddenTalent.java @@ -9,6 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; @@ -29,6 +30,8 @@ public final class HaruHiddenTalent extends CardImpl { public HaruHiddenTalent(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.PEASANT); this.subtype.add(SubType.ALLY); diff --git a/Mage.Sets/src/mage/cards/h/HellToPay.java b/Mage.Sets/src/mage/cards/h/HellToPay.java index 640b2be38b8..8b3e35e9589 100644 --- a/Mage.Sets/src/mage/cards/h/HellToPay.java +++ b/Mage.Sets/src/mage/cards/h/HellToPay.java @@ -60,13 +60,11 @@ class HellToPayEffect extends OneShotEffect { if (permanent == null) { return false; } - int damage = CardUtil.getSourceCostsTag(game, source, "X", 0); - int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), damage); - permanent.damage(damage, source.getSourceId(), source, game); - if (damage > lethal) { - new TreasureToken().putOntoBattlefield( - damage - lethal, game, source, source.getControllerId(), true, false - ); + int excess = permanent.damageWithExcess( + CardUtil.getSourceCostsTag(game, source, "X", 0), source, game + ); + if (excess > 0) { + new TreasureToken().putOntoBattlefield(excess, game, source, source.getControllerId(), true, false); } return true; } diff --git a/Mage.Sets/src/mage/cards/h/HowToStartARiot.java b/Mage.Sets/src/mage/cards/h/HowToStartARiot.java new file mode 100644 index 00000000000..7c93cc5ea8c --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HowToStartARiot.java @@ -0,0 +1,84 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HowToStartARiot extends CardImpl { + + public HowToStartARiot(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + this.subtype.add(SubType.LESSON); + + // Target creature gains menace until end of turn. + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(new MenaceAbility(false))); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // Creatures target player controls get +2/+0 until end of turn. + this.getSpellAbility().addEffect(new HowToStartARiotEffect()); + this.getSpellAbility().addTarget(new TargetPlayer()); + } + + private HowToStartARiot(final HowToStartARiot card) { + super(card); + } + + @Override + public HowToStartARiot copy() { + return new HowToStartARiot(this); + } +} + +class HowToStartARiotEffect extends OneShotEffect { + + HowToStartARiotEffect() { + super(Outcome.Benefit); + this.setTargetPointer(new SecondTargetPointer()); + staticText = "creatures target player controls get +2/+0 until end of turn"; + this.concatBy("
"); + } + + private HowToStartARiotEffect(final HowToStartARiotEffect effect) { + super(effect); + } + + @Override + public HowToStartARiotEffect copy() { + return new HowToStartARiotEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player == null) { + return false; + } + FilterCreaturePermanent filter = new FilterCreaturePermanent(); + filter.add(new ControllerIdPredicate(player.getId())); + game.addEffect(new BoostAllEffect( + 2, 0, Duration.EndOfTurn, filter, false + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/l/LacerateFlesh.java b/Mage.Sets/src/mage/cards/l/LacerateFlesh.java index 0d4f8c4eea3..9e060e65cd7 100644 --- a/Mage.Sets/src/mage/cards/l/LacerateFlesh.java +++ b/Mage.Sets/src/mage/cards/l/LacerateFlesh.java @@ -55,14 +55,13 @@ class LacerateFleshEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { return false; } - int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), 4); - permanent.damage(4, source.getSourceId(), source, game); - if (lethal < 4) { - new BloodToken().putOntoBattlefield(4 - lethal, game, source); + int excess = permanent.damageWithExcess(4, source, game); + if (excess > 0) { + new BloodToken().putOntoBattlefield(excess, game, source); } return true; } diff --git a/Mage.Sets/src/mage/cards/l/LongFengGrandSecretariat.java b/Mage.Sets/src/mage/cards/l/LongFengGrandSecretariat.java new file mode 100644 index 00000000000..b3d6d812afb --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LongFengGrandSecretariat.java @@ -0,0 +1,72 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.PutIntoGraveFromBattlefieldAllTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LongFengGrandSecretariat extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("another creature you control or a land you control"); + + static { + filter.add(LongFengGrandSecretariatPredicate.instance); + } + + public LongFengGrandSecretariat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B/G}{B/G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever another creature you control or a land you control is put into a graveyard from the battlefield, put a +1/+1 counter on target creature you control. + Ability ability = new PutIntoGraveFromBattlefieldAllTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + false, filter, false + ); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private LongFengGrandSecretariat(final LongFengGrandSecretariat card) { + super(card); + } + + @Override + public LongFengGrandSecretariat copy() { + return new LongFengGrandSecretariat(this); + } +} + +enum LongFengGrandSecretariatPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input.getObject().isLand(game) + || input.getObject().isCreature(game) + && AnotherPredicate.instance.apply(input, game); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MegatronDestructiveForce.java b/Mage.Sets/src/mage/cards/m/MegatronDestructiveForce.java index 2badfb10257..7b718d6efbd 100644 --- a/Mage.Sets/src/mage/cards/m/MegatronDestructiveForce.java +++ b/Mage.Sets/src/mage/cards/m/MegatronDestructiveForce.java @@ -79,7 +79,7 @@ class MegatronDestructiveForceEffect extends OneShotEffect { return false; } TargetSacrifice target = new TargetSacrifice( - 0, 1, StaticFilters.FILTER_CONTROLLED_ANOTHER_ARTIFACT + 0, 1, StaticFilters.FILTER_CONTROLLED_ANOTHER_ARTIFACT ); player.choose(outcome, target, source, game); Permanent permanent = game.getPermanent(target.getFirstTarget()); @@ -93,7 +93,7 @@ class MegatronDestructiveForceEffect extends OneShotEffect { } ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( - new MegatronDestructiveForceReflexiveEffect(manaValue), false + new MegatronDestructiveForceReflexiveEffect(manaValue), false ); ability.addHint(new StaticHint("Sacrificed artifact mana value: " + manaValue)); ability.addTarget(new TargetCreaturePermanent()); @@ -109,8 +109,8 @@ class MegatronDestructiveForceReflexiveEffect extends OneShotEffect { MegatronDestructiveForceReflexiveEffect(int value) { super(Outcome.Damage); staticText = "{this} deals damage equal to the sacrificed artifact's mana value to target " + - "creature. If excess damage would be dealt to that creature this way, instead that damage " + - "is dealt to that creature's controller and you convert {this}."; + "creature. If excess damage would be dealt to that creature this way, instead that damage " + + "is dealt to that creature's controller and you convert {this}."; this.value = value; } @@ -126,36 +126,22 @@ class MegatronDestructiveForceReflexiveEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent sourcePermanent = source.getSourcePermanentOrLKI(game); - if (sourcePermanent == null) { - return false; - } - if (value < 1) { return false; } - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); if (permanent == null) { return false; } - - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - int excess = value - lethal; - if (excess <= 0) { - // no excess damage. - permanent.damage(value, source.getSourceId(), source, game); + int excess = permanent.damageWithExcess(value, source, game); + if (excess < 1) { return true; } - - // excess damage. dealing excess to controller's instead. And convert Megatron. - permanent.damage(lethal, source.getSourceId(), source, game); Player player = game.getPlayer(permanent.getControllerId()); if (player != null) { player.damage(excess, source, game); } new TransformSourceEffect().apply(game, source); - return true; } } diff --git a/Mage.Sets/src/mage/cards/m/MomoFriendlyFlier.java b/Mage.Sets/src/mage/cards/m/MomoFriendlyFlier.java new file mode 100644 index 00000000000..8ee49906584 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MomoFriendlyFlier.java @@ -0,0 +1,132 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalCostModificationEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MomoFriendlyFlier extends CardImpl { + + private static final FilterCard filter = new FilterCard(); + private static final FilterPermanent filter2 = new FilterControlledCreaturePermanent("another creature you control with flying"); + + static { + filter.add(Predicates.not(SubType.LEMUR.getPredicate())); + filter.add(new AbilityPredicate(FlyingAbility.class)); + filter2.add(new AbilityPredicate(FlyingAbility.class)); + } + + public MomoFriendlyFlier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.LEMUR); + this.subtype.add(SubType.BAT); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // The first non-Lemur creature spell with flying you cast during each of your turns costs {1} less to cast. + this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( + new SpellsCostReductionControllerEffect(filter, 1), MomoFriendlyFlierCondition.instance, + "the first non-Lemur creature spell with flying you cast during each of your turns costs {1} less to cast" + )).addHint(MomoFriendlyFlierCondition.getHint()), new MomoFriendlyFlierWatcher()); + + // Whenever another creature you control with flying enters, Momo gets +1/+1 until end of turn. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new BoostSourceEffect(1, 1, Duration.EndOfTurn), filter2 + )); + } + + private MomoFriendlyFlier(final MomoFriendlyFlier card) { + super(card); + } + + @Override + public MomoFriendlyFlier copy() { + return new MomoFriendlyFlier(this); + } +} + +enum MomoFriendlyFlierCondition implements Condition { + instance; + private static final Hint hint = new ConditionHint( + instance, "You haven't cast a non-Lemur creature spell with flying during your turn yet" + ); + + public static Hint getHint() { + return hint; + } + + @Override + public boolean apply(Game game, Ability source) { + return game.isActivePlayer(source.getControllerId()) + && !MomoFriendlyFlierWatcher.checkPlayer(game, source); + } +} + +class MomoFriendlyFlierWatcher extends Watcher { + + private final Set set = new HashSet<>(); + + MomoFriendlyFlierWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST) { + return; + } + Spell spell = game.getSpell(event.getTargetId()); + if (spell != null + && spell.isCreature(game) + && !spell.hasSubtype(SubType.LEMUR, game) + && spell.getAbilities(game).containsClass(FlyingAbility.class)) { + set.add(spell.getControllerId()); + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkPlayer(Game game, Ability source) { + return game + .getState() + .getWatcher(MomoFriendlyFlierWatcher.class) + .set + .contains(source.getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/n/NahirisWarcrafting.java b/Mage.Sets/src/mage/cards/n/NahirisWarcrafting.java index cd5f2e4ceab..c170893bc2d 100644 --- a/Mage.Sets/src/mage/cards/n/NahirisWarcrafting.java +++ b/Mage.Sets/src/mage/cards/n/NahirisWarcrafting.java @@ -80,9 +80,8 @@ class NahirisWarcraftingEffect extends OneShotEffect { if (player == null || permanent == null) { return false; } - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - int excess = permanent.damage(5, source, game) - lethal; - if (excess <= 0) { + int excess = permanent.damageWithExcess(5, source, game); + if (excess < 1) { return true; } Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, excess)); diff --git a/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java b/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java index e5238585a3d..a7436dcb122 100644 --- a/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java +++ b/Mage.Sets/src/mage/cards/o/OrbitalPlunge.java @@ -2,7 +2,6 @@ package mage.cards.o; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -61,10 +60,8 @@ class OrbitalPlungeEffect extends OneShotEffect { if (permanent == null) { return false; } - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - permanent.damage(6, source.getSourceId(), source, game); - if (lethal < 6) { - new CreateTokenEffect(new LanderToken()).apply(game, source); + if (permanent.damageWithExcess(6, source, game) > 0) { + new LanderToken().putOntoBattlefield(1, game, source); } return true; } diff --git a/Mage.Sets/src/mage/cards/r/RamThrough.java b/Mage.Sets/src/mage/cards/r/RamThrough.java index 582f0ccb988..18bac9209d1 100644 --- a/Mage.Sets/src/mage/cards/r/RamThrough.java +++ b/Mage.Sets/src/mage/cards/r/RamThrough.java @@ -7,15 +7,18 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.StaticFilters; +import mage.game.Controllable; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.EachTargetPointer; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import static mage.filter.StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL; @@ -47,6 +50,7 @@ class RamThroughEffect extends OneShotEffect { RamThroughEffect() { super(Outcome.Benefit); + this.setTargetPointer(new EachTargetPointer()); staticText = "Target creature you control deals damage equal to its power to target creature you don't control. " + "If the creature you control has trample, excess damage is dealt to that creature's controller instead."; } @@ -62,29 +66,31 @@ class RamThroughEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - if (source.getTargets().size() != 2) { - throw new IllegalStateException("It must have two targets, but found " + source.getTargets().size()); - } - - Permanent myPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - Permanent anotherPermanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - - if (myPermanent == null || anotherPermanent == null) { + List permanents = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (permanents.size() < 2) { return false; } - int power = myPermanent.getPower().getValue(); + Permanent permanent = permanents.get(0); + int power = permanent.getPower().getValue(); if (power < 1) { return false; } - if (!myPermanent.getAbilities().containsKey(TrampleAbility.getInstance().getId())) { - return anotherPermanent.damage(power, myPermanent.getId(), source, game, false, true) > 0; + Permanent creature = permanents.get(1); + if (!permanent.hasAbility(TrampleAbility.getInstance(), game)) { + return creature.damage(power, permanent.getId(), source, game) > 0; } - int lethal = anotherPermanent.getLethalDamage(myPermanent.getId(), game); - lethal = Math.min(lethal, power); - anotherPermanent.damage(lethal, myPermanent.getId(), source, game); - Player player = game.getPlayer(anotherPermanent.getControllerId()); - if (player != null && lethal < power) { - player.damage(power - lethal, myPermanent.getId(), source, game); + int excess = creature.damageWithExcess(power, permanent.getId(), source, game); + if (excess > 0) { + Optional.ofNullable(creature) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.damage(excess, permanent.getId(), source, game)); } return true; } diff --git a/Mage.Sets/src/mage/cards/r/RazorRings.java b/Mage.Sets/src/mage/cards/r/RazorRings.java new file mode 100644 index 00000000000..de184c5311d --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RazorRings.java @@ -0,0 +1,72 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetAttackingOrBlockingCreature; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RazorRings extends CardImpl { + + public RazorRings(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{W}"); + + // Razor Rings deals 4 damage to target attacking or blocking creature. You gain life equal to the excess damage dealt this way. + this.getSpellAbility().addEffect(new RazorRingsEffect()); + this.getSpellAbility().addTarget(new TargetAttackingOrBlockingCreature()); + } + + private RazorRings(final RazorRings card) { + super(card); + } + + @Override + public RazorRings copy() { + return new RazorRings(this); + } +} + +class RazorRingsEffect extends OneShotEffect { + + RazorRingsEffect() { + super(Outcome.Benefit); + staticText = "{this} deals 4 damage to target attacking or blocking creature. " + + "You gain life equal to the excess damage dealt this way"; + } + + private RazorRingsEffect(final RazorRingsEffect effect) { + super(effect); + } + + @Override + public RazorRingsEffect copy() { + return new RazorRingsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + int excess = permanent.damageWithExcess(4, source, game); + if (excess > 0) { + Optional.ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.gainLife(excess, game, source)); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/r/RexCyberHound.java b/Mage.Sets/src/mage/cards/r/RexCyberHound.java index 3b04fb97384..90f7f61121b 100644 --- a/Mage.Sets/src/mage/cards/r/RexCyberHound.java +++ b/Mage.Sets/src/mage/cards/r/RexCyberHound.java @@ -129,7 +129,6 @@ class RexCyberhoundContinuousEffect extends ContinuousEffectImpl { for (Ability ability : card.getAbilities(game)) { if (ability.isActivatedAbility()) { ActivatedAbility copyAbility = (ActivatedAbility) ability.copy(); - copyAbility.setMaxActivationsPerTurn(1); perm.addAbility(copyAbility, source.getSourceId(), game, true); } } diff --git a/Mage.Sets/src/mage/cards/s/SerpentOfThePass.java b/Mage.Sets/src/mage/cards/s/SerpentOfThePass.java new file mode 100644 index 00000000000..a868ebce450 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SerpentOfThePass.java @@ -0,0 +1,71 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInControllerGraveyardCondition; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashSourceEffect; +import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SerpentOfThePass extends CardImpl { + + private static final Condition condition = new CardsInControllerGraveyardCondition(3, new FilterCard(SubType.LESSON)); + private static final Hint hint = new ValueHint( + "Lesson cards in your graveyard", new CardsInControllerGraveyardCount(new FilterCard(SubType.LESSON)) + ); + private static final FilterCard filter = new FilterCard("noncreature, nonland card"); + + static { + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + filter.add(Predicates.not(CardType.LAND.getPredicate())); + } + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(filter); + private static final Hint hint2 = new ValueHint("Noncreature, nonland cards in your graveyard", xValue); + + public SerpentOfThePass(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}"); + + this.subtype.add(SubType.SERPENT); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // If there are three or more Lesson cards in your graveyard, you may cast this spell as though it had flash. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new ConditionalAsThoughEffect( + new CastAsThoughItHadFlashSourceEffect(Duration.EndOfGame), condition + ).setText("if there are three or more Lesson cards in your graveyard, " + + "you may cast this spell as though it had flash.")).addHint(hint)); + + // This spell costs {1} less to cast for each noncreature, nonland card in your graveyard. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionForEachSourceEffect(1, xValue) + ).setRuleAtTheTop(true).addHint(hint2)); + } + + private SerpentOfThePass(final SerpentOfThePass card) { + super(card); + } + + @Override + public SerpentOfThePass copy() { + return new SerpentOfThePass(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SokkaLateralStrategist.java b/Mage.Sets/src/mage/cards/s/SokkaLateralStrategist.java new file mode 100644 index 00000000000..82338117d3b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SokkaLateralStrategist.java @@ -0,0 +1,75 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SokkaLateralStrategist extends CardImpl { + + public SokkaLateralStrategist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W/U}{W/U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever Sokka and at least one other creature attack, draw a card. + this.addAbility(new SokkaLateralStrategistTriggeredAbility()); + } + + private SokkaLateralStrategist(final SokkaLateralStrategist card) { + super(card); + } + + @Override + public SokkaLateralStrategist copy() { + return new SokkaLateralStrategist(this); + } +} + +class SokkaLateralStrategistTriggeredAbility extends TriggeredAbilityImpl { + + SokkaLateralStrategistTriggeredAbility() { + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); + this.setTriggerPhrase("Whenever {this} and at least one other creature attack, "); + } + + private SokkaLateralStrategistTriggeredAbility(final SokkaLateralStrategistTriggeredAbility ability) { + super(ability); + } + + @Override + public SokkaLateralStrategistTriggeredAbility copy() { + return new SokkaLateralStrategistTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return game.getCombat().getAttackers().size() >= 2 && game.getCombat().getAttackers().contains(getSourceId()); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SquealingDevil.java b/Mage.Sets/src/mage/cards/s/SquealingDevil.java index 556bc576a33..25957faee0a 100644 --- a/Mage.Sets/src/mage/cards/s/SquealingDevil.java +++ b/Mage.Sets/src/mage/cards/s/SquealingDevil.java @@ -1,33 +1,31 @@ - package mage.cards.s; import mage.MageInt; -import mage.abilities.keyword.FearAbility; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.ManaWasSpentCondition; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.SacrificeSourceUnlessConditionEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.FearAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; -import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.constants.Duration; import mage.constants.Outcome; +import mage.constants.SubType; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class SquealingDevil extends CardImpl { @@ -76,24 +74,22 @@ class SquealingDevilEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - ManaCosts cost = new ManaCostsImpl<>("{X}"); - if (player != null) { - if (player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { - int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to boost)", game, source, true); - cost.add(new GenericManaCost(costX)); - if (cost.pay(source, game, source, source.getControllerId(), false, null)) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null && permanent.isCreature(game)) { - ContinuousEffect effect = new BoostTargetEffect(costX, 0, Duration.EndOfTurn); - effect.setTargetPointer(new FixedTarget(permanent, game)); - game.addEffect(effect, source); - return true; - } - return false; - } - } + if (player == null || !player.chooseUse(Outcome.BoostCreature, "Pay {X}?", source, game)) { + return false; } - return false; + int xValue = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to boost)", game, source, true); + ManaCosts cost = new ManaCostsImpl<>("{X}"); + cost.add(new GenericManaCost(xValue)); + if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { + return false; + } + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + game.addEffect(new BoostTargetEffect(xValue, 0, Duration.EndOfTurn) + .setTargetPointer(new FixedTarget(permanent, game)), source); + return true; } @Override diff --git a/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java b/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java index ae0fbeacb18..5497da547fb 100644 --- a/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java +++ b/Mage.Sets/src/mage/cards/s/StalwartSuccessor.java @@ -78,14 +78,19 @@ class StalwartSuccessorTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + int zccOffset = 0; Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent == null) { + permanent = game.getPermanentEntering(event.getTargetId()); + zccOffset = 1; + } if (permanent == null || !permanent.isCreature(game) || !permanent.isControlledBy(getControllerId()) || !StalwartSuccessorWatcher.checkCreature(permanent, event, game)) { return false; } - this.getEffects().setTargetPointer(new FixedTarget(permanent, game)); + this.getEffects().setTargetPointer(new FixedTarget(permanent.getId(), permanent.getZoneChangeCounter(game) + zccOffset)); return true; } } @@ -101,6 +106,11 @@ class StalwartSuccessorWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.COUNTERS_ADDED) { + if (game.getPermanent(event.getTargetId()) == null) { + // permanent entering + Permanent permanent = game.getPermanentEntering(event.getTargetId()); + map.putIfAbsent(new MageObjectReference(event.getTargetId(), permanent.getZoneChangeCounter(game) + 1, game), event.getId()); + } map.putIfAbsent(new MageObjectReference(event.getTargetId(), game), event.getId()); } } diff --git a/Mage.Sets/src/mage/cards/t/TheRiseOfSozin.java b/Mage.Sets/src/mage/cards/t/TheRiseOfSozin.java new file mode 100644 index 00000000000..78a8b2bd579 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheRiseOfSozin.java @@ -0,0 +1,85 @@ +package mage.cards.t; + +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.Effects; +import mage.abilities.effects.common.ChooseACardNameEffect; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.ExileSagaAndReturnTransformedEffect; +import mage.abilities.effects.common.search.SearchTargetGraveyardHandLibraryForCardNameAndExileEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheRiseOfSozin extends CardImpl { + + public TheRiseOfSozin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{B}{B}"); + + this.subtype.add(SubType.SAGA); + this.secondSideCardClazz = mage.cards.f.FireLordSozin.class; + + // (As this Saga enters and after your draw step, add a lore counter.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Destroy all creatures. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES) + ); + + // II -- Choose a card name. Search target opponent's graveyard, hand, and library for up to four cards with that name and exile them. Then that player shuffles. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, + new Effects( + new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.ALL), new TheRiseOfSozinEffect() + ), new TargetOpponent() + ); + + // 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()); + this.addAbility(sagaAbility); + } + + private TheRiseOfSozin(final TheRiseOfSozin card) { + super(card); + } + + @Override + public TheRiseOfSozin copy() { + return new TheRiseOfSozin(this); + } +} + +class TheRiseOfSozinEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExileEffect { + + TheRiseOfSozinEffect() { + super(true, "target opponent's", "up to four cards with that name", false, 4); + } + + private TheRiseOfSozinEffect(final TheRiseOfSozinEffect effect) { + super(effect); + } + + @Override + public TheRiseOfSozinEffect copy() { + return new TheRiseOfSozinEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + String chosenCardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); + return applySearchAndExile(game, source, chosenCardName, getTargetPointer().getFirst(game, source)); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TorchTheWitness.java b/Mage.Sets/src/mage/cards/t/TorchTheWitness.java index 283c3409be3..da1d98ac6e1 100644 --- a/Mage.Sets/src/mage/cards/t/TorchTheWitness.java +++ b/Mage.Sets/src/mage/cards/t/TorchTheWitness.java @@ -60,8 +60,7 @@ class TorchTheWitnessEffect extends OneShotEffect { if (permanent == null) { return false; } - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - if (lethal < permanent.damage(2 * CardUtil.getSourceCostsTag(game, source, "X", 0), source, game)) { + if (permanent.damageWithExcess(2 * CardUtil.getSourceCostsTag(game, source, "X", 0), source, game) > 0) { InvestigateEffect.doInvestigate(source.getControllerId(), 1, game, source); } return true; diff --git a/Mage.Sets/src/mage/cards/t/TreeOfPerdition.java b/Mage.Sets/src/mage/cards/t/TreeOfPerdition.java index 07de60bce02..6c7ac347f8c 100644 --- a/Mage.Sets/src/mage/cards/t/TreeOfPerdition.java +++ b/Mage.Sets/src/mage/cards/t/TreeOfPerdition.java @@ -63,7 +63,7 @@ class TreeOfPerditionEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player opponent = game.getPlayer(source.getFirstTarget()); Permanent perm = game.getPermanent(source.getSourceId()); - if (perm == null || opponent == null || !opponent.isLifeTotalCanChange()) { + if (perm == null || opponent == null || !opponent.isLifeTotalCanChange() || !perm.isCreature(game)) { return false; } diff --git a/Mage.Sets/src/mage/cards/t/TreeOfRedemption.java b/Mage.Sets/src/mage/cards/t/TreeOfRedemption.java index 25a6806d764..9e8cb4c558f 100644 --- a/Mage.Sets/src/mage/cards/t/TreeOfRedemption.java +++ b/Mage.Sets/src/mage/cards/t/TreeOfRedemption.java @@ -59,7 +59,7 @@ class TreeOfRedemptionEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); Permanent perm = game.getPermanent(source.getSourceId()); - if (perm == null || player == null || !player.isLifeTotalCanChange()) { + if (perm == null || player == null || !player.isLifeTotalCanChange() || !perm.isCreature(game)) { return false; } diff --git a/Mage.Sets/src/mage/cards/u/UnleashTheInferno.java b/Mage.Sets/src/mage/cards/u/UnleashTheInferno.java index 33ccf27b203..a2df1217059 100644 --- a/Mage.Sets/src/mage/cards/u/UnleashTheInferno.java +++ b/Mage.Sets/src/mage/cards/u/UnleashTheInferno.java @@ -67,19 +67,18 @@ class UnleashTheInfernoEffect extends OneShotEffect { if (permanent == null) { return false; } - int lethal = Math.min(permanent.getLethalDamage(source.getSourceId(), game), 7); - permanent.damage(7, source, game); - int excess = 7 - lethal; - if (excess > 0) { - ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new DestroyTargetEffect(), false); - FilterPermanent filter = new FilterArtifactOrEnchantmentPermanent( - "artifact or enchantment an opponent controls with mana value less than or equal to " + excess - ); - filter.add(TargetController.OPPONENT.getControllerPredicate()); - filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, excess + 1)); - ability.addTarget(new TargetPermanent(filter)); - game.fireReflexiveTriggeredAbility(ability, source); + int excess = permanent.damageWithExcess(7, source, game); + if (excess < 1) { + return true; } + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new DestroyTargetEffect(), false); + FilterPermanent filter = new FilterArtifactOrEnchantmentPermanent( + "artifact or enchantment an opponent controls with mana value less than or equal to " + excess + ); + filter.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, excess + 1)); + ability.addTarget(new TargetPermanent(filter)); + game.fireReflexiveTriggeredAbility(ability, source); return true; } } diff --git a/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java b/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java index df9fcd03c86..b30eaba92de 100644 --- a/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java +++ b/Mage.Sets/src/mage/cards/u/UriangerAugurelt.java @@ -6,6 +6,9 @@ import mage.abilities.Ability; import mage.abilities.common.PlayLandOrCastSpellTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.costs.mana.ManaCost; +import mage.abilities.costs.mana.ManaCosts; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeEffect; @@ -14,7 +17,6 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; -import mage.game.Controllable; import mage.game.Game; import mage.players.Player; import mage.target.targetpointer.FixedTarget; @@ -142,14 +144,16 @@ class UriangerAugureltPlayEffect extends AsThoughEffectImpl { if (card.isLand(game)) { return true; } - // TODO: This should ideally apply the reduction while the spell is being cast because effects that increase the cost apply first - Optional.ofNullable(source) - .map(Controllable::getControllerId) - .map(game::getPlayer) - .ifPresent(player -> player.setCastSourceIdWithAlternateMana( - card.getId(), CardUtil.reduceCost(card.getManaCost(), 2), - null, MageIdentifier.UriangerAugureltAlternateCast - )); + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + ManaCosts newCost = CardUtil.reduceCost(card.getManaCost(), 2); + if (newCost.isEmpty()) { + newCost.add(new GenericManaCost(0)); + } + controller.setCastSourceIdWithAlternateMana( + card.getId(), newCost, null, MageIdentifier.UriangerAugureltAlternateCast + ); + } return true; } } diff --git a/Mage.Sets/src/mage/cards/v/VikyaScorchingStalwart.java b/Mage.Sets/src/mage/cards/v/VikyaScorchingStalwart.java index 8fa03b3c406..bd59aeb7c9b 100644 --- a/Mage.Sets/src/mage/cards/v/VikyaScorchingStalwart.java +++ b/Mage.Sets/src/mage/cards/v/VikyaScorchingStalwart.java @@ -14,11 +14,13 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; +import mage.game.Controllable; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetAnyTarget; +import java.util.Optional; import java.util.UUID; /** @@ -91,14 +93,11 @@ class VikyaScorchingStalwartEffect extends OneShotEffect { if (!permanent.isCreature(game)) { return permanent.damage(amount, source, game) > 0; } - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - permanent.damage(amount, source.getSourceId(), source, game); - if (lethal >= amount) { - return true; - } - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.drawCards(1, source, game); + if (permanent.damageWithExcess(amount, source, game) > 0) { + Optional.ofNullable(source) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.drawCards(1, source, game)); } return true; } diff --git a/Mage.Sets/src/mage/cards/w/WindswiftSlice.java b/Mage.Sets/src/mage/cards/w/WindswiftSlice.java index 0c9751e1c7a..59cb03ca713 100644 --- a/Mage.Sets/src/mage/cards/w/WindswiftSlice.java +++ b/Mage.Sets/src/mage/cards/w/WindswiftSlice.java @@ -1,32 +1,33 @@ package mage.cards.w; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.ElfWarriorToken; import mage.target.TargetPermanent; import mage.target.common.TargetControlledCreaturePermanent; -import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.EachTargetPointer; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; import static mage.filter.StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL; /** - * * @author bwsinger */ public final class WindswiftSlice extends CardImpl { public WindswiftSlice(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); - + // Target creature you control deals damage equal to its power to target creature you don't control. Create a number of 1/1 green Elf Warrior creature tokens equal to the amount of excess damage dealt this way. this.getSpellAbility().addEffect(new WindswiftSliceEffect()); @@ -48,6 +49,7 @@ class WindswiftSliceEffect extends OneShotEffect { WindswiftSliceEffect() { super(Outcome.Benefit); + this.setTargetPointer(new EachTargetPointer()); staticText = "Target creature you control deals damage equal to its power to target " + "creature you don't control. Create a number of 1/1 green Elf Warrior creature " + "tokens equal to the amount of excess damage dealt this way."; @@ -64,27 +66,26 @@ class WindswiftSliceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent myPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - Permanent anotherPermanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); - - if (myPermanent == null || anotherPermanent == null) { + List permanents = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (permanents.size() < 2) { return false; } - - int power = myPermanent.getPower().getValue(); + Permanent permanent = permanents.get(0); + int power = permanent.getPower().getValue(); if (power < 1) { return false; } - - int lethal = anotherPermanent.getLethalDamage(myPermanent.getId(), game); - lethal = Math.min(lethal, power); - - anotherPermanent.damage(power, myPermanent.getId(), source, game); - - if (lethal < power) { - new ElfWarriorToken().putOntoBattlefield(power - lethal, game, source, source.getControllerId()); + Permanent creature = permanents.get(1); + int excess = creature.damageWithExcess(power, permanent.getId(), source, game); + if (excess > 0) { + new ElfWarriorToken().putOntoBattlefield(excess, game, source); } - return true; } } diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index 363d71b4765..10881e59c8a 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -27,6 +27,7 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Aang's Iceberg", 336, Rarity.RARE, mage.cards.a.AangsIceberg.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aang's Iceberg", 5, Rarity.RARE, mage.cards.a.AangsIceberg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang's Journey", 1, Rarity.COMMON, mage.cards.a.AangsJourney.class)); cards.add(new SetCardInfo("Aang, Master of Elements", 207, Rarity.MYTHIC, mage.cards.a.AangMasterOfElements.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aang, Master of Elements", 363, Rarity.MYTHIC, mage.cards.a.AangMasterOfElements.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aang, the Last Airbender", 4, Rarity.UNCOMMON, mage.cards.a.AangTheLastAirbender.class)); @@ -55,6 +56,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Epic Downfall", 96, Rarity.UNCOMMON, mage.cards.e.EpicDownfall.class)); cards.add(new SetCardInfo("Fated Firepower", 132, Rarity.MYTHIC, mage.cards.f.FatedFirepower.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fated Firepower", 341, Rarity.MYTHIC, mage.cards.f.FatedFirepower.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Lord Sozin", 117, Rarity.MYTHIC, mage.cards.f.FireLordSozin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Lord Sozin", 356, Rarity.MYTHIC, mage.cards.f.FireLordSozin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fire Lord Zuko", 221, Rarity.RARE, mage.cards.f.FireLordZuko.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fire Lord Zuko", 360, Rarity.RARE, mage.cards.f.FireLordZuko.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fire Nation Attacks", 133, Rarity.UNCOMMON, mage.cards.f.FireNationAttacks.class)); @@ -63,15 +66,19 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("First-Time Flyer", 49, Rarity.COMMON, mage.cards.f.FirstTimeFlyer.class)); cards.add(new SetCardInfo("Flexible Waterbender", 50, Rarity.COMMON, mage.cards.f.FlexibleWaterbender.class)); cards.add(new SetCardInfo("Flopsie, Bumi's Buddy", 179, Rarity.UNCOMMON, mage.cards.f.FlopsieBumisBuddy.class)); + cards.add(new SetCardInfo("Forest", 286, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 291, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Geyser Leaper", 52, Rarity.COMMON, mage.cards.g.GeyserLeaper.class)); cards.add(new SetCardInfo("Giant Koi", 53, Rarity.COMMON, mage.cards.g.GiantKoi.class)); cards.add(new SetCardInfo("Glider Kids", 21, Rarity.COMMON, mage.cards.g.GliderKids.class)); + cards.add(new SetCardInfo("Hakoda, Selfless Commander", 366, Rarity.RARE, mage.cards.h.HakodaSelflessCommander.class)); cards.add(new SetCardInfo("Haru, Hidden Talent", 182, Rarity.UNCOMMON, mage.cards.h.HaruHiddenTalent.class)); cards.add(new SetCardInfo("Heartless Act", 103, Rarity.UNCOMMON, mage.cards.h.HeartlessAct.class)); cards.add(new SetCardInfo("Hei Bai, Spirit of Balance", 225, Rarity.UNCOMMON, mage.cards.h.HeiBaiSpiritOfBalance.class)); cards.add(new SetCardInfo("Hog-Monkey", 104, Rarity.COMMON, mage.cards.h.HogMonkey.class)); + cards.add(new SetCardInfo("How to Start a Riot", 140, Rarity.COMMON, mage.cards.h.HowToStartARiot.class)); cards.add(new SetCardInfo("Iguana Parrot", 56, Rarity.COMMON, mage.cards.i.IguanaParrot.class)); + cards.add(new SetCardInfo("Island", 283, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 288, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("It'll Quench Ya!", 58, Rarity.COMMON, mage.cards.i.ItllQuenchYa.class)); cards.add(new SetCardInfo("Jeong Jeong's Deserters", 25, Rarity.COMMON, mage.cards.j.JeongJeongsDeserters.class)); @@ -82,20 +89,26 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Katara, the Fearless", 350, Rarity.RARE, mage.cards.k.KataraTheFearless.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Katara, the Fearless", 361, Rarity.RARE, mage.cards.k.KataraTheFearless.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lightning Strike", 146, Rarity.COMMON, mage.cards.l.LightningStrike.class)); + cards.add(new SetCardInfo("Long Feng, Grand Secretariat", 233, Rarity.UNCOMMON, mage.cards.l.LongFengGrandSecretariat.class)); cards.add(new SetCardInfo("Master Pakku", 63, Rarity.UNCOMMON, mage.cards.m.MasterPakku.class)); cards.add(new SetCardInfo("Master Piandao", 28, Rarity.UNCOMMON, mage.cards.m.MasterPiandao.class)); cards.add(new SetCardInfo("Merchant of Many Hats", 110, Rarity.COMMON, mage.cards.m.MerchantOfManyHats.class)); + cards.add(new SetCardInfo("Momo, Friendly Flier", 29, Rarity.RARE, mage.cards.m.MomoFriendlyFlier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Momo, Friendly Flier", 317, Rarity.RARE, mage.cards.m.MomoFriendlyFlier.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mongoose Lizard", 148, Rarity.COMMON, mage.cards.m.MongooseLizard.class)); + cards.add(new SetCardInfo("Mountain", 285, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 290, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Ostrich-Horse", 188, Rarity.COMMON, mage.cards.o.OstrichHorse.class)); cards.add(new SetCardInfo("Otter-Penguin", 67, Rarity.COMMON, mage.cards.o.OtterPenguin.class)); cards.add(new SetCardInfo("Ozai's Cruelty", 113, Rarity.UNCOMMON, mage.cards.o.OzaisCruelty.class)); cards.add(new SetCardInfo("Path to Redemption", 31, Rarity.COMMON, mage.cards.p.PathToRedemption.class)); cards.add(new SetCardInfo("Pillar Launch", 189, Rarity.COMMON, mage.cards.p.PillarLaunch.class)); + cards.add(new SetCardInfo("Plains", 282, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 287, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Pretending Poxbearers", 237, Rarity.COMMON, mage.cards.p.PretendingPoxbearers.class)); cards.add(new SetCardInfo("Rabaroo Troop", 32, Rarity.COMMON, mage.cards.r.RabarooTroop.class)); cards.add(new SetCardInfo("Raucous Audience", 190, Rarity.COMMON, mage.cards.r.RaucousAudience.class)); + cards.add(new SetCardInfo("Razor Rings", 33, Rarity.COMMON, mage.cards.r.RazorRings.class)); cards.add(new SetCardInfo("Rebellious Captives", 191, Rarity.COMMON, mage.cards.r.RebelliousCaptives.class)); cards.add(new SetCardInfo("Redirect Lightning", 151, Rarity.RARE, mage.cards.r.RedirectLightning.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Redirect Lightning", 343, Rarity.RARE, mage.cards.r.RedirectLightning.class, NON_FULL_USE_VARIOUS)); @@ -103,12 +116,17 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Rough Rhino Cavalry", 152, Rarity.COMMON, mage.cards.r.RoughRhinoCavalry.class)); cards.add(new SetCardInfo("Rowdy Snowballers", 68, Rarity.COMMON, mage.cards.r.RowdySnowballers.class)); cards.add(new SetCardInfo("Saber-Tooth Moose-Lion", 194, Rarity.COMMON, mage.cards.s.SaberToothMooseLion.class)); + cards.add(new SetCardInfo("Serpent of the Pass", 70, Rarity.UNCOMMON, mage.cards.s.SerpentOfThePass.class)); cards.add(new SetCardInfo("Sokka's Haiku", 71, Rarity.UNCOMMON, mage.cards.s.SokkasHaiku.class)); cards.add(new SetCardInfo("Sokka, Bold Boomeranger", 240, Rarity.RARE, mage.cards.s.SokkaBoldBoomeranger.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sokka, Bold Boomeranger", 383, Rarity.RARE, mage.cards.s.SokkaBoldBoomeranger.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sokka, Lateral Strategist", 241, Rarity.UNCOMMON, mage.cards.s.SokkaLateralStrategist.class)); cards.add(new SetCardInfo("Southern Air Temple", 36, Rarity.UNCOMMON, mage.cards.s.SouthernAirTemple.class)); cards.add(new SetCardInfo("Suki, Kyoshi Warrior", 243, Rarity.UNCOMMON, mage.cards.s.SukiKyoshiWarrior.class)); + cards.add(new SetCardInfo("Swamp", 284, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 289, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("The Rise of Sozin", 117, Rarity.MYTHIC, mage.cards.t.TheRiseOfSozin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Rise of Sozin", 356, Rarity.MYTHIC, mage.cards.t.TheRiseOfSozin.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Toph, the Blind Bandit", 198, Rarity.UNCOMMON, mage.cards.t.TophTheBlindBandit.class)); cards.add(new SetCardInfo("Toph, the First Metalbender", 247, Rarity.RARE, mage.cards.t.TophTheFirstMetalbender.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Toph, the First Metalbender", 353, Rarity.RARE, mage.cards.t.TophTheFirstMetalbender.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 877f0be8ad6..a1b357b008b 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -24,51 +24,85 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { super("Avatar: The Last Airbender Eternal", "TLE", ExpansionSet.buildDate(2025, 11, 21), SetType.SUPPLEMENTAL); this.blockName = "Avatar: The Last Airbender"; // for sorting in GUI this.rotationSet = true; - this.hasBasicLands = false; + this.hasBasicLands = true; - cards.add(new SetCardInfo("Aang's Defense", 211, Rarity.COMMON, mage.cards.a.AangsDefense.class)); - cards.add(new SetCardInfo("Aang, Air Nomad", 210, Rarity.RARE, mage.cards.a.AangAirNomad.class)); + cards.add(new SetCardInfo("Aang's Defense", 211, Rarity.COMMON, mage.cards.a.AangsDefense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang's Defense", 266, Rarity.COMMON, mage.cards.a.AangsDefense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang, Air Nomad", 210, Rarity.RARE, mage.cards.a.AangAirNomad.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aang, Air Nomad", 265, Rarity.RARE, mage.cards.a.AangAirNomad.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aang, Airbending Master", 74, Rarity.MYTHIC, mage.cards.a.AangAirbendingMaster.class)); - cards.add(new SetCardInfo("Aardvark Sloth", 212, Rarity.COMMON, mage.cards.a.AardvarkSloth.class)); + cards.add(new SetCardInfo("Aardvark Sloth", 212, Rarity.COMMON, mage.cards.a.AardvarkSloth.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Aardvark Sloth", 267, Rarity.COMMON, mage.cards.a.AardvarkSloth.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Allied Teamwork", 213, Rarity.RARE, mage.cards.a.AlliedTeamwork.class)); - cards.add(new SetCardInfo("Appa, Aang's Companion", 214, Rarity.UNCOMMON, mage.cards.a.AppaAangsCompanion.class)); + cards.add(new SetCardInfo("Appa, Aang's Companion", 214, Rarity.UNCOMMON, mage.cards.a.AppaAangsCompanion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Appa, Aang's Companion", 268, Rarity.UNCOMMON, mage.cards.a.AppaAangsCompanion.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bumi, Eclectic Earthbender", 248, Rarity.RARE, mage.cards.b.BumiEclecticEarthbender.class)); - cards.add(new SetCardInfo("Capital Guard", 234, Rarity.COMMON, mage.cards.c.CapitalGuard.class)); + cards.add(new SetCardInfo("Capital Guard", 234, Rarity.COMMON, mage.cards.c.CapitalGuard.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Capital Guard", 277, Rarity.COMMON, mage.cards.c.CapitalGuard.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Deny Entry", 222, Rarity.COMMON, mage.cards.d.DenyEntry.class)); - cards.add(new SetCardInfo("Dragon Moose", 235, Rarity.COMMON, mage.cards.d.DragonMoose.class)); + cards.add(new SetCardInfo("Dragon Moose", 235, Rarity.COMMON, mage.cards.d.DragonMoose.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Dragon Moose", 278, Rarity.COMMON, mage.cards.d.DragonMoose.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Earthbending Student", 249, Rarity.UNCOMMON, mage.cards.e.EarthbendingStudent.class)); cards.add(new SetCardInfo("Eel-Hounds", 250, Rarity.UNCOMMON, mage.cards.e.EelHounds.class)); cards.add(new SetCardInfo("Elephant-Rat", 228, Rarity.COMMON, mage.cards.e.ElephantRat.class)); cards.add(new SetCardInfo("Explore", 259, Rarity.COMMON, mage.cards.e.Explore.class)); - cards.add(new SetCardInfo("Explosive Shot", 236, Rarity.COMMON, mage.cards.e.ExplosiveShot.class)); + cards.add(new SetCardInfo("Explosive Shot", 236, Rarity.COMMON, mage.cards.e.ExplosiveShot.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Explosive Shot", 279, Rarity.COMMON, mage.cards.e.ExplosiveShot.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Feed the Swarm", 257, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class)); cards.add(new SetCardInfo("Fire Nation Ambushers", 229, Rarity.COMMON, mage.cards.f.FireNationAmbushers.class)); cards.add(new SetCardInfo("Fire Nation Archers", 237, Rarity.RARE, mage.cards.f.FireNationArchers.class)); cards.add(new SetCardInfo("Fire Nation Sentinels", 230, Rarity.RARE, mage.cards.f.FireNationSentinels.class)); - cards.add(new SetCardInfo("Fire Nation Soldier", 238, Rarity.COMMON, mage.cards.f.FireNationSoldier.class)); - cards.add(new SetCardInfo("Fire Nation's Conquest", 239, Rarity.UNCOMMON, mage.cards.f.FireNationsConquest.class)); + cards.add(new SetCardInfo("Fire Nation Soldier", 238, Rarity.COMMON, mage.cards.f.FireNationSoldier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Nation Soldier", 280, Rarity.COMMON, mage.cards.f.FireNationSoldier.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Nation's Conquest", 239, Rarity.UNCOMMON, mage.cards.f.FireNationsConquest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fire Nation's Conquest", 281, Rarity.UNCOMMON, mage.cards.f.FireNationsConquest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Flying Dolphin-Fish", 223, Rarity.COMMON, mage.cards.f.FlyingDolphinFish.class)); cards.add(new SetCardInfo("Force of Negation", 13, Rarity.MYTHIC, mage.cards.f.ForceOfNegation.class)); cards.add(new SetCardInfo("Frog-Squirrels", 251, Rarity.COMMON, mage.cards.f.FrogSquirrels.class)); cards.add(new SetCardInfo("Gilacorn", 231, Rarity.COMMON, mage.cards.g.Gilacorn.class)); cards.add(new SetCardInfo("Hippo-Cows", 252, Rarity.COMMON, mage.cards.h.HippoCows.class)); - cards.add(new SetCardInfo("Iroh, Firebending Instructor", 240, Rarity.UNCOMMON, mage.cards.i.IrohFirebendingInstructor.class)); - cards.add(new SetCardInfo("Katara, Heroic Healer", 215, Rarity.UNCOMMON, mage.cards.k.KataraHeroicHealer.class)); + cards.add(new SetCardInfo("Iroh, Firebending Instructor", 240, Rarity.UNCOMMON, mage.cards.i.IrohFirebendingInstructor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Iroh, Firebending Instructor", 282, Rarity.UNCOMMON, mage.cards.i.IrohFirebendingInstructor.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Katara, Heroic Healer", 215, Rarity.UNCOMMON, mage.cards.k.KataraHeroicHealer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Katara, Heroic Healer", 269, Rarity.UNCOMMON, mage.cards.k.KataraHeroicHealer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Katara, Waterbending Master", 93, Rarity.MYTHIC, mage.cards.k.KataraWaterbendingMaster.class)); - cards.add(new SetCardInfo("Komodo Rhino", 241, Rarity.COMMON, mage.cards.k.KomodoRhino.class)); + cards.add(new SetCardInfo("Komodo Rhino", 241, Rarity.COMMON, mage.cards.k.KomodoRhino.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Komodo Rhino", 283, Rarity.COMMON, mage.cards.k.KomodoRhino.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kyoshi Warrior Guard", 216, Rarity.COMMON, mage.cards.k.KyoshiWarriorGuard.class)); cards.add(new SetCardInfo("Lion Vulture", 232, Rarity.RARE, mage.cards.l.LionVulture.class)); cards.add(new SetCardInfo("Lost in the Spirit World", 224, Rarity.UNCOMMON, mage.cards.l.LostInTheSpiritWorld.class)); cards.add(new SetCardInfo("Loyal Fire Sage", 242, Rarity.UNCOMMON, mage.cards.l.LoyalFireSage.class)); cards.add(new SetCardInfo("Match the Odds", 253, Rarity.UNCOMMON, mage.cards.m.MatchTheOdds.class)); cards.add(new SetCardInfo("Mechanical Glider", 256, Rarity.COMMON, mage.cards.m.MechanicalGlider.class)); - cards.add(new SetCardInfo("Momo, Rambunctious Rascal", 217, Rarity.UNCOMMON, mage.cards.m.MomoRambunctiousRascal.class)); + cards.add(new SetCardInfo("Momo, Rambunctious Rascal", 217, Rarity.UNCOMMON, mage.cards.m.MomoRambunctiousRascal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Momo, Rambunctious Rascal", 270, Rarity.UNCOMMON, mage.cards.m.MomoRambunctiousRascal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 289, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 290, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 291, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 292, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 293, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 294, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 295, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mountain", 296, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Path to Redemption", 271, Rarity.COMMON, mage.cards.p.PathToRedemption.class)); + cards.add(new SetCardInfo("Plains", 297, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 298, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 299, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 300, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 301, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 302, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 303, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Plains", 304, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Purple Pentapus", 233, Rarity.COMMON, mage.cards.p.PurplePentapus.class)); + cards.add(new SetCardInfo("Razor Rings", 272, Rarity.COMMON, mage.cards.r.RazorRings.class)); cards.add(new SetCardInfo("Roku's Mastery", 243, Rarity.UNCOMMON, mage.cards.r.RokusMastery.class)); - cards.add(new SetCardInfo("Run Amok", 258, Rarity.COMMON, mage.cards.r.RunAmok.class)); + cards.add(new SetCardInfo("Run Amok", 258, Rarity.COMMON, mage.cards.r.RunAmok.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Run Amok", 284, Rarity.COMMON, mage.cards.r.RunAmok.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Seismic Tutelage", 254, Rarity.RARE, mage.cards.s.SeismicTutelage.class)); - cards.add(new SetCardInfo("Sledding Otter-Penguin", 218, Rarity.COMMON, mage.cards.s.SleddingOtterPenguin.class)); - cards.add(new SetCardInfo("Sokka, Wolf Cove's Protector", 219, Rarity.UNCOMMON, mage.cards.s.SokkaWolfCovesProtector.class)); + cards.add(new SetCardInfo("Sledding Otter-Penguin", 218, Rarity.COMMON, mage.cards.s.SleddingOtterPenguin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sledding Otter-Penguin", 273, Rarity.COMMON, mage.cards.s.SleddingOtterPenguin.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sokka, Wolf Cove's Protector", 219, Rarity.UNCOMMON, mage.cards.s.SokkaWolfCovesProtector.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sokka, Wolf Cove's Protector", 274, Rarity.UNCOMMON, mage.cards.s.SokkaWolfCovesProtector.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Cabbage Merchant", 134, Rarity.RARE, mage.cards.t.TheCabbageMerchant.class)); cards.add(new SetCardInfo("The Great Henge", 41, Rarity.MYTHIC, mage.cards.t.TheGreatHenge.class)); cards.add(new SetCardInfo("The Terror of Serpent's Pass", 225, Rarity.RARE, mage.cards.t.TheTerrorOfSerpentsPass.class)); @@ -77,14 +111,20 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Thriving Heath", 262, Rarity.COMMON, mage.cards.t.ThrivingHeath.class)); cards.add(new SetCardInfo("Thriving Isle", 263, Rarity.COMMON, mage.cards.t.ThrivingIsle.class)); cards.add(new SetCardInfo("Thriving Moor", 264, Rarity.COMMON, mage.cards.t.ThrivingMoor.class)); - cards.add(new SetCardInfo("Tundra Wall", 220, Rarity.COMMON, mage.cards.t.TundraWall.class)); + cards.add(new SetCardInfo("Tundra Wall", 220, Rarity.COMMON, mage.cards.t.TundraWall.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Tundra Wall", 275, Rarity.COMMON, mage.cards.t.TundraWall.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Turtle-Seals", 226, Rarity.COMMON, mage.cards.t.TurtleSeals.class)); - cards.add(new SetCardInfo("Warship Scout", 244, Rarity.COMMON, mage.cards.w.WarshipScout.class)); + cards.add(new SetCardInfo("Warship Scout", 244, Rarity.COMMON, mage.cards.w.WarshipScout.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Warship Scout", 285, Rarity.COMMON, mage.cards.w.WarshipScout.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Water Whip", 227, Rarity.RARE, mage.cards.w.WaterWhip.class)); - cards.add(new SetCardInfo("Wolf Cove Villager", 221, Rarity.COMMON, mage.cards.w.WolfCoveVillager.class)); - cards.add(new SetCardInfo("Zhao, the Seething Flame", 245, Rarity.UNCOMMON, mage.cards.z.ZhaoTheSeethingFlame.class)); - cards.add(new SetCardInfo("Zuko's Offense", 247, Rarity.COMMON, mage.cards.z.ZukosOffense.class)); - cards.add(new SetCardInfo("Zuko, Avatar Hunter", 246, Rarity.RARE, mage.cards.z.ZukoAvatarHunter.class)); + cards.add(new SetCardInfo("Wolf Cove Villager", 221, Rarity.COMMON, mage.cards.w.WolfCoveVillager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Wolf Cove Villager", 276, Rarity.COMMON, mage.cards.w.WolfCoveVillager.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zhao, the Seething Flame", 245, Rarity.UNCOMMON, mage.cards.z.ZhaoTheSeethingFlame.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zhao, the Seething Flame", 286, Rarity.UNCOMMON, mage.cards.z.ZhaoTheSeethingFlame.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zuko's Offense", 247, Rarity.COMMON, mage.cards.z.ZukosOffense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zuko's Offense", 288, Rarity.COMMON, mage.cards.z.ZukosOffense.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zuko, Avatar Hunter", 246, Rarity.RARE, mage.cards.z.ZukoAvatarHunter.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Zuko, Avatar Hunter", 287, Rarity.RARE, mage.cards.z.ZukoAvatarHunter.class, NON_FULL_USE_VARIOUS)); cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AddCardSubtypeAllEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AddCardSubtypeAllEffectTest.java new file mode 100644 index 00000000000..6b52fd0918c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AddCardSubtypeAllEffectTest.java @@ -0,0 +1,55 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class AddCardSubtypeAllEffectTest extends CardTestPlayerBase { + + /* + Kudo, King Among Bears + {G}{W} + Legendary Creature — Bear + Other creatures have base power and toughness 2/2 and are Bears in addition to their other types. + 2/2 + */ + private static final String kudo = "Kudo, King Among Bears"; + + /* + Fugitive Wizard + {U} + Creature — Human Wizard + 1/1 + */ + private static final String fugitive = "Fugitive Wizard"; + + @Test + public void testAddCardSubtypeAllEffect() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, fugitive, 3); + addCard(Zone.BATTLEFIELD, playerB, fugitive, 3); + addCard(Zone.BATTLEFIELD, playerA, kudo); + addCard(Zone.BATTLEFIELD, playerA, "Savannah", 2); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { + if (permanent.getName().equals(fugitive)) { + assertTrue(permanent.hasSubtype(SubType.BEAR, currentGame)); + assertTrue(permanent.hasSubtype(SubType.WIZARD, currentGame)); + assertTrue(permanent.hasSubtype(SubType.HUMAN, currentGame)); + assertEquals(2, permanent.getPower().getModifiedBaseValue()); + assertEquals(2, permanent.getToughness().getModifiedBaseValue()); + } + } + + assertSubtype(kudo, SubType.BEAR); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesColorEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesColorEffectTest.java new file mode 100644 index 00000000000..0002250c017 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesColorEffectTest.java @@ -0,0 +1,69 @@ +package org.mage.test.cards.continuous; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class BecomesColorEffectTest extends CardTestPlayerBase { + + /* + Ancient Kavu + {3}{R} + Creature — Kavu + {2}: This creature becomes colorless until end of turn. + Those with the ability to change their nature survived Phyrexia’s biological attacks. Everything else died. + 3/3 + */ + String kavu = "Ancient Kavu"; + /* + Alchor's Tomb + {4} + Artifact + {2}, {T}: Target permanent you control becomes the color of your choice. (This effect lasts indefinitely.) + */ + String alchorsTomb = "Alchor's Tomb"; + /* + + */ + + @Test + public void testBecomesColorSource() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, kavu); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + + checkColor("Ancient Kavu is red", 1, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "R", true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: {this}"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkColor("Ancient Kavu is colorless", 1, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "C", true); + checkColor("Ancient Kavu is red again", 2, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "R", true); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + } + + @Test + public void testBecomesColorTarget() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, kavu); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, alchorsTomb); + + checkColor("Ancient Kavu is red", 1, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "R", true); + // make Ancient Kavu green + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}: Target permanent", kavu); + setChoice(playerA, "Green"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkColor("Ancient Kavu is green", 1, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "G", true); + checkColor("Ancient Kavu is still green the following turn", 2, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "G", true); + // activate Ancient Kavu's ability to override green color until end of turn + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}: {this}"); + checkColor("Ancient Kavu is colorless", 3, PhaseStep.POSTCOMBAT_MAIN, playerA, kavu, "C", true); + // next turn it should be green again + checkColor("Ancient Kavu is green again", 4, PhaseStep.PRECOMBAT_MAIN, playerA, kavu, "G", true); + + setStopAt(4, PhaseStep.BEGIN_COMBAT); + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureEffectTest.java new file mode 100644 index 00000000000..9b9cff0f81f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureEffectTest.java @@ -0,0 +1,87 @@ +package org.mage.test.cards.continuous; + +import mage.ObjectColor; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.filter.predicate.mageobject.ToughnessPredicate; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import java.util.Collections; + +public class BecomesCreatureEffectTest extends CardTestPlayerBase { + /* + Ambush Commander + {3}{G}{G} + Creature — Elf + Forests you control are 1/1 green Elf creatures that are still lands. + {1}{G}, Sacrifice an Elf: Target creature gets +3/+3 until end of turn. + 2/2 + */ + String ambushCommander = "Ambush Commander"; + /* + Dryad Arbor + Land Creature — Forest Dryad + 1/1 + */ + String dryadArbor = "Dryad Arbor"; + /* + Frogify + {1}{U} + Enchantment — Aura + Enchant creature + Enchanted creature loses all abilities and is a blue Frog creature with base power and toughness 1/1. + (It loses all other card types and creature types.) + */ + String frogify = "Frogify"; + @Test + public void testBecomesCreatureAllEffect() { + FilterPermanent filter = new FilterPermanent(); + filter.add(CardType.CREATURE.getPredicate()); + filter.add(SubType.ELF.getPredicate()); + filter.add(new PowerPredicate(ComparisonType.EQUAL_TO, 1)); + filter.add(new ToughnessPredicate(ComparisonType.EQUAL_TO, 1)); + + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.BATTLEFIELD, playerA, dryadArbor); + addCard(Zone.HAND, playerA, ambushCommander); + + runCode("Check forests are not 1/1 Elves", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + int numElves = game.getBattlefield().getActivePermanents(filter, player.getId(), game).size(); + Assert.assertEquals("No 1/1 elves should be present", 0, numElves); + }); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ambushCommander); + runCode("Check forests are 1/1 Elves", 1, PhaseStep.BEGIN_COMBAT, playerA, (info, player, game) -> { + int numElves = game.getBattlefield().getActivePermanents(filter, player.getId(), game).size(); + // 5 forests + dryad arbor + Assert.assertEquals("There should be 6 1/1 elves present", 6, numElves); + }); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + } + + @Test + public void testBecomesCreatureAttachedEffect() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, dryadArbor); + addCard(Zone.HAND, playerA, frogify); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, frogify, dryadArbor); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAbilities(playerA, dryadArbor, Collections.emptyList()); + assertPowerToughness(playerA, dryadArbor, 1, 1); + assertType(dryadArbor, CardType.CREATURE, SubType.FROG); + assertNotSubtype(dryadArbor, SubType.DRYAD); + assertNotType(dryadArbor, CardType.LAND); + assertColor(playerA, dryadArbor, ObjectColor.BLUE, true); + assertColor(playerA, dryadArbor, ObjectColor.GREEN, false); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureIfVehicleEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureIfVehicleEffectTest.java new file mode 100644 index 00000000000..a95936d743a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/BecomesCreatureIfVehicleEffectTest.java @@ -0,0 +1,45 @@ +package org.mage.test.cards.continuous; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class BecomesCreatureIfVehicleEffectTest extends CardTestPlayerBase { + + /* + Aerial Modification + {4}{W} + Enchantment — Aura + Enchant creature or Vehicle + As long as enchanted permanent is a Vehicle, it’s a creature in addition to its other types. + Enchanted creature gets +2/+2 and has flying. + */ + String aerialMod = "Aerial Modification"; + /* + Goliath Truck + {4} + Artifact — Vehicle + Stowage — Whenever this Vehicle attacks, put two +1/+1 counters on another target attacking creature. + Crew 2 (Tap any number of creatures you control with total power 2 or more: This Vehicle becomes an artifact creature until end of turn.) + 4/4 + */ + String goliathTruck = "Goliath Truck"; + + @Test + public void testBecomesCreatureIfVehicleEffect() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, goliathTruck); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.HAND, playerA, aerialMod); + + checkType("Goliath Truck is not a creature", 1, PhaseStep.PRECOMBAT_MAIN, playerA, goliathTruck, CardType.CREATURE, false); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aerialMod, goliathTruck); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertType(goliathTruck, CardType.CREATURE, true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemsTest.java index a1587ff240e..5260c13070f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/emblems/EmblemsTest.java @@ -157,4 +157,29 @@ public class EmblemsTest extends CardTestPlayerBase { assertHandCount(playerA, 0); } + + @Test + public void testJayaFieryNegotiator() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Jaya, Fiery Negotiator"); + addCard(Zone.HAND, playerA, "Wrenn's Resolve"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.LIBRARY, playerA, "Lightning Bolt", 2); + skipInitShuffling(); + + addCounters(1, PhaseStep.UPKEEP, playerA, "Jaya, Fiery Negotiator", CounterType.LOYALTY, 6); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-8: You get an emblem"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wrenn's Resolve"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + setChoice(playerA, false, 2); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, 6 - 1); + assertLife(playerB, 20 - 3 * 3); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/_5ed/AnHavvaConstableTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/_5ed/AnHavvaConstableTest.java new file mode 100644 index 00000000000..d6485265308 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/_5ed/AnHavvaConstableTest.java @@ -0,0 +1,51 @@ +package org.mage.test.cards.single._5ed; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AnHavvaConstableTest extends CardTestPlayerBase { + + /* + An-Havva Constable + {1}{G}{G} + Creature — Human + An-Havva Constable’s toughness is equal to 1 plus the number of green creatures on the battlefield. + 2/1+* + */ + private static final String constable = "An-Havva Constable"; + /* + Bear Cub + {1}{G} + Creature — Bear + 2/2 + */ + private static final String cub = "Bear Cub"; + @Test + public void testAnHavva() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, constable); + addCard(Zone.HAND, playerA, constable); + addCard(Zone.BATTLEFIELD, playerA, cub, 2); + addCard(Zone.BATTLEFIELD, playerB, cub, 3); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + checkPT("An-Havva Constable has toughness 7", 1, PhaseStep.PRECOMBAT_MAIN, playerA, constable, 2, 1 + 6); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", cub); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPT("An-Havva Constable has toughness 6", 1, PhaseStep.PRECOMBAT_MAIN, playerA, constable, 2, 1 + 5); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + playerA.getHand().getCards(currentGame).stream() + .filter(card -> card.getName().equals(constable)) + .findFirst(). + ifPresent(card -> Assert.assertEquals(6, card.getToughness().getValue())); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/DarkImpostorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/DarkImpostorTest.java new file mode 100644 index 00000000000..6be3f478b97 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/avr/DarkImpostorTest.java @@ -0,0 +1,53 @@ +package org.mage.test.cards.single.avr; + +import mage.abilities.common.SimpleActivatedAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class DarkImpostorTest extends CardTestPlayerBase { + + /* + Dark Impostor + {2}{B} + Creature — Vampire Assassin + + {4}{B}{B}: Exile target creature and put a +1/+1 counter on this creature. + + This creature has all activated abilities of all creature cards exiled with it. + 2/2 + */ + public static final String darkImposter = "Dark Impostor"; + /* + Deathrite Shaman + {B/G} + Creature — Elf Shaman + + {T}: Exile target land card from a graveyard. Add one mana of any color. + + {B}, {T}: Exile target instant or sorcery card from a graveyard. Each opponent loses 2 life. + + {G}, {T}: Exile target creature card from a graveyard. You gain 2 life. + + 1/2 + */ + public static final String deathriteShaman = "Deathrite Shaman"; + + @Test + public void testDarkImpostor() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, darkImposter); + addCard(Zone.BATTLEFIELD, playerA, deathriteShaman); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{B}{B}"); + addTarget(playerA, deathriteShaman); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAbilityCount(playerA, darkImposter, SimpleActivatedAbility.class, 4); // own ability + 3 other from deathrite + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c13/ActOfAuthorityEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c13/ActOfAuthorityEffectTest.java new file mode 100644 index 00000000000..05ce5bdb0d4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c13/ActOfAuthorityEffectTest.java @@ -0,0 +1,34 @@ +package org.mage.test.cards.single.c13; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class ActOfAuthorityEffectTest extends CardTestPlayerBase { + /* + Act of Authority + {1}{W}{W} + Enchantment + When this enchantment enters, you may exile target artifact or enchantment. + At the beginning of your upkeep, you may exile target artifact or enchantment. If you do, its controller gains control of this enchantment. + */ + private static final String actOfAuthority = "Act of Authority"; + + @Test + public void testActOfAuthority() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, actOfAuthority); + addCard(Zone.BATTLEFIELD, playerB, actOfAuthority + "@actB"); + + setChoice(playerA, true); // upkeep + addTarget(playerA, "@actB"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerB, actOfAuthority, 1); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/AatchikEmeraldRadianTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/AatchikEmeraldRadianTest.java new file mode 100644 index 00000000000..bb03167ce2a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/AatchikEmeraldRadianTest.java @@ -0,0 +1,57 @@ +package org.mage.test.cards.single.dft; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AatchikEmeraldRadianTest extends CardTestPlayerBase { + + /* + Aatchik, Emerald Radian + {3}{B}{B}{G} + Legendary Creature — Insect Druid + When Aatchik enters, create a 1/1 green Insect creature token for each artifact and/or creature card in your graveyard. + Whenever another Insect you control dies, put a +1/+1 counter on Aatchik. Each opponent loses 1 life. + 3/3 + */ + private static final String aatchik = "Aatchik, Emerald Radian"; + + /* + Springheart Nantuko + {1}{G} + Enchantment Creature — Insect Monk + Bestow {1}{G} + Enchanted creature gets +1/+1. + Landfall — Whenever a land you control enters, you may pay {1}{G} if this permanent is attached to a creature you control. + If you do, create a token that’s a copy of that creature. If you didn’t create a token this way, create a 1/1 green Insect creature token. + 1/1 + */ + private static final String nantuko = "Springheart Nantuko"; + + @Test + public void testOpponentCreatingTokens() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, aatchik); + addCard(Zone.BATTLEFIELD, playerB, aatchik); + addCard(Zone.GRAVEYARD, playerA, aatchik); + addCard(Zone.GRAVEYARD, playerB, aatchik); + addCard(Zone.BATTLEFIELD, playerA, "Bayou", 9); + addCard(Zone.HAND, playerA, nantuko); + addCard(Zone.HAND, playerA, "Bayou"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nantuko + " using bestow"); + addTarget(playerA, aatchik); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Bayou"); + setChoice(playerA, true); + setChoice(playerA, aatchik); + setChoice(playerA, "When {this} enters"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertTokenCount(playerB, "Insect Token", 0); + assertTokenCount(playerA, "Insect Token", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dis/ExperimentKrajTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dis/ExperimentKrajTest.java new file mode 100644 index 00000000000..2a0e364821b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dis/ExperimentKrajTest.java @@ -0,0 +1,77 @@ +package org.mage.test.cards.single.dis; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.UntapAllControllerEffect; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import static org.junit.Assert.assertEquals; + +public class ExperimentKrajTest extends CardTestPlayerBase { + + /* + Experiment Kraj + {2}{G}{G}{U}{U} + Legendary Creature — Ooze Mutant + + Experiment Kraj has all activated abilities of each other creature with a +1/+1 counter on it. + + {T}: Put a +1/+1 counter on target creature. + */ + private static final String experimentKraj = "Experiment Kraj"; + /* + Stoneforge Mystic + {1}{W} + Creature — Kor Artificer + + When this creature enters, you may search your library for an Equipment card, reveal it, put it into your hand, then shuffle. + + {1}{W}, {T}: You may put an Equipment card from your hand onto the battlefield. + */ + private static final String stoneforgeMystic = "Stoneforge Mystic"; + /* + Noble Hierarch + {G} + Creature — Human Druid + + Exalted (Whenever a creature you control attacks alone, that creature gets +1/+1 until end of turn.) + + {T}: Add {G}, {W}, or {U}. + */ + private static final String nobleHierarch = "Noble Hierarch"; + + @Test + public void testExperimentKraj() { + setStrictChooseMode(true); + + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new UntapAllControllerEffect(StaticFilters.FILTER_CONTROLLED_A_CREATURE), + new ManaCostsImpl<>("") + ); + addCustomCardWithAbility("Untap creatures", playerA, ability); + + addCard(Zone.BATTLEFIELD, playerA, experimentKraj); + addCard(Zone.BATTLEFIELD, playerA, stoneforgeMystic); + addCard(Zone.BATTLEFIELD, playerB, nobleHierarch); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Put a +1/+1", stoneforgeMystic); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "untap all"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Put a +1/+1", nobleHierarch); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertEquals("Kraj should have 5 activated abilities", 5, getPermanent(experimentKraj).getAbilities(currentGame) + .stream() + .filter(Ability::isActivatedAbility) + .count()); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/UriangerAugureltTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/UriangerAugureltTest.java new file mode 100644 index 00000000000..cf66bcd37fd --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/UriangerAugureltTest.java @@ -0,0 +1,75 @@ +package org.mage.test.cards.single.fic; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.UntapAllControllerEffect; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class UriangerAugureltTest extends CardTestPlayerBase { + + private static final String urianger = "Urianger Augurelt"; + private static final String arcaneSignet = "Arcane Signet"; + private static final String howlingMine = "Howling Mine"; + private static final String thoughtVessel = "Thought Vessel"; + private static final String benalishKnight = "Benalish Knight"; + + @Test + public void uriangerAugureltTest() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + + Ability ability = new SimpleActivatedAbility( + Zone.ALL, + new UntapAllControllerEffect(StaticFilters.FILTER_CONTROLLED_A_CREATURE), + new ManaCostsImpl<>("") + ); + addCustomCardWithAbility("Untap creatures", playerA, ability); + + addCard(Zone.BATTLEFIELD, playerA, urianger); + addCard(Zone.LIBRARY, playerA, "Plains", 3); + addCard(Zone.LIBRARY, playerA, arcaneSignet); + addCard(Zone.LIBRARY, playerA, howlingMine); + addCard(Zone.LIBRARY, playerA, thoughtVessel); + addCard(Zone.LIBRARY, playerA, benalishKnight); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Draw Arcanum"); + setChoice(playerA, true, 4); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "untap all"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Draw Arcanum"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "untap all"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Draw Arcanum"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "untap all"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Draw Arcanum"); + + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Play Arcanum"); + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); + playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, benalishKnight, true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, arcaneSignet, true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, howlingMine, true); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, thoughtVessel, true); + + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, benalishKnight, 1); + assertPermanentCount(playerA, arcaneSignet, 1); + assertPermanentCount(playerA, howlingMine, 1); + assertPermanentCount(playerA, thoughtVessel, 1); + assertLife(playerA, 20 + 2 * 4); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/AettirAndPriwenTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/AettirAndPriwenTest.java new file mode 100644 index 00000000000..c5bd9d4e334 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fin/AettirAndPriwenTest.java @@ -0,0 +1,66 @@ +package org.mage.test.cards.single.fin; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AettirAndPriwenTest extends CardTestPlayerBase { + + /* + Aettir and Priwen + {6} + Legendary Artifact — Equipment + Equipped creature has base power and toughness X/X, where X is your life total. + Equip {5} + */ + private static final String aettir = "Aettir and Priwen"; + /* + Bear Cub + {1}{G} + Creature — Bear + 2/2 + */ + private static final String cub = "Bear Cub"; + /* + Lightning Bolt + {R} + Instant + Lightning Bolt deals 3 damage to any target. + */ + public static final String bolt = "Lightning Bolt"; + + @Test + public void testAettirAndPriwen() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, aettir); + addCard(Zone.BATTLEFIELD, playerA, cub); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.HAND, playerA, bolt, 2); + + checkPowerToughness(2, cub, 1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}: Equip"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPowerToughness(20, cub, 1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bolt, playerA); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + checkPowerToughness(20 - 3, cub, 1, PhaseStep.POSTCOMBAT_MAIN); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bolt, playerA); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + checkPowerToughness(20 - 3 * 2, cub, 1, PhaseStep.POSTCOMBAT_MAIN); + + checkPowerToughness(20 - 3 * 2, cub, 2, PhaseStep.PRECOMBAT_MAIN); + } + + void checkPowerToughness(int xValue, String name, int turnNum, PhaseStep phaseStep) { + runCode("Checking P/T is " + xValue, turnNum, phaseStep, playerA, (info, player, game) -> { + Permanent permanent = getPermanent(name, player); + Assert.assertEquals(xValue, permanent.getPower().getModifiedBaseValue()); + Assert.assertEquals(xValue, permanent.getToughness().getModifiedBaseValue()); + }); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/AbuelosAwakeningTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/AbuelosAwakeningTest.java new file mode 100644 index 00000000000..569db29afcf --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/AbuelosAwakeningTest.java @@ -0,0 +1,78 @@ +package org.mage.test.cards.single.lci; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AbuelosAwakeningTest extends CardTestPlayerBase { + + /* + Abuelo's Awakening + {X}{3}{W} + Sorcery + Return target artifact or non-Aura enchantment card from your graveyard to the battlefield with X additional +1/+1 counters on it. + It’s a 1/1 Spirit creature with flying in addition to its other types. + */ + public static final String abuelosAwakening = "Abuelo's Awakening"; + /* + Talisman of Progress + {2} + Artifact + {T}: Add {C}. + {T}: Add {W} or {U}. This artifact deals 1 damage to you. + */ + public static final String talisman = "Talisman of Progress"; + /* + Lightning Bolt + {R} + Instant + Lightning Bolt deals 3 damage to any target. + */ + public static final String bolt = "Lightning Bolt"; + + @Test + public void testAbuelosAwakening() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerA, talisman); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.HAND, playerA, abuelosAwakening); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, abuelosAwakening, talisman); + setChoiceAmount(playerA, 2); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, talisman, CounterType.P1P1, 2); + assertType(talisman, CardType.CREATURE, true); + assertSubtype(talisman, SubType.SPIRIT); + assertBasePowerToughness(playerA, talisman, 1, 1); + } + + @Test + public void testAbuelosAwakeningDies() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerA, talisman); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + addCard(Zone.HAND, playerA, abuelosAwakening); + addCard(Zone.HAND, playerB, bolt); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, abuelosAwakening, talisman); + setChoiceAmount(playerA, 2); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, bolt, talisman); + + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, talisman, 0); + assertGraveyardCount(playerA, talisman, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/DireBlunderbussTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/DireBlunderbussTest.java new file mode 100644 index 00000000000..2acd66fb4de --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/DireBlunderbussTest.java @@ -0,0 +1,44 @@ +package org.mage.test.cards.single.lci; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class DireBlunderbussTest extends CardTestPlayerBase { + + /* + Dire Blunderbuss + Color Indicator: RedArtifact — Equipment + + Equipped creature gets +3/+0 and has “Whenever this creature attacks, you may sacrifice an artifact other than Dire Blunderbuss. When you do, this creature deals damage equal to its power to target creature.” + + Equip {1} + */ + private static final String direBlunderBuss = "Dire Blunderbuss"; + + @Test + public void DireBlunderbussTest() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.BATTLEFIELD, playerA, direBlunderBuss); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears"); + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears@bearsB"); + addCard(Zone.BATTLEFIELD, playerA, "Tormod's Crypt"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Balduvian Bears"); + + attack(1, playerA, "Balduvian Bears"); + setChoice(playerA, true); + setChoice(playerA, "Tormod's Crypt"); + addTarget(playerA, "@bearsB"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, "Balduvian Bears", 1); + assertLife(playerB, 20 - 2 - 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/EatenByPiranhasTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/EatenByPiranhasTest.java new file mode 100644 index 00000000000..55692a80fe0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/EatenByPiranhasTest.java @@ -0,0 +1,44 @@ +package org.mage.test.cards.single.lci; + +import mage.ObjectColor; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class EatenByPiranhasTest extends CardTestPlayerBase { + + /* + Eaten by Piranhas + {1}{U} + Enchantment — Aura + + Flash (You may cast this spell any time you could cast an instant.) + + Enchant creature + + Enchanted creature loses all abilities and is a black Skeleton creature with base power and toughness 1/1. (It loses all other colors, card types, and creature types.) + */ + private static final String eatenByPiranhas = "Eaten by Piranhas"; + + @Test + public void testEatenByPiranhas() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerB, eatenByPiranhas); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, eatenByPiranhas); + addTarget(playerB, "Balduvian Bears"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertType("Balduvian Bears", CardType.CREATURE, SubType.SKELETON); + assertPowerToughness(playerA, "Balduvian Bears", 1, 1); + assertColor(playerA, "Balduvian Bears", ObjectColor.BLACK, true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AlpineMoonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AlpineMoonTest.java new file mode 100644 index 00000000000..6f1399f9e03 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AlpineMoonTest.java @@ -0,0 +1,63 @@ +package org.mage.test.cards.single.m19; + +import mage.abilities.mana.AnyColorManaAbility; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AlpineMoonTest extends CardTestPlayerBase { + /* + Alpine Moon + {R} + Enchantment + As this enchantment enters, choose a nonbasic land card name. + Lands your opponents control with the chosen name lose all land types and abilities, and they gain “{T}: Add one mana of any color.” + */ + private static final String alpine = "Alpine Moon"; + /* + Urborg, Tomb of Yawgmoth + Legendary Land + Each land is a Swamp in addition to its other land types. + */ + private static final String urborg = "Urborg, Tomb of Yawgmoth"; + + @Test + public void testAlpineMoonAfterUrborg() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, urborg); + addCard(Zone.BATTLEFIELD, playerB, alpine); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + + setChoice(playerB, urborg); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertNotSubtype(urborg, SubType.SWAMP); + assertSubtype("Mountain", SubType.SWAMP); + assertSubtype("Island", SubType.SWAMP); + assertAbility(playerA, urborg, new AnyColorManaAbility(), true); + } + + @Test + public void testAlpineMoonBeforeUrborg() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerB, urborg); + addCard(Zone.BATTLEFIELD, playerA, alpine); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerB, "Island"); + + setChoice(playerA, urborg); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertNotSubtype(urborg, SubType.SWAMP); + assertSubtype("Mountain", SubType.SWAMP); + assertSubtype("Island", SubType.SWAMP); + assertAbility(playerB, urborg, new AnyColorManaAbility(), true); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/AeveProgenitorOozeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/AeveProgenitorOozeTest.java new file mode 100644 index 00000000000..f60dc9fa1e6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/AeveProgenitorOozeTest.java @@ -0,0 +1,60 @@ +package org.mage.test.cards.single.mh2; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AeveProgenitorOozeTest extends CardTestPlayerBase { + + /* + Aeve, Progenitor Ooze + {2}{G}{G}{G} + Legendary Creature — Ooze + Storm (When you cast this spell, copy it for each spell cast before it this turn. Copies become tokens.) + Aeve isn’t legendary if it’s a token. + Aeve enters with a +1/+1 counter on it for each other Ooze you control. + 2/2 + */ + private static final String aeve = "Aeve, Progenitor Ooze"; + /* + Lightning Bolt + {R} + Instant + Lightning Bolt deals 3 damage to any target. + */ + public static final String bolt = "Lightning Bolt"; + + @Test + public void testAeve() { + setStrictChooseMode(true); + addCard(Zone.HAND, playerA, aeve); + addCard(Zone.HAND, playerA, bolt); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, playerB); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aeve); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, aeve, 2); + assertTokenCount(playerA, aeve, 1); + for (Permanent permanent : currentGame.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, playerA.getId(), currentGame)) { + if (permanent.getName().equals(aeve)) { + if (permanent instanceof PermanentToken) { + Assert.assertEquals(0, permanent.getCounters(currentGame).getCount(CounterType.P1P1)); + } else { + Assert.assertEquals(1, permanent.getCounters(currentGame).getCount(CounterType.P1P1)); + } + } + } + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/PhyrexianDreadnoughtTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/PhyrexianDreadnoughtTest.java new file mode 100644 index 00000000000..db3d1766d7a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/PhyrexianDreadnoughtTest.java @@ -0,0 +1,59 @@ +package org.mage.test.cards.single.mir; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.player.TestPlayer; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class PhyrexianDreadnoughtTest extends CardTestPlayerBase { + + /* + Phyrexian Dreadnought + {1} + Artifact Creature — Phyrexian Dreadnought + + Trample + + When this creature enters, sacrifice it unless you sacrifice any number of creatures with total power 12 or greater. + + 12/12 + */ + private static final String phyrexianDreadnought = "Phyrexian Dreadnought"; + + @Test + public void testPhyrexianDreadnoughtCanPay() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, phyrexianDreadnought); + addCard(Zone.BATTLEFIELD, playerA, phyrexianDreadnought + "@sacTarget"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, phyrexianDreadnought); + setChoice(playerA, true); + setChoice(playerA, "@sacTarget"); + setChoice(playerA, TestPlayer.CHOICE_SKIP); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, phyrexianDreadnought, 1); + assertGraveyardCount(playerA, phyrexianDreadnought, 1); + } + + @Test + public void testPhyrexianDreadnoughtCantPay() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, phyrexianDreadnought); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, phyrexianDreadnought); + + setStopAt(1, PhaseStep.END_TURN); + setChoice(playerA, false); + execute(); + + assertGraveyardCount(playerA, phyrexianDreadnought, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/neo/EaterOfVirtueTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/neo/EaterOfVirtueTest.java new file mode 100644 index 00000000000..6db049ab18b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/neo/EaterOfVirtueTest.java @@ -0,0 +1,54 @@ +package org.mage.test.cards.single.neo; + +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import java.util.Arrays; + + +public class EaterOfVirtueTest extends CardTestPlayerBase { + + /* + Eater of Virtue + {1} + Legendary Artifact — Equipment + + Whenever equipped creature dies, exile it. + + Equipped creature gets +2/+0. + + As long as a card exiled with Eater of Virtue has flying, equipped creature has flying. + The same is true for first strike, double strike, deathtouch, haste, hexproof, indestructible, lifelink, menace, protection, reach, trample, and vigilance. + + Equip {1} + */ + public static final String eaterOfVirtue = "Eater of Virtue"; + + @Test + public void testEaterOfVirtue() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, eaterOfVirtue); + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + addCard(Zone.BATTLEFIELD, playerA, "Adult Gold Dragon"); // Flying, Lifelink, Haste + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears"); + addCard(Zone.HAND, playerB, "Doom Blade"); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip", "Adult Gold Dragon"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Doom Blade", "Adult Gold Dragon"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Equip", "Balduvian Bears"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertAbilities(playerA, "Balduvian Bears", Arrays.asList(FlyingAbility.getInstance(), LifelinkAbility.getInstance(), HasteAbility.getInstance())); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/one/EncroachingMycosynthTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/one/EncroachingMycosynthTest.java new file mode 100644 index 00000000000..8646657de6b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/one/EncroachingMycosynthTest.java @@ -0,0 +1,58 @@ +package org.mage.test.cards.single.one; + +import mage.cards.Card; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommanderDuelBase; + +import java.util.List; + +import static org.junit.Assert.assertTrue; + + +public class EncroachingMycosynthTest extends CardTestCommanderDuelBase { + + /* + Encroaching Mycosynth + {3}{U} + Artifact + + Nonland permanents you control are artifacts in addition to their other types. + The same is true for permanent spells you control and nonland permanent cards you own that aren’t on the battlefield. + */ + private static final String encroachingMycosynth = "Encroaching Mycosynth"; + private static final String balduvianBears = "Balduvian Bears"; + @Test + public void testEncroachingMycosynth() { + setStrictChooseMode(true); + + addCard(Zone.GRAVEYARD, playerA, balduvianBears); + addCard(Zone.HAND, playerA, balduvianBears, 2); + addCard(Zone.BATTLEFIELD, playerA, balduvianBears); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, encroachingMycosynth); + addCard(Zone.EXILED, playerA, balduvianBears); + addCard(Zone.LIBRARY, playerA, balduvianBears); + addCard(Zone.COMMAND, playerA, balduvianBears); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, balduvianBears); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertType(balduvianBears, CardType.ARTIFACT, true); + List cards = getHandCards(playerA); + cards.addAll(getLibraryCards(playerA)); + cards.addAll(getCommandCards(playerA)); + cards.addAll(getExiledCards(playerA)); + cards.addAll(getLibraryCards(playerA)); + for (Card card : cards) { + if (!card.isLand(currentGame)) { + assertTrue(card.getCardType(currentGame).contains(CardType.ARTIFACT)); + } + } + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/one/NahirisSacrificeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/one/NahirisSacrificeTest.java new file mode 100644 index 00000000000..e69179635a4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/one/NahirisSacrificeTest.java @@ -0,0 +1,59 @@ +package org.mage.test.cards.single.one; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class NahirisSacrificeTest extends CardTestPlayerBase { + + /* + Nahiri's Sacrifice + {1}{R} + Sorcery + + As an additional cost to cast this spell, sacrifice an artifact or creature with mana value X. + + Nahiri’s Sacrifice deals X damage divided as you choose among any number of target creatures. + */ + private static final String nahirisSacrifice = "Nahiri's Sacrifice"; + private static final String balduvianBears = "Balduvian Bears"; + @Test + public void testNahirisSacrifice() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, nahirisSacrifice); + addCard(Zone.BATTLEFIELD, playerA, balduvianBears); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerB, balduvianBears + "@bearsB"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, nahirisSacrifice, "@bearsB"); + setChoice(playerA, "X=2"); + setChoice(playerA, balduvianBears); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, balduvianBears, 1); + assertGraveyardCount(playerA, nahirisSacrifice, 1); + } + + @Test + @Ignore // Enable after merging #13916 + public void testNahirisSacrificePrevented() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, nahirisSacrifice); + addCard(Zone.BATTLEFIELD, playerA, balduvianBears); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerB, balduvianBears + "@bearsB"); + addCard(Zone.BATTLEFIELD, playerB, "Yasharn, Implacable Earth"); + + checkPlayableAbility("Can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + nahirisSacrifice, false); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/ElsewhereFlaskTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/ElsewhereFlaskTest.java new file mode 100644 index 00000000000..40699057a95 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/shm/ElsewhereFlaskTest.java @@ -0,0 +1,57 @@ +package org.mage.test.cards.single.shm; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + + +public class ElsewhereFlaskTest extends CardTestPlayerBase { + + /* + Elsewhere Flask + {2} + Artifact + + When this artifact enters, draw a card. + + Sacrifice this artifact: Choose a basic land type. Each land you control becomes that type until end of turn. + */ + private static final String elsewhereFlask = "Elsewhere Flask"; + + @Test + public void testElsewhereFlask() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, elsewhereFlask); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice"); + setChoice(playerA, "Swamp"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertType("Island", CardType.LAND, SubType.SWAMP); + assertType("Forest", CardType.LAND, SubType.SWAMP); + } + + @Test + public void testElsewhereFlask2() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, elsewhereFlask); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice"); + setChoice(playerA, "Swamp"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertType("Island", CardType.LAND, SubType.ISLAND); + assertType("Forest", CardType.LAND, SubType.FOREST); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/StalwartSuccessorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/StalwartSuccessorTest.java new file mode 100644 index 00000000000..343dcf899fc --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/StalwartSuccessorTest.java @@ -0,0 +1,48 @@ +package org.mage.test.cards.single.tdm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class StalwartSuccessorTest extends CardTestPlayerBase { + + /* + Stalwart Successor + {1}{B}{G} + Creature — Human Warrior + + Menace (This creature can’t be blocked except by two or more creatures.) + + Whenever one or more counters are put on a creature you control, if it’s the first time counters have been put on that creature this turn, put a +1/+1 counter on that creature. + + 3/2 + */ + private static final String stalwartSuccessor = "Stalwart Successor"; + /* + Purestrain Genestealer + {2}{G} + Creature — Tyranid + + This creature enters with two +1/+1 counters on it. + + Vanguard Species — Whenever this creature attacks, you may remove a +1/+1 counter from it. If you do, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. + + 1/1 + */ + private static final String purestrainGenestealer = "Purestrain Genestealer"; + @Test + public void testStalwartSuccessor() { + + addCard(Zone.BATTLEFIELD, playerA, stalwartSuccessor); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.HAND, playerA, purestrainGenestealer); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, purestrainGenestealer); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, purestrainGenestealer, CounterType.P1P1, 2 + 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/AlurenTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/AlurenTest.java new file mode 100644 index 00000000000..b1ded1979b0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/AlurenTest.java @@ -0,0 +1,45 @@ +package org.mage.test.cards.single.tmp; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class AlurenTest extends CardTestPlayerBase { + + /* + Aluren + {2}{G}{G} + Enchantment + Any player may cast creature spells with mana value 3 or less without paying their mana costs and as though they had flash. + */ + private static final String aluren = "Aluren"; + + /* + Bear Cub + {1}{G} + Creature — Bear + 2/2 + */ + private static final String cub = "Bear Cub"; + + @Test + public void testAluren() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, aluren); + addCard(Zone.HAND, playerA, cub); + addCard(Zone.HAND, playerB, cub); + + castSpell(1, PhaseStep.UPKEEP, playerA, cub); + setChoice(playerA, "Cast without paying its mana cost"); + + castSpell(1, PhaseStep.END_TURN, playerB, cub); + setChoice(playerB, "Cast without paying its mana cost"); + + setStopAt(2, PhaseStep.UPKEEP); + execute(); + + assertPermanentCount(playerA, cub, 1); + assertPermanentCount(playerB, cub, 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/ExcavatorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/ExcavatorTest.java new file mode 100644 index 00000000000..7ee1daedf6e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/ExcavatorTest.java @@ -0,0 +1,54 @@ +package org.mage.test.cards.single.tmp; + +import mage.abilities.keyword.LandwalkAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class ExcavatorTest extends CardTestPlayerBase { + + /* + Excavator + {2} + Artifact + + {T}, Sacrifice a basic land: Target creature gains landwalk of each of the land types of the sacrificed land until end of turn. + (It can’t be blocked as long as defending player controls a land of any of those types.) + */ + public static final String excavator = "Excavator"; + + /* + Leyline of the Guildpact + {G/W}{G/U}{B/G}{R/G} + Enchantment + + If this card is in your opening hand, you may begin the game with it on the battlefield. + + Each nonland permanent you control is all colors. + + Lands you control are every basic land type in addition to their other types. + */ + public static final String leylineOfTheGuildpact = "Leyline of the Guildpact"; + + @Test + @Ignore("Failing because permanent LKI does not save MageObjectAttribute values") + public void testExcavator() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, excavator); + addCard(Zone.BATTLEFIELD, playerA, leylineOfTheGuildpact); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}"); + setChoice(playerA, "Island"); + addTarget(playerA, "Balduvian Bears"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertAbilityCount(playerA, "Balduvian Bears", LandwalkAbility.class, 5); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/who/EverybodyLivesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/EverybodyLivesTest.java new file mode 100644 index 00000000000..c1cd4693912 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/EverybodyLivesTest.java @@ -0,0 +1,95 @@ +package org.mage.test.cards.single.who; + +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.LoseGameSourceControllerEffect; +import mage.abilities.effects.common.LoseLifeAllPlayersEffect; +import mage.abilities.effects.common.WinGameSourceControllerEffect; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander4Players; + +public class EverybodyLivesTest extends CardTestCommander4Players { + + /* + Everybody Lives! + {1}{W} + Instant + + All creatures gain hexproof and indestructible until end of turn. Players gain hexproof until end of turn. + Players can’t lose life this turn and players can’t lose the game or win the game this turn. + */ + private static final String everybodyLives = "Everybody Lives!"; + + @Test + public void testEverybodyLivesCantLoseLifeAndHexproof() { + setStrictChooseMode(true); + + addCustomCardWithAbility("lose life effect", playerA, new SimpleActivatedAbility( + new LoseLifeAllPlayersEffect(20), + new ManaCostsImpl<>("")) + ); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.HAND, playerA, everybodyLives); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, everybodyLives, true); + checkPlayableAbility("Can't cast lightning bolt", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", false); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "each player"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + assertLife(playerC, 20); + assertLife(playerD, 20); + } + + @Test + public void testEverybodyLivesCantLoseGame() { + setStrictChooseMode(true); + + addCustomCardWithAbility("lose game effect", playerA, new SimpleActivatedAbility( + new LoseGameSourceControllerEffect(), + new ManaCostsImpl<>("")) + ); + addCard(Zone.HAND, playerA, everybodyLives); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, everybodyLives, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "you lose"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertHasNotLostTheGame(playerA); + + } + + @Test + public void testEverybodyLivesCantWinGame() { + setStrictChooseMode(true); + + addCustomCardWithAbility("win game effect", playerA, new SimpleActivatedAbility( + new WinGameSourceControllerEffect(), + new ManaCostsImpl<>("")) + ); + addCard(Zone.HAND, playerA, everybodyLives); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, everybodyLives, true); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "you win"); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertHasNotWonTheGame(playerA); + + } + +} diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index d205547d0a7..ebebe6c5903 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -2079,6 +2079,7 @@ public class VerifyCardDataTest { } } } + private void checkSubtypes(Card card, MtgJsonCard ref) { if (skipListHaveName(SKIP_LIST_SUBTYPE, card.getExpansionSetCode(), card.getName())) { return; @@ -2343,7 +2344,11 @@ public class VerifyCardDataTest { // search and check dies related abilities - String rules = triggeredAbility.getRule(); + // remove reminder text + String rules = triggeredAbility + .getRule() + .replaceAll("(?i) \\(.+\\)", "") + .replaceAll("(?i) \\(.+\\)", ""); if (ignoredAbilities.stream().anyMatch(rules::contains)) { continue; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java index 84721c6224f..2df5d3a11cd 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DamageWithExcessEffect.java @@ -6,9 +6,11 @@ import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; +import mage.game.Controllable; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; + +import java.util.Optional; /** * @author TheElk801 @@ -47,12 +49,12 @@ public class DamageWithExcessEffect extends OneShotEffect { return false; } int damage = amount.calculate(game, source, this); - int lethal = permanent.getLethalDamage(source.getSourceId(), game); - lethal = Math.min(lethal, damage); - permanent.damage(lethal, source.getSourceId(), source, game); - Player player = game.getPlayer(permanent.getControllerId()); - if (player != null && lethal < damage) { - player.damage(damage - lethal, source.getSourceId(), source, game); + int excess = permanent.damageWithExcess(damage, source, game); + if (excess > 0) { + Optional.ofNullable(permanent) + .map(Controllable::getControllerId) + .map(game::getPlayer) + .ifPresent(player -> player.damage(excess, source, game)); } return true; } diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index 1447879b362..73075f8f663 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -8,6 +8,7 @@ import mage.constants.Zone; import mage.game.Controllable; import mage.game.Game; import mage.game.GameState; +import mage.util.CardUtil; import java.util.List; import java.util.Set; @@ -177,6 +178,23 @@ public interface Permanent extends Card, Controllable { int getLethalDamage(UUID attackerId, Game game); + /** + * Same arguments as regular damage method, but returns the amount of excess damage dealt instead + * + * @return + */ + default int damageWithExcess(int damage, Ability source, Game game) { + return this.damageWithExcess(damage, source.getSourceId(), source, game); + } + + default int damageWithExcess(int damage, UUID attackerId, Ability source, Game game) { + int lethal = getLethalDamage(attackerId, game); + int excess = Math.max(CardUtil.overflowDec(damage, lethal), 0); + int dealt = Math.min(lethal, damage); + this.damage(dealt, attackerId, source, game); + return excess; + } + void removeAllDamage(Game game); void reset(Game game); diff --git a/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java b/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java index 3b5b3461715..3def0cb8984 100644 --- a/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java +++ b/Mage/src/main/java/mage/target/common/TargetCardAndOrCardInLibrary.java @@ -38,14 +38,6 @@ public class TargetCardAndOrCardInLibrary extends TargetCardInLibrary { private final PredicateCardAssignment assignment; - /** - * a [firstType] card and/or a [secondType] card - */ - protected TargetCardAndOrCardInLibrary(Predicate firstPredicate, Predicate secondPredicate, String filterText) { - super(0, 2, makeFilter(firstPredicate, secondPredicate, filterText)); - this.assignment = new PredicateCardAssignment(firstPredicate, secondPredicate); - } - public TargetCardAndOrCardInLibrary(CardType firstType, CardType secondType) { this(firstType.getPredicate(), secondType.getPredicate(), makeFilterText( CardUtil.getTextWithFirstCharLowerCase(firstType.toString()), @@ -60,6 +52,14 @@ public class TargetCardAndOrCardInLibrary extends TargetCardInLibrary { this(firstType.getPredicate(), secondType.getPredicate(), makeFilterText(firstType.getDescription(), secondType.getDescription())); } + /** + * a [firstType] card and/or a [secondType] card + */ + public TargetCardAndOrCardInLibrary(Predicate firstPredicate, Predicate secondPredicate, String filterText) { + super(0, 2, makeFilter(firstPredicate, secondPredicate, filterText)); + this.assignment = new PredicateCardAssignment(firstPredicate, secondPredicate); + } + protected TargetCardAndOrCardInLibrary(final TargetCardAndOrCardInLibrary target) { super(target); this.assignment = target.assignment; diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 9c8920538a8..e2c195fa7b8 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -59737,7 +59737,7 @@ Flexible Waterbender|Avatar: The Last Airbender|50|C|{3}{U}|Creature - Human War Geyser Leaper|Avatar: The Last Airbender|52|C|{4}{U}|Creature - Human Warrior Ally|4|3|Flying$Waterbend {4}: Draw a card, then discard a card.| Giant Koi|Avatar: The Last Airbender|53|C|{4}{U}{U}|Creature - Fish|5|7|Waterbend {3}: This creature can't be blocked this turn.$Islandcycling {2}| Iguana Parrot|Avatar: The Last Airbender|56|C|{2}{U}|Creature - Lizard Bird Pirate|2|2|Flying, vigilance$Prowess| -It'll Quench Ya!|Avatar: The Last Airbender|58|C|{1}{U}|Instant - Lesson|||Counter target spell unless its controller pays 2.| +It'll Quench Ya!|Avatar: The Last Airbender|58|C|{1}{U}|Instant - Lesson|||Counter target spell unless its controller pays {2}.| Katara, Bending Prodigy|Avatar: The Last Airbender|59|U|{2}{U}|Legendary Creature - Human Warrior Ally|2|3|At the beginning of your end step, if Katara is tapped, put a +1/+1 counter on her.$Waterbend {6}: Draw a card.| Master Pakku|Avatar: The Last Airbender|63|U|{1}{U}|Legendary Creature - Human Advisor Ally|1|3|Prowess$Whenever Master Pakku becomes tapped, target player mills X cards, where X is the number of Lesson cards in your graveyard.| Otter-Penguin|Avatar: The Last Airbender|67|C|{1}{U}|Creature - Otter Bird|2|1|Whenever you draw your second card each turn, this creature gets +1/+2 until end of turn and can't be blocked this turn.| @@ -59804,6 +59804,11 @@ Toph, the First Metalbender|Avatar: The Last Airbender|247|R|{1}{R}{G}{W}|Legend Vindictive Warden|Avatar: The Last Airbender|249|C|{2}{B/R}|Creature - Human Soldier|2|3|Menace$Firebending 1${3}: This creature deals 1 damage to each opponent.| Barrels of Blasting Jelly|Avatar: The Last Airbender|254|C|{1}|Artifact|||{1}: Add one mana of any color. Activate only once each turn.${5}, {T}, Sacrifice this artifact: It deals 5 damage to target creature.| Bender's Waterskin|Avatar: The Last Airbender|255|C|{3}|Artifact|||Untap this artifact during each other player's untap step.${T}: Add one mana of any color.| +Plains|Avatar: The Last Airbender|282|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Avatar: The Last Airbender|283|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Avatar: The Last Airbender|284|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Avatar: The Last Airbender|285|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Avatar: The Last Airbender|286|C||Basic Land - Forest|||({T}: Add {G}.)| Plains|Avatar: The Last Airbender|287|C||Basic Land - Plains|||({T}: Add {W}.)| Island|Avatar: The Last Airbender|288|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|Avatar: The Last Airbender|289|C||Basic Land - Swamp|||({T}: Add {B}.)| @@ -59825,6 +59830,7 @@ Katara, the Fearless|Avatar: The Last Airbender|361|R|{G}{W}{U}|Legendary Creatu Toph, the First Metalbender|Avatar: The Last Airbender|362|R|{1}{R}{G}{W}|Legendary Creature - Human Warrior Ally|3|3|Nontoken artifacts you control are lands in addition to their other types.$At the beginning of your end step, earthbend 2.| Avatar Aang|Avatar: The Last Airbender|363|M|{R}{G}{W}{U}|Legendary Creature - Human Avatar Ally|4|4|Flying, firebending 2$Whenever you waterbend, earthbend, firebend, or airbend, draw a card. Then if you've done all four this turn, transform Avatar Aang.| Aang, Master of Elements|Avatar: The Last Airbender|363|M||Legendary Creature - Avatar Ally|6|6|Flying$Spells you cast cost {W}{U}{B}{R}{G} less to cast.$At the beginning of each upkeep, you may transform Aang, Master of Elements. If you do, you gain 4 life, draw four cards, put four +1/+1 counters on him, and he deals 4 damage to each opponent.| +Hakoda, Selfless Commander|Avatar: The Last Airbender|366|R|{3}{W}|Legendary Creature - Human Warrior Ally|3|5|Vigilance$You may look at the top card of your library any time.$You may cast Ally spells from the top of your library.$Sacrifice Hakoda: Creatures you control get +0/+5 and gain indestructible until end of turn.| Sokka, Bold Boomeranger|Avatar: The Last Airbender|383|R|{U}{R}|Legendary Creature - Human Warrior Ally|1|1|When Sokka enters, discard up to two cards, then draw that many cards.$Whenever you cast an artifact or Lesson spell, put a +1/+1 counter on Sokka.| Anti-Venom, Horrifying Healer|Marvel's Spider-Man|1|M|{W}{W}{W}{W}{W}|Legendary Creature - Symbiote Hero|5|5|When Anti-Venom enters, if he was cast, return target creature card from your graveyard to the battlefield.$If damage would be dealt to Anti-Venom, prevent that damage and put that many +1/+1 counters on him.| Aunt May|Marvel's Spider-Man|3|U|{W}|Legendary Creature - Human Citizen|0|2|Whenever another creature you control enters, you gain 1 life. If it's a Spider, put a +1/+1 counter on it.| @@ -59940,7 +59946,7 @@ Aang, Air Nomad|Avatar: The Last Airbender Eternal|210|R|{3}{W}{W}|Legendary Cre Aang's Defense|Avatar: The Last Airbender Eternal|211|C|{W}|Instant|||Target blocking creature you control gets +2/+2 until end of turn.$Draw a card.| Aardvark Sloth|Avatar: The Last Airbender Eternal|212|C|{3}{W}|Creature - Sloth Beast|3|3|Lifelink| Allied Teamwork|Avatar: The Last Airbender Eternal|213|R|{2}{W}|Enchantment|||When this enchantment enters, create a 1/1 white Ally creature token.$Allies you control get +1/+1.| -Appa, Aang's Companion|Avatar: The Last Airbender Eternal|214|U|{3}{W}|Legendary Creature - Bison Ally|2|4|Flying$Whenever Appa attacks, another target attacking creature without flying gains flying until until end of turn.| +Appa, Aang's Companion|Avatar: The Last Airbender Eternal|214|U|{3}{W}|Legendary Creature - Bison Ally|2|4|Flying$Whenever Appa attacks, another target attacking creature without flying gains flying until end of turn.| Katara, Heroic Healer|Avatar: The Last Airbender Eternal|215|U|{4}{W}|Legendary Creature - Human Warrior Ally|2|3|Lifelink$When Katara enters, put a +1/+1 counter on each other creature you control.| Kyoshi Warrior Guard|Avatar: The Last Airbender Eternal|216|C|{1}{W}|Creature - Human Warrior Ally|2|3|| Momo, Rambunctious Rascal|Avatar: The Last Airbender Eternal|217|U|{2}{W}|Legendary Creature - Lemur Bat Ally|1|1|Flying$When Momo enters, he deals 4 damage to target tapped creature an opponent controls.| @@ -59991,3 +59997,43 @@ Thriving Grove|Avatar: The Last Airbender Eternal|261|C||Land|||This land enters Thriving Heath|Avatar: The Last Airbender Eternal|262|C||Land|||This land enters tapped. As it enters, choose a color other than white.${T}: Add {W} or one mana of the chosen color.| Thriving Isle|Avatar: The Last Airbender Eternal|263|C||Land|||This land enters tapped. As it enters, choose a color other than blue.${T}: Add {U} or one mana of the chosen color.| Thriving Moor|Avatar: The Last Airbender Eternal|264|C||Land|||This land enters tapped. As it enters, choose a color other than black.${T}: Add {B} or one mana of the chosen color.| +Aang, Air Nomad|Avatar: The Last Airbender Eternal|265|R|{3}{W}{W}|Legendary Creature - Human Avatar Ally|5|4|Flying$Vigilance$Other creatures you control have vigilance.| +Aang's Defense|Avatar: The Last Airbender Eternal|266|C|{W}|Instant|||Target blocking creature you control gets +2/+2 until end of turn.$Draw a card.| +Aardvark Sloth|Avatar: The Last Airbender Eternal|267|C|{3}{W}|Creature - Sloth Beast|3|3|Lifelink| +Appa, Aang's Companion|Avatar: The Last Airbender Eternal|268|U|{3}{W}|Legendary Creature - Bison Ally|2|4|Flying$Whenever Appa attacks, another target attacking creature without flying gains flying until end of turn.| +Katara, Heroic Healer|Avatar: The Last Airbender Eternal|269|U|{4}{W}|Legendary Creature - Human Warrior Ally|2|3|Lifelink$When Katara enters, put a +1/+1 counter on each other creature you control.| +Momo, Rambunctious Rascal|Avatar: The Last Airbender Eternal|270|U|{2}{W}|Legendary Creature - Lemur Bat Ally|1|1|Flying$When Momo enters, he deals 4 damage to target tapped creature an opponent controls.| +Path to Redemption|Avatar: The Last Airbender Eternal|271|C|{1}{W}|Enchantment - Aura|||Enchant creature$Enchanted creature can't attack or block.${5}, Sacrifice this Aura: Exile enchanted creature. Create a 1/1 white Ally creature token. Activate only during your turn.| +Razor Rings|Avatar: The Last Airbender Eternal|272|C|{1}{W}|Instant|||Razor Rings deals 4 damage to target attacking or blocking creature. You gain life equal to the excess damage dealt this way.| +Sledding Otter-Penguin|Avatar: The Last Airbender Eternal|273|C|{2}{W}|Creature - Otter Bird|2|3|{3}: Put a +1/+1 counter on this creature.| +Sokka, Wolf Cove's Protector|Avatar: The Last Airbender Eternal|274|U|{2}{W}|Legendary Creature - Human Warrior Ally|3|3|Vigilance| +Tundra Wall|Avatar: The Last Airbender Eternal|275|C|{1}{W}|Creature - Wall|0|4|Defender| +Wolf Cove Villager|Avatar: The Last Airbender Eternal|276|C|{W}|Creature - Human Peasant|2|2|This creature enters tapped.| +Capital Guard|Avatar: The Last Airbender Eternal|277|C|{1}{R}|Creature - Human Soldier|2|2|| +Dragon Moose|Avatar: The Last Airbender Eternal|278|C|{3}{R}|Creature - Dragon Elk|3|3|Haste| +Explosive Shot|Avatar: The Last Airbender Eternal|279|C|{1}{R}|Sorcery|||Explosive Shot deals 4 damage to target creature.| +Fire Nation Soldier|Avatar: The Last Airbender Eternal|280|C|{2}{R}|Creature - Human Soldier|3|2|Haste| +Fire Nation's Conquest|Avatar: The Last Airbender Eternal|281|U|{2}{R}|Enchantment|||Creatures you control get +1/+0.| +Iroh, Firebending Instructor|Avatar: The Last Airbender Eternal|282|U|{2}{R}|Legendary Creature - Human Noble Ally|2|2|Whenever Iroh attacks, attacking creatures get +1/+1 until end of turn.| +Komodo Rhino|Avatar: The Last Airbender Eternal|283|C|{3}{R}|Creature - Lizard Rhino|5|2|Trample| +Run Amok|Avatar: The Last Airbender Eternal|284|C|{1}{R}|Instant|||Target attacking creature gets +3/+3 and gains trample until end of turn.| +Warship Scout|Avatar: The Last Airbender Eternal|285|C|{R}|Creature - Human Scout|2|1|| +Zhao, the Seething Flame|Avatar: The Last Airbender Eternal|286|U|{4}{R}|Legendary Creature - Human Soldier|5|5|Menace| +Zuko, Avatar Hunter|Avatar: The Last Airbender Eternal|287|R|{3}{R}{R}|Legendary Creature - Human Noble|4|5|Reach$Whenever you cast a red spell, create a 2/2 red Soldier creature token.| +Zuko's Offense|Avatar: The Last Airbender Eternal|288|C|{R}|Sorcery|||Zuko's Offense deals 2 damage to any target.| +Mountain|Avatar: The Last Airbender Eternal|289|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|290|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|291|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|292|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|293|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|294|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|295|C||Basic Land - Mountain|||({T}: Add {R}.)| +Mountain|Avatar: The Last Airbender Eternal|296|C||Basic Land - Mountain|||({T}: Add {R}.)| +Plains|Avatar: The Last Airbender Eternal|297|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|298|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|299|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|300|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|301|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|302|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|303|C||Basic Land - Plains|||({T}: Add {W}.)| +Plains|Avatar: The Last Airbender Eternal|304|C||Basic Land - Plains|||({T}: Add {W}.)|