From 2bf680fb2a62032efd4c0311e929b0dc1ebc38d0 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 10:11:44 -0400 Subject: [PATCH 01/59] [TDM] Implement Elspeth, Storm Slayer --- .../src/mage/cards/e/ElspethStormSlayer.java | 74 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 75 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/ElspethStormSlayer.java diff --git a/Mage.Sets/src/mage/cards/e/ElspethStormSlayer.java b/Mage.Sets/src/mage/cards/e/ElspethStormSlayer.java new file mode 100644 index 00000000000..cbf61ff0f3a --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElspethStormSlayer.java @@ -0,0 +1,74 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.effects.common.replacement.CreateTwiceThatManyTokensEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterOpponentsCreaturePermanent; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.permanent.token.SoldierToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ElspethStormSlayer extends CardImpl { + + private static final FilterPermanent filter + = new FilterOpponentsCreaturePermanent("creature an opponent controls with mana value 3 or greater"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.MORE_THAN, 2)); + } + + public ElspethStormSlayer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{3}{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ELSPETH); + this.setStartingLoyalty(5); + + // If one or more tokens would be created under your control, twice that many of those tokens are created instead. + this.addAbility(new SimpleStaticAbility(new CreateTwiceThatManyTokensEffect())); + + // +1: Create a 1/1 white Soldier creature token. + this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new SoldierToken()), 1)); + + // 0: Put a +1/+1 counter on each creature you control. Those creatures gain flying until your next turn. + Ability ability = new LoyaltyAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE + ), 0); + ability.addEffect(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_CONTROLLED_CREATURE + ).setText("those creatures gain flying until end of turn")); + this.addAbility(ability); + + // -3: Destroy target creature an opponent controls with mana value 3 or greater. + ability = new LoyaltyAbility(new DestroyTargetEffect(), -3); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability); + } + + private ElspethStormSlayer(final ElspethStormSlayer card) { + super(card); + } + + @Override + public ElspethStormSlayer copy() { + return new ElspethStormSlayer(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 9371cf4d2d0..98cf94b8163 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -78,6 +78,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Dragonstorm Globe", 241, Rarity.COMMON, mage.cards.d.DragonstormGlobe.class)); cards.add(new SetCardInfo("Dusyut Earthcarver", 141, Rarity.COMMON, mage.cards.d.DusyutEarthcarver.class)); cards.add(new SetCardInfo("Duty Beyond Death", 10, Rarity.UNCOMMON, mage.cards.d.DutyBeyondDeath.class)); + cards.add(new SetCardInfo("Elspeth, Storm Slayer", 11, Rarity.MYTHIC, mage.cards.e.ElspethStormSlayer.class)); cards.add(new SetCardInfo("Embermouth Sentinel", 242, Rarity.COMMON, mage.cards.e.EmbermouthSentinel.class)); cards.add(new SetCardInfo("Encroaching Dragonstorm", 142, Rarity.UNCOMMON, mage.cards.e.EncroachingDragonstorm.class)); cards.add(new SetCardInfo("Equilibrium Adept", 106, Rarity.UNCOMMON, mage.cards.e.EquilibriumAdept.class)); From c8504322f1d6636863adbc2a36bffecc9cab9097 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 10:15:34 -0400 Subject: [PATCH 02/59] [TDM] Implement Essence Anchor --- Mage.Sets/src/mage/cards/e/EssenceAnchor.java | 64 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 65 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/EssenceAnchor.java diff --git a/Mage.Sets/src/mage/cards/e/EssenceAnchor.java b/Mage.Sets/src/mage/cards/e/EssenceAnchor.java new file mode 100644 index 00000000000..503512b1615 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EssenceAnchor.java @@ -0,0 +1,64 @@ +package mage.cards.e; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateIfConditionActivatedAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.keyword.SurveilEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.Game; +import mage.game.permanent.token.ZombieDruidToken; +import mage.watchers.common.CardsLeftGraveyardWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EssenceAnchor extends CardImpl { + + public EssenceAnchor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{U}"); + + // At the beginning of your upkeep, surveil 1. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new SurveilEffect(1))); + + // {T}: Create a 2/2 black Zombie Druid creature token. Activate only during your turn and only if a card left your graveyard this turn. + this.addAbility(new ActivateIfConditionActivatedAbility( + new CreateTokenEffect(new ZombieDruidToken()), + new TapSourceCost(), EssenceAnchorCondition.instance + ), new CardsLeftGraveyardWatcher()); + } + + private EssenceAnchor(final EssenceAnchor card) { + super(card); + } + + @Override + public EssenceAnchor copy() { + return new EssenceAnchor(this); + } +} + +enum EssenceAnchorCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game.isActivePlayer(source.getControllerId()) + && !game + .getState() + .getWatcher(CardsLeftGraveyardWatcher.class) + .getCardsThatLeftGraveyard(source.getControllerId(), game) + .isEmpty(); + } + + @Override + public String toString() { + return "during your turn and only if a card left your graveyard this turn"; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 98cf94b8163..b9039e2d8fe 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -82,6 +82,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Embermouth Sentinel", 242, Rarity.COMMON, mage.cards.e.EmbermouthSentinel.class)); cards.add(new SetCardInfo("Encroaching Dragonstorm", 142, Rarity.UNCOMMON, mage.cards.e.EncroachingDragonstorm.class)); cards.add(new SetCardInfo("Equilibrium Adept", 106, Rarity.UNCOMMON, mage.cards.e.EquilibriumAdept.class)); + cards.add(new SetCardInfo("Essence Anchor", 44, Rarity.UNCOMMON, mage.cards.e.EssenceAnchor.class)); cards.add(new SetCardInfo("Evolving Wilds", 255, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); cards.add(new SetCardInfo("Fangkeeper's Familiar", 183, Rarity.RARE, mage.cards.f.FangkeepersFamiliar.class)); cards.add(new SetCardInfo("Felothar, Dawn of the Abzan", 184, Rarity.RARE, mage.cards.f.FelotharDawnOfTheAbzan.class)); From 113d93aef4c11d33241c6873f9613020bab5556e Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 10:21:12 -0400 Subject: [PATCH 03/59] [TDM] Implement Frontline Rush --- Mage.Sets/src/mage/cards/f/FrontlineRush.java | 41 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 42 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FrontlineRush.java diff --git a/Mage.Sets/src/mage/cards/f/FrontlineRush.java b/Mage.Sets/src/mage/cards/f/FrontlineRush.java new file mode 100644 index 00000000000..4cab716fcb2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrontlineRush.java @@ -0,0 +1,41 @@ +package mage.cards.f; + +import mage.abilities.Mode; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.GoblinToken; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FrontlineRush extends CardImpl { + + public FrontlineRush(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}{W}"); + + // Choose one -- + // * Create two 1/1 red Goblin creature tokens. + this.getSpellAbility().addEffect(new CreateTokenEffect(new GoblinToken(), 2)); + + // * Target creature gets +X/+X until end of turn, where X is the number of creatures you control. + this.getSpellAbility().addMode(new Mode(new BoostTargetEffect( + CreaturesYouControlCount.instance, CreaturesYouControlCount.instance + )).addTarget(new TargetCreaturePermanent())); + } + + private FrontlineRush(final FrontlineRush card) { + super(card); + } + + @Override + public FrontlineRush copy() { + return new FrontlineRush(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index b9039e2d8fe..7c4c17cbdc3 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -92,6 +92,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Fortress Kin-Guard", 12, Rarity.COMMON, mage.cards.f.FortressKinGuard.class)); cards.add(new SetCardInfo("Fresh Start", 46, Rarity.UNCOMMON, mage.cards.f.FreshStart.class)); cards.add(new SetCardInfo("Frontier Bivouac", 256, Rarity.UNCOMMON, mage.cards.f.FrontierBivouac.class)); + cards.add(new SetCardInfo("Frontline Rush", 186, Rarity.UNCOMMON, mage.cards.f.FrontlineRush.class)); cards.add(new SetCardInfo("Glacial Dragonhunt", 188, Rarity.UNCOMMON, mage.cards.g.GlacialDragonhunt.class)); cards.add(new SetCardInfo("Great Arashin City", 257, Rarity.RARE, mage.cards.g.GreatArashinCity.class)); cards.add(new SetCardInfo("Gurmag Nightwatch", 190, Rarity.COMMON, mage.cards.g.GurmagNightwatch.class)); From 35d03e10f1b820329d0de565bae1b620fb443557 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 10:41:35 -0400 Subject: [PATCH 04/59] [TDM] Implement Trade Route Envoy --- .../src/mage/cards/t/TradeRouteEnvoy.java | 84 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 85 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TradeRouteEnvoy.java diff --git a/Mage.Sets/src/mage/cards/t/TradeRouteEnvoy.java b/Mage.Sets/src/mage/cards/t/TradeRouteEnvoy.java new file mode 100644 index 00000000000..28512ab25a8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TradeRouteEnvoy.java @@ -0,0 +1,84 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.CounterAnyPredicate; +import mage.game.Game; +import mage.players.Player; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TradeRouteEnvoy extends CardImpl { + + public TradeRouteEnvoy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.DOG); + this.subtype.add(SubType.SOLDIER); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // When this creature enters, draw a card if you control a creature with a counter on it. If you don't draw a card this way, put a +1/+1 counter on this creature. + this.addAbility(new EntersBattlefieldTriggeredAbility(new TradeRouteEnvoyEffect())); + } + + private TradeRouteEnvoy(final TradeRouteEnvoy card) { + super(card); + } + + @Override + public TradeRouteEnvoy copy() { + return new TradeRouteEnvoy(this); + } +} + +class TradeRouteEnvoyEffect extends OneShotEffect { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(CounterAnyPredicate.instance); + } + + TradeRouteEnvoyEffect() { + super(Outcome.Benefit); + staticText = "draw a card if you control a creature with a counter on it. " + + "If you don't draw a card this way, put a +1/+1 counter on this creature"; + } + + private TradeRouteEnvoyEffect(final TradeRouteEnvoyEffect effect) { + super(effect); + } + + @Override + public TradeRouteEnvoyEffect copy() { + return new TradeRouteEnvoyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (game.getBattlefield().contains(filter, source, game, 1)) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null && player.drawCards(1, source, game) > 0) { + return true; + } + } + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .ifPresent(permanent -> permanent.addCounters(CounterType.P1P1.createInstance(), source, game)); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 7c4c17cbdc3..e94ce50a506 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -196,6 +196,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Temur Tawnyback", 229, Rarity.COMMON, mage.cards.t.TemurTawnyback.class)); cards.add(new SetCardInfo("The Sibsig Ceremony", 91, Rarity.RARE, mage.cards.t.TheSibsigCeremony.class)); cards.add(new SetCardInfo("Thornwood Falls", 269, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class)); + cards.add(new SetCardInfo("Trade Route Envoy", 163, Rarity.COMMON, mage.cards.t.TradeRouteEnvoy.class)); cards.add(new SetCardInfo("Tranquil Cove", 270, Rarity.COMMON, mage.cards.t.TranquilCove.class)); cards.add(new SetCardInfo("Twin Bolt", 128, Rarity.COMMON, mage.cards.t.TwinBolt.class)); cards.add(new SetCardInfo("Ugin, Eye of the Storms", 1, Rarity.MYTHIC, mage.cards.u.UginEyeOfTheStorms.class)); From fdfe7c0d6e76b252386692eb6ec5482a76cb7cfb Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 11:48:40 -0400 Subject: [PATCH 05/59] [TDM] Implement Rainveil Rejuvenator --- .../src/mage/cards/r/RainveilRejuvenator.java | 46 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 47 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RainveilRejuvenator.java diff --git a/Mage.Sets/src/mage/cards/r/RainveilRejuvenator.java b/Mage.Sets/src/mage/cards/r/RainveilRejuvenator.java new file mode 100644 index 00000000000..c878316282e --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RainveilRejuvenator.java @@ -0,0 +1,46 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.mana.DynamicManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RainveilRejuvenator extends CardImpl { + + public RainveilRejuvenator(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.ELEPHANT); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // When this creature enters, you may mill three cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(3), true)); + + // {T}: Add an amount of {G} equal to this creature's power. + this.addAbility(new DynamicManaAbility( + Mana.GreenMana(1), SourcePermanentPowerValue.NOT_NEGATIVE, "Add an amount of {G} equal to {this}'s power." + )); + } + + private RainveilRejuvenator(final RainveilRejuvenator card) { + super(card); + } + + @Override + public RainveilRejuvenator copy() { + return new RainveilRejuvenator(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index e94ce50a506..46766fc91ac 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -144,6 +144,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Plains", 277, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Poised Practitioner", 18, Rarity.COMMON, mage.cards.p.PoisedPractitioner.class)); cards.add(new SetCardInfo("Qarsi Revenant", 86, Rarity.RARE, mage.cards.q.QarsiRevenant.class)); + cards.add(new SetCardInfo("Rainveil Rejuvenator", 152, Rarity.UNCOMMON, mage.cards.r.RainveilRejuvenator.class)); cards.add(new SetCardInfo("Rakshasa's Bargain", 214, Rarity.UNCOMMON, mage.cards.r.RakshasasBargain.class)); cards.add(new SetCardInfo("Rally the Monastery", 19, Rarity.UNCOMMON, mage.cards.r.RallyTheMonastery.class)); cards.add(new SetCardInfo("Rebellious Strike", 20, Rarity.COMMON, mage.cards.r.RebelliousStrike.class)); From 588b0f390f70f4e796df96a5b54cfcc5ec089706 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 14:02:01 -0400 Subject: [PATCH 06/59] [TDM] Implement Gurmag Rakshasa --- .../src/mage/cards/g/GurmagRakshasa.java | 51 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 52 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GurmagRakshasa.java diff --git a/Mage.Sets/src/mage/cards/g/GurmagRakshasa.java b/Mage.Sets/src/mage/cards/g/GurmagRakshasa.java new file mode 100644 index 00000000000..828278fe81f --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GurmagRakshasa.java @@ -0,0 +1,51 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GurmagRakshasa extends CardImpl { + + public GurmagRakshasa(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{B}"); + + this.subtype.add(SubType.DEMON); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Menace + this.addAbility(new MenaceAbility()); + + // When this creature enters, target creature an opponent controls gets -2/-2 until end of turn and target creature you control gets +2/+2 until end of turn. + Ability ability = new EntersBattlefieldTriggeredAbility(new BoostTargetEffect(-2, -2)); + ability.addTarget(new TargetOpponentsCreaturePermanent()); + ability.addEffect(new BoostTargetEffect(2, 2) + .setTargetPointer(new SecondTargetPointer()) + .concatBy("and")); + ability.addTarget(new TargetControlledCreaturePermanent()); + this.addAbility(ability); + } + + private GurmagRakshasa(final GurmagRakshasa card) { + super(card); + } + + @Override + public GurmagRakshasa copy() { + return new GurmagRakshasa(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 46766fc91ac..0f212b98028 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -96,6 +96,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Glacial Dragonhunt", 188, Rarity.UNCOMMON, mage.cards.g.GlacialDragonhunt.class)); cards.add(new SetCardInfo("Great Arashin City", 257, Rarity.RARE, mage.cards.g.GreatArashinCity.class)); cards.add(new SetCardInfo("Gurmag Nightwatch", 190, Rarity.COMMON, mage.cards.g.GurmagNightwatch.class)); + cards.add(new SetCardInfo("Gurmag Rakshasa", 81, Rarity.UNCOMMON, mage.cards.g.GurmagRakshasa.class)); cards.add(new SetCardInfo("Hardened Tactician", 191, Rarity.UNCOMMON, mage.cards.h.HardenedTactician.class)); cards.add(new SetCardInfo("Heritage Reclamation", 145, Rarity.COMMON, mage.cards.h.HeritageReclamation.class)); cards.add(new SetCardInfo("Humbling Elder", 48, Rarity.COMMON, mage.cards.h.HumblingElder.class)); From 4d1f154b13f88fa6c85bbedc16998b16d9cebb82 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 14:12:04 -0400 Subject: [PATCH 07/59] [TDM] Implement Rediscover the Way --- .../src/mage/cards/r/RediscoverTheWay.java | 86 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 87 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RediscoverTheWay.java diff --git a/Mage.Sets/src/mage/cards/r/RediscoverTheWay.java b/Mage.Sets/src/mage/cards/r/RediscoverTheWay.java new file mode 100644 index 00000000000..efab4b20360 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RediscoverTheWay.java @@ -0,0 +1,86 @@ +package mage.cards.r; + +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RediscoverTheWay extends CardImpl { + + public RediscoverTheWay(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}{R}{W}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I, II -- Look at the top three cards of your library. Put one of them into your hand and the rest on the bottom of your library in any order. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_II, + new LookLibraryAndPickControllerEffect(3, 1, PutCards.HAND, PutCards.BOTTOM_ANY) + ); + + // III -- Whenever you cast a noncreature spell this turn, target creature you control gains double strike until end of turn. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_III, + new CreateDelayedTriggeredAbilityEffect(new RediscoverTheWayTriggeredAbility()) + ); + this.addAbility(sagaAbility); + } + + private RediscoverTheWay(final RediscoverTheWay card) { + super(card); + } + + @Override + public RediscoverTheWay copy() { + return new RediscoverTheWay(this); + } +} + +class RediscoverTheWayTriggeredAbility extends DelayedTriggeredAbility { + + RediscoverTheWayTriggeredAbility() { + super(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance()), Duration.EndOfTurn, false, false); + this.addTarget(new TargetControlledCreaturePermanent()); + this.setTriggerPhrase("Whenever you cast a noncreature spell this turn, "); + } + + private RediscoverTheWayTriggeredAbility(final RediscoverTheWayTriggeredAbility ability) { + super(ability); + } + + @Override + public RediscoverTheWayTriggeredAbility copy() { + return new RediscoverTheWayTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!isControlledBy(event.getPlayerId())) { + return false; + } + Spell spell = game.getSpell(event.getTargetId()); + return spell != null && !spell.isCreature(game); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 0f212b98028..3c5f8ded306 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -149,6 +149,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Rakshasa's Bargain", 214, Rarity.UNCOMMON, mage.cards.r.RakshasasBargain.class)); cards.add(new SetCardInfo("Rally the Monastery", 19, Rarity.UNCOMMON, mage.cards.r.RallyTheMonastery.class)); cards.add(new SetCardInfo("Rebellious Strike", 20, Rarity.COMMON, mage.cards.r.RebelliousStrike.class)); + cards.add(new SetCardInfo("Rediscover the Way", 215, Rarity.RARE, mage.cards.r.RediscoverTheWay.class)); cards.add(new SetCardInfo("Reigning Victor", 216, Rarity.COMMON, mage.cards.r.ReigningVictor.class)); cards.add(new SetCardInfo("Reputable Merchant", 217, Rarity.COMMON, mage.cards.r.ReputableMerchant.class)); cards.add(new SetCardInfo("Rescue Leopard", 116, Rarity.COMMON, mage.cards.r.RescueLeopard.class)); From c044593efeb6e81ab2e102555f7f65c8c19547d4 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 14:15:30 -0400 Subject: [PATCH 08/59] [TDM] Implement Salt Road Skirmish --- .../src/mage/cards/s/SaltRoadSkirmish.java | 73 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 74 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SaltRoadSkirmish.java diff --git a/Mage.Sets/src/mage/cards/s/SaltRoadSkirmish.java b/Mage.Sets/src/mage/cards/s/SaltRoadSkirmish.java new file mode 100644 index 00000000000..5f8189291b5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SaltRoadSkirmish.java @@ -0,0 +1,73 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.token.RedWarriorToken; +import mage.game.permanent.token.Token; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SaltRoadSkirmish extends CardImpl { + + public SaltRoadSkirmish(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}"); + + // Destroy target creature. Create two 1/1 red Warrior creature tokens. They gain haste until end of turn. Sacrifice them at the beginning of the next end step. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new SaltRoadSkirmishEffect()); + } + + private SaltRoadSkirmish(final SaltRoadSkirmish card) { + super(card); + } + + @Override + public SaltRoadSkirmish copy() { + return new SaltRoadSkirmish(this); + } +} + +class SaltRoadSkirmishEffect extends OneShotEffect { + + SaltRoadSkirmishEffect() { + super(Outcome.Benefit); + staticText = ""; + } + + private SaltRoadSkirmishEffect(final SaltRoadSkirmishEffect effect) { + super(effect); + } + + @Override + public SaltRoadSkirmishEffect copy() { + return new SaltRoadSkirmishEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new RedWarriorToken(); + token.putOntoBattlefield(2, game, source); + game.addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance()) + .setTargetPointer(new FixedTargets(token, game)), source); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new SacrificeTargetEffect("sacrifice them").setTargetPointer(new FixedTargets(token, game)) + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 3c5f8ded306..281702bf3f3 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -163,6 +163,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Sage of the Fang", 155, Rarity.UNCOMMON, mage.cards.s.SageOfTheFang.class)); cards.add(new SetCardInfo("Sagu Pummeler", 156, Rarity.COMMON, mage.cards.s.SaguPummeler.class)); cards.add(new SetCardInfo("Salt Road Packbeast", 23, Rarity.COMMON, mage.cards.s.SaltRoadPackbeast.class)); + cards.add(new SetCardInfo("Salt Road Skirmish", 88, Rarity.UNCOMMON, mage.cards.s.SaltRoadSkirmish.class)); cards.add(new SetCardInfo("Sandskitter Outrider", 89, Rarity.COMMON, mage.cards.s.SandskitterOutrider.class)); cards.add(new SetCardInfo("Sandsteppe Citadel", 266, Rarity.UNCOMMON, mage.cards.s.SandsteppeCitadel.class)); cards.add(new SetCardInfo("Sarkhan's Resolve", 158, Rarity.COMMON, mage.cards.s.SarkhansResolve.class)); From f2ea83f73bf07e1b12485089d570bc0c455e86d9 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 16:16:40 -0400 Subject: [PATCH 09/59] update ban lists --- .../Mage.Deck.Constructed/src/mage/deck/Legacy.java | 2 ++ .../Mage.Deck.Constructed/src/mage/deck/Modern.java | 1 + .../Mage.Deck.Constructed/src/mage/deck/Pauper.java | 5 +++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Legacy.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Legacy.java index a0dfd0d541b..a75071e26b9 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Legacy.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Legacy.java @@ -64,6 +64,7 @@ public class Legacy extends Constructed { banned.add("Sensei's Divining Top"); banned.add("Skullclamp"); banned.add("Sol Ring"); + banned.add("Sowing Mycospawn"); banned.add("Strip Mine"); banned.add("Survival of the Fittest"); banned.add("Time Vault"); @@ -72,6 +73,7 @@ public class Legacy extends Constructed { banned.add("Tinker"); banned.add("Tolarian Academy"); banned.add("Treasure Cruise"); + banned.add("Troll of Khazad-dum"); banned.add("Underworld Breach"); banned.add("Vampiric Tutor"); banned.add("Vexing Bauble"); diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Modern.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Modern.java index f079385508e..cb7a5fa939c 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Modern.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Modern.java @@ -69,6 +69,7 @@ public class Modern extends Constructed { banned.add("Treasure Cruise"); banned.add("Tree of Tales"); banned.add("Umezawa's Jitte"); + banned.add("Underworld Breach"); banned.add("Up the Beanstalk"); banned.add("Uro, Titan of Nature's Wrath"); banned.add("Vault of Whispers"); diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Pauper.java b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Pauper.java index e7ec4d300ce..6dbba75643f 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Pauper.java +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/src/mage/deck/Pauper.java @@ -29,6 +29,7 @@ public class Pauper extends Constructed { banned.add("All That Glitters"); banned.add("Arcum's Astrolabe"); banned.add("Atog"); + banned.add("Basking Broodscale"); banned.add("Bonder's Ornament"); banned.add("Chatterstorm"); banned.add("Cloud of Faeries"); @@ -36,6 +37,7 @@ public class Pauper extends Constructed { banned.add("Cranial Plating"); banned.add("Cranial Ram"); banned.add("Daze"); + banned.add("Deadly Dispute"); banned.add("Disciple of the Vault"); banned.add("Empty the Warrens"); banned.add("Fall from Favor"); @@ -44,13 +46,12 @@ public class Pauper extends Constructed { banned.add("Gitaxian Probe"); banned.add("Grapeshot"); banned.add("Gush"); - banned.add("High Tide"); banned.add("Hymn to Tourach"); banned.add("Invigorate"); + banned.add("Kuldotha Rebirth"); banned.add("Monastery Swiftspear"); banned.add("Mystic Sanctuary"); banned.add("Peregrine Drake"); - banned.add("Prophetic Prism"); banned.add("Sinkhole"); banned.add("Stirring Bard"); banned.add("Sojourner's Companion"); From 1da557534eb4f8486a8d36173c715051d30540ef Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 16:21:41 -0400 Subject: [PATCH 10/59] [TDM] Implement Thunder of Unity --- .../src/mage/cards/t/ThunderOfUnity.java | 83 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 84 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/ThunderOfUnity.java diff --git a/Mage.Sets/src/mage/cards/t/ThunderOfUnity.java b/Mage.Sets/src/mage/cards/t/ThunderOfUnity.java new file mode 100644 index 00000000000..e1619ec66d4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThunderOfUnity.java @@ -0,0 +1,83 @@ +package mage.cards.t; + +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ThunderOfUnity extends CardImpl { + + public ThunderOfUnity(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}{W}{B}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- You draw two cards and you lose 2 life. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new DrawCardSourceControllerEffect(2, true), + new LoseLifeSourceControllerEffect(2).concatBy("and") + ); + + // II, III -- Whenever a creature you control enters this turn, each opponent loses 1 life and you gain 1 life. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_III, + new CreateDelayedTriggeredAbilityEffect(new ThunderOfUnityTriggeredAbility()) + ); + this.addAbility(sagaAbility); + } + + private ThunderOfUnity(final ThunderOfUnity card) { + super(card); + } + + @Override + public ThunderOfUnity copy() { + return new ThunderOfUnity(this); + } +} + +class ThunderOfUnityTriggeredAbility extends DelayedTriggeredAbility { + + ThunderOfUnityTriggeredAbility() { + super(new LoseLifeOpponentsEffect(1), Duration.EndOfTurn, false, false); + this.addEffect(new GainLifeEffect(1).concatBy("and")); + this.setTriggerPhrase("Whenever a creature you control enters this turn, "); + } + + private ThunderOfUnityTriggeredAbility(final ThunderOfUnityTriggeredAbility ability) { + super(ability); + } + + @Override + public ThunderOfUnityTriggeredAbility copy() { + return new ThunderOfUnityTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Permanent permanent = game.getPermanent(event.getTargetId()); + return permanent != null && permanent.isCreature(game) && permanent.isControlledBy(getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 281702bf3f3..c4938d72957 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -200,6 +200,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Temur Tawnyback", 229, Rarity.COMMON, mage.cards.t.TemurTawnyback.class)); cards.add(new SetCardInfo("The Sibsig Ceremony", 91, Rarity.RARE, mage.cards.t.TheSibsigCeremony.class)); cards.add(new SetCardInfo("Thornwood Falls", 269, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class)); + cards.add(new SetCardInfo("Thunder of Unity", 231, Rarity.RARE, mage.cards.t.ThunderOfUnity.class)); cards.add(new SetCardInfo("Trade Route Envoy", 163, Rarity.COMMON, mage.cards.t.TradeRouteEnvoy.class)); cards.add(new SetCardInfo("Tranquil Cove", 270, Rarity.COMMON, mage.cards.t.TranquilCove.class)); cards.add(new SetCardInfo("Twin Bolt", 128, Rarity.COMMON, mage.cards.t.TwinBolt.class)); From 19dfcc4e5c6a29800fda03f6f65193d664c7c5f9 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 16:34:50 -0400 Subject: [PATCH 11/59] [TDM] Implement Traveling Botanist --- .../src/mage/cards/t/TravelingBotanist.java | 88 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 89 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TravelingBotanist.java diff --git a/Mage.Sets/src/mage/cards/t/TravelingBotanist.java b/Mage.Sets/src/mage/cards/t/TravelingBotanist.java new file mode 100644 index 00000000000..31fac690a1c --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TravelingBotanist.java @@ -0,0 +1,88 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesTappedSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TravelingBotanist extends CardImpl { + + public TravelingBotanist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.DOG); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Whenever this creature becomes tapped, look at the top card of your library. If it's a land card, you may reveal it and put it into your hand. If you don't put the card into your hand, you may put it into your graveyard. + this.addAbility(new BecomesTappedSourceTriggeredAbility(new TravelingBotanistEffect())); + } + + private TravelingBotanist(final TravelingBotanist card) { + super(card); + } + + @Override + public TravelingBotanist copy() { + return new TravelingBotanist(this); + } +} + +class TravelingBotanistEffect extends OneShotEffect { + + TravelingBotanistEffect() { + super(Outcome.Benefit); + staticText = "look at the top card of your library. If it's a land card, you may reveal it and " + + "put it into your hand. If you don't put the card into your hand, you may put it into your graveyard"; + } + + private TravelingBotanistEffect(final TravelingBotanistEffect effect) { + super(effect); + } + + @Override + public TravelingBotanistEffect copy() { + return new TravelingBotanistEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = player.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + player.lookAtCards("Top card of library", card, game); + if (card.isLand(game) && player.chooseUse( + Outcome.DrawCard, "Reveal " + card.getName() + + " and put it into your hand?", source, game + )) { + player.revealCards(source, new CardsImpl(card), game); + player.moveCards(card, Zone.HAND, source, game); + return true; + } + if (player.chooseUse(Outcome.Neutral, "Put " + card.getName() + " into your graveyard?", source, game)) { + return player.moveCards(card, Zone.GRAVEYARD, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index c4938d72957..95a496a7951 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -203,6 +203,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Thunder of Unity", 231, Rarity.RARE, mage.cards.t.ThunderOfUnity.class)); cards.add(new SetCardInfo("Trade Route Envoy", 163, Rarity.COMMON, mage.cards.t.TradeRouteEnvoy.class)); cards.add(new SetCardInfo("Tranquil Cove", 270, Rarity.COMMON, mage.cards.t.TranquilCove.class)); + cards.add(new SetCardInfo("Traveling Botanist", 164, Rarity.UNCOMMON, mage.cards.t.TravelingBotanist.class)); cards.add(new SetCardInfo("Twin Bolt", 128, Rarity.COMMON, mage.cards.t.TwinBolt.class)); cards.add(new SetCardInfo("Ugin, Eye of the Storms", 1, Rarity.MYTHIC, mage.cards.u.UginEyeOfTheStorms.class)); cards.add(new SetCardInfo("Unburied Earthcarver", 95, Rarity.COMMON, mage.cards.u.UnburiedEarthcarver.class)); From 0758118014f05a9c36e99e20cbfb4ab68e4d6618 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 16:36:37 -0400 Subject: [PATCH 12/59] [TDM] Implement Veteran Ice Climber --- .../src/mage/cards/v/VeteranIceClimber.java | 51 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 52 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/v/VeteranIceClimber.java diff --git a/Mage.Sets/src/mage/cards/v/VeteranIceClimber.java b/Mage.Sets/src/mage/cards/v/VeteranIceClimber.java new file mode 100644 index 00000000000..e4057da3457 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VeteranIceClimber.java @@ -0,0 +1,51 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.keyword.CantBeBlockedSourceAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VeteranIceClimber extends CardImpl { + + public VeteranIceClimber(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // This creature can't be blocked. + this.addAbility(new CantBeBlockedSourceAbility()); + + // Whenever this creature attacks, up to one target player mills cards equal to this creature's power. + Ability ability = new AttacksTriggeredAbility(new MillCardsTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE)); + ability.addTarget(new TargetPlayer(0, 1, false)); + this.addAbility(ability); + } + + private VeteranIceClimber(final VeteranIceClimber card) { + super(card); + } + + @Override + public VeteranIceClimber copy() { + return new VeteranIceClimber(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 95a496a7951..a1d00e2c71e 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -215,6 +215,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Unsparing Boltcaster", 130, Rarity.UNCOMMON, mage.cards.u.UnsparingBoltcaster.class)); cards.add(new SetCardInfo("Ureni's Rebuff", 63, Rarity.UNCOMMON, mage.cards.u.UrenisRebuff.class)); cards.add(new SetCardInfo("Venerated Stormsinger", 97, Rarity.UNCOMMON, mage.cards.v.VeneratedStormsinger.class)); + cards.add(new SetCardInfo("Veteran Ice Climber", 64, Rarity.UNCOMMON, mage.cards.v.VeteranIceClimber.class)); cards.add(new SetCardInfo("Voice of Victory", 33, Rarity.RARE, mage.cards.v.VoiceOfVictory.class)); cards.add(new SetCardInfo("Watcher of the Wayside", 249, Rarity.COMMON, mage.cards.w.WatcherOfTheWayside.class)); cards.add(new SetCardInfo("Wayspeaker Bodyguard", 34, Rarity.UNCOMMON, mage.cards.w.WayspeakerBodyguard.class)); From ba4f51698d3cce66a08fa20c933533848bd8991b Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 16:40:12 -0400 Subject: [PATCH 13/59] [TDM] Implement War Effort --- Mage.Sets/src/mage/cards/w/WarEffort.java | 73 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 74 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/w/WarEffort.java diff --git a/Mage.Sets/src/mage/cards/w/WarEffort.java b/Mage.Sets/src/mage/cards/w/WarEffort.java new file mode 100644 index 00000000000..3c5b7ee0af5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WarEffort.java @@ -0,0 +1,73 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.token.RedWarriorToken; +import mage.game.permanent.token.Token; +import mage.target.targetpointer.FixedTargets; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WarEffort extends CardImpl { + + public WarEffort(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}"); + + // Creatures you control get +1/+0. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 0, Duration.WhileOnBattlefield))); + + // Whenever you attack, create a 1/1 red Warrior creature token that's tapped and attacking. Sacrifice it at the beginning of the next end step. + this.addAbility(new AttacksWithCreaturesTriggeredAbility(new WarEffortEffect(), 1)); + } + + private WarEffort(final WarEffort card) { + super(card); + } + + @Override + public WarEffort copy() { + return new WarEffort(this); + } +} + +class WarEffortEffect extends OneShotEffect { + + WarEffortEffect() { + super(Outcome.Benefit); + staticText = "create a 1/1 red Warrior creature token that's tapped and attacking. " + + "Sacrifice it at the beginning of the next end step"; + } + + private WarEffortEffect(final WarEffortEffect effect) { + super(effect); + } + + @Override + public WarEffortEffect copy() { + return new WarEffortEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Token token = new RedWarriorToken(); + token.putOntoBattlefield(1, game, source, source.getControllerId(), true, true); + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new SacrificeTargetEffect("sacrifice it").setTargetPointer(new FixedTargets(token, game)) + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index a1d00e2c71e..dc6cc22ae65 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -217,6 +217,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Venerated Stormsinger", 97, Rarity.UNCOMMON, mage.cards.v.VeneratedStormsinger.class)); cards.add(new SetCardInfo("Veteran Ice Climber", 64, Rarity.UNCOMMON, mage.cards.v.VeteranIceClimber.class)); cards.add(new SetCardInfo("Voice of Victory", 33, Rarity.RARE, mage.cards.v.VoiceOfVictory.class)); + cards.add(new SetCardInfo("War Effort", 131, Rarity.UNCOMMON, mage.cards.w.WarEffort.class)); cards.add(new SetCardInfo("Watcher of the Wayside", 249, Rarity.COMMON, mage.cards.w.WatcherOfTheWayside.class)); cards.add(new SetCardInfo("Wayspeaker Bodyguard", 34, Rarity.UNCOMMON, mage.cards.w.WayspeakerBodyguard.class)); cards.add(new SetCardInfo("Wild Ride", 132, Rarity.COMMON, mage.cards.w.WildRide.class)); From 08135af525f88dde96dd421907feb06672d702df Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 31 Mar 2025 16:47:22 -0400 Subject: [PATCH 14/59] [TDM] Implement Yathan Roadwatcher --- .../src/mage/cards/y/YathanRoadwatcher.java | 60 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 61 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/y/YathanRoadwatcher.java diff --git a/Mage.Sets/src/mage/cards/y/YathanRoadwatcher.java b/Mage.Sets/src/mage/cards/y/YathanRoadwatcher.java new file mode 100644 index 00000000000..c48e3de7af9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/y/YathanRoadwatcher.java @@ -0,0 +1,60 @@ +package mage.cards.y; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.condition.common.CastFromEverywhereSourceCondition; +import mage.abilities.costs.common.MillCardsCost; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class YathanRoadwatcher extends CardImpl { + + private static final FilterCard filter + = new FilterCreatureCard("creature card with mana value 3 or less from your graveyard"); + + static { + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4)); + } + + public YathanRoadwatcher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}{B}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When this creature enters, if you cast it, mill four cards. When you do, return target creature card with mana value 3 or less from your graveyard to the battlefield. + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new ReturnFromGraveyardToBattlefieldTargetEffect(), false + ); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoWhenCostPaid( + ability, new MillCardsCost(4), "", false + )).withInterveningIf(CastFromEverywhereSourceCondition.instance)); + } + + private YathanRoadwatcher(final YathanRoadwatcher card) { + super(card); + } + + @Override + public YathanRoadwatcher copy() { + return new YathanRoadwatcher(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index dc6cc22ae65..1a0c64cf5d8 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -226,6 +226,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Wingspan Stride", 66, Rarity.COMMON, mage.cards.w.WingspanStride.class)); cards.add(new SetCardInfo("Winternight Stories", 67, Rarity.RARE, mage.cards.w.WinternightStories.class)); cards.add(new SetCardInfo("Worthy Cost", 99, Rarity.COMMON, mage.cards.w.WorthyCost.class)); + cards.add(new SetCardInfo("Yathan Roadwatcher", 236, Rarity.RARE, mage.cards.y.YathanRoadwatcher.class)); cards.add(new SetCardInfo("Yathan Tombguard", 100, Rarity.UNCOMMON, mage.cards.y.YathanTombguard.class)); cards.add(new SetCardInfo("Zurgo's Vanguard", 133, Rarity.UNCOMMON, mage.cards.z.ZurgosVanguard.class)); cards.add(new SetCardInfo("Zurgo, Thunder's Decree", 237, Rarity.RARE, mage.cards.z.ZurgoThundersDecree.class)); From 730bd8e63d20fccbf92c359977bfe40bf6a6bd2b Mon Sep 17 00:00:00 2001 From: androosss <101566943+androosss@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:58:05 +0200 Subject: [PATCH 15/59] refactor: improved tokens structure (#13487) - removed duplicate tokens - fixed names of some tokens - corrected tokens used in tokens database --- .../src/mage/cards/c/CrushOfTentacles.java | 4 +- .../mage/cards/d/DanceOfTheTumbleweeds.java | 4 +- .../src/mage/cards/e/EyesOfTheWisent.java | 4 +- Mage.Sets/src/mage/cards/f/FleshCarver.java | 4 +- .../mage/cards/g/GrismoldTheDreadsower.java | 4 +- .../src/mage/cards/g/GrovetenderDruids.java | 4 +- .../src/mage/cards/i/InfernalGenesis.java | 4 +- .../src/mage/cards/m/MarathWillOfTheWild.java | 12 ++--- .../src/mage/cards/p/PhyrexianProcessor.java | 11 ++--- Mage.Sets/src/mage/cards/r/RallyTheHorde.java | 4 +- Mage.Sets/src/mage/cards/s/SeedGuardian.java | 4 +- .../src/mage/cards/s/SorinSolemnVisitor.java | 4 +- Mage.Sets/src/mage/cards/s/SpoilsOfBlood.java | 4 +- .../src/mage/cards/t/TumbleweedRising.java | 4 +- .../src/mage/cards/w/WalkerOfTheGrove.java | 4 +- .../token/CrushOfTentaclesToken.java | 29 ------------ .../game/permanent/token/DinDragonToken.java | 2 +- ...lToken.java => Elemental44GreenToken.java} | 10 ++--- ...nToken.java => ElementalXXGreenToken.java} | 12 ++--- .../token/GrovetenderDruidsPlantToken.java | 28 ------------ ...rrorToken.java => HorrorXXBlackToken.java} | 12 ++--- .../game/permanent/token/HumanRogueToken.java | 2 +- .../MarathWillOfTheWildElementalToken.java | 30 ------------- .../game/permanent/token/MinionToken.java | 15 +++---- .../game/permanent/token/MinionToken2.java | 28 ------------ .../token/NighteyesTheDesecratorToken.java | 44 ------------------- .../permanent/token/PhyrexianMinionToken.java | 33 ++++++++++++++ ...smoldPlantToken.java => Plant11Token.java} | 13 +++--- .../token/RallyTheHordeWarriorToken.java | 29 ------------ .../token/SorinSolemnVisitorVampireToken.java | 30 ------------- .../token/SpoilsOfBloodHorrorToken.java | 32 -------------- .../token/WalkerOfTheGroveToken.java | 29 ------------ Mage/src/main/resources/tokens-database.txt | 26 +++++------ 33 files changed, 112 insertions(+), 367 deletions(-) delete mode 100644 Mage/src/main/java/mage/game/permanent/token/CrushOfTentaclesToken.java rename Mage/src/main/java/mage/game/permanent/token/{EyesOfTheWisentElementalToken.java => Elemental44GreenToken.java} (59%) rename Mage/src/main/java/mage/game/permanent/token/{SeedGuardianToken.java => ElementalXXGreenToken.java} (60%) delete mode 100644 Mage/src/main/java/mage/game/permanent/token/GrovetenderDruidsPlantToken.java rename Mage/src/main/java/mage/game/permanent/token/{FleshCarverHorrorToken.java => HorrorXXBlackToken.java} (59%) delete mode 100644 Mage/src/main/java/mage/game/permanent/token/MarathWillOfTheWildElementalToken.java delete mode 100644 Mage/src/main/java/mage/game/permanent/token/MinionToken2.java delete mode 100644 Mage/src/main/java/mage/game/permanent/token/NighteyesTheDesecratorToken.java create mode 100644 Mage/src/main/java/mage/game/permanent/token/PhyrexianMinionToken.java rename Mage/src/main/java/mage/game/permanent/token/{GrismoldPlantToken.java => Plant11Token.java} (62%) delete mode 100644 Mage/src/main/java/mage/game/permanent/token/RallyTheHordeWarriorToken.java delete mode 100644 Mage/src/main/java/mage/game/permanent/token/SorinSolemnVisitorVampireToken.java delete mode 100644 Mage/src/main/java/mage/game/permanent/token/SpoilsOfBloodHorrorToken.java delete mode 100644 Mage/src/main/java/mage/game/permanent/token/WalkerOfTheGroveToken.java diff --git a/Mage.Sets/src/mage/cards/c/CrushOfTentacles.java b/Mage.Sets/src/mage/cards/c/CrushOfTentacles.java index 933d08d7330..2e2f579b3c0 100644 --- a/Mage.Sets/src/mage/cards/c/CrushOfTentacles.java +++ b/Mage.Sets/src/mage/cards/c/CrushOfTentacles.java @@ -12,7 +12,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.common.FilterNonlandPermanent; -import mage.game.permanent.token.CrushOfTentaclesToken; +import mage.game.permanent.token.OctopusToken; /** * @@ -25,7 +25,7 @@ public final class CrushOfTentacles extends CardImpl { // Return all nonland permanents to their owners' hands. If Crush of Tentacles surge cost was paid, create an 8/8 blue Octopus creature token. getSpellAbility().addEffect(new ReturnToHandFromBattlefieldAllEffect(new FilterNonlandPermanent("nonland permanents"))); - Effect effect = new ConditionalOneShotEffect(new CreateTokenEffect(new CrushOfTentaclesToken()), SurgedCondition.instance); + Effect effect = new ConditionalOneShotEffect(new CreateTokenEffect(new OctopusToken()), SurgedCondition.instance); effect.setText("If this spell's surge cost was paid, create an 8/8 blue Octopus creature token"); getSpellAbility().addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/d/DanceOfTheTumbleweeds.java b/Mage.Sets/src/mage/cards/d/DanceOfTheTumbleweeds.java index 9a158a24685..0d6f28edb95 100644 --- a/Mage.Sets/src/mage/cards/d/DanceOfTheTumbleweeds.java +++ b/Mage.Sets/src/mage/cards/d/DanceOfTheTumbleweeds.java @@ -17,7 +17,7 @@ import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.game.Game; -import mage.game.permanent.token.SeedGuardianToken; +import mage.game.permanent.token.ElementalXXGreenToken; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -83,7 +83,7 @@ class DanceOfTheTumbleweedsEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - return new SeedGuardianToken(LandsYouControlCount.instance.calculate(game, source, this)) + return new ElementalXXGreenToken(LandsYouControlCount.instance.calculate(game, source, this)) .putOntoBattlefield(1, game, source); } } diff --git a/Mage.Sets/src/mage/cards/e/EyesOfTheWisent.java b/Mage.Sets/src/mage/cards/e/EyesOfTheWisent.java index 5da9e6b315e..4a97eda2c78 100644 --- a/Mage.Sets/src/mage/cards/e/EyesOfTheWisent.java +++ b/Mage.Sets/src/mage/cards/e/EyesOfTheWisent.java @@ -12,7 +12,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterSpell; import mage.filter.predicate.mageobject.ColorPredicate; -import mage.game.permanent.token.EyesOfTheWisentElementalToken; +import mage.game.permanent.token.Elemental44GreenToken; import java.util.UUID; @@ -33,7 +33,7 @@ public final class EyesOfTheWisent extends CardImpl { // Whenever an opponent casts a blue spell during your turn, you may create a 4/4 green Elemental creature token. this.addAbility(new ConditionalTriggeredAbility( - new SpellCastOpponentTriggeredAbility(new CreateTokenEffect(new EyesOfTheWisentElementalToken()), filter, true), + new SpellCastOpponentTriggeredAbility(new CreateTokenEffect(new Elemental44GreenToken()), filter, true), MyTurnCondition.instance, "Whenever an opponent casts a blue spell during your turn, you may create a 4/4 green Elemental creature token." ).addHint(MyTurnHint.instance)); diff --git a/Mage.Sets/src/mage/cards/f/FleshCarver.java b/Mage.Sets/src/mage/cards/f/FleshCarver.java index 8247d4cbb23..7ba5d59fdcd 100644 --- a/Mage.Sets/src/mage/cards/f/FleshCarver.java +++ b/Mage.Sets/src/mage/cards/f/FleshCarver.java @@ -24,7 +24,7 @@ import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.game.permanent.token.FleshCarverHorrorToken; +import mage.game.permanent.token.HorrorXXBlackToken; import mage.players.Player; import mage.target.common.TargetControlledPermanent; @@ -110,7 +110,7 @@ class FleshCarverEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { int xValue = (Integer) getValue("power"); - return new CreateTokenEffect(new FleshCarverHorrorToken(xValue)).apply(game, source); + return new CreateTokenEffect(new HorrorXXBlackToken(xValue)).apply(game, source); } return false; } diff --git a/Mage.Sets/src/mage/cards/g/GrismoldTheDreadsower.java b/Mage.Sets/src/mage/cards/g/GrismoldTheDreadsower.java index 5ea3e290dc9..ab06afa01bb 100644 --- a/Mage.Sets/src/mage/cards/g/GrismoldTheDreadsower.java +++ b/Mage.Sets/src/mage/cards/g/GrismoldTheDreadsower.java @@ -16,7 +16,7 @@ import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.TokenPredicate; -import mage.game.permanent.token.GrismoldPlantToken; +import mage.game.permanent.token.Plant11Token; import java.util.UUID; @@ -45,7 +45,7 @@ public final class GrismoldTheDreadsower extends CardImpl { // At the beginning of your end step, each player creates a 1/1 green Plant creature token. this.addAbility(new BeginningOfEndStepTriggeredAbility( - new CreateTokenAllEffect(new GrismoldPlantToken(), TargetController.EACH_PLAYER) + new CreateTokenAllEffect(new Plant11Token(), TargetController.EACH_PLAYER) )); // Whenever a creature token dies, put a +1/+1 counter on Grismold, the Dreadsower. diff --git a/Mage.Sets/src/mage/cards/g/GrovetenderDruids.java b/Mage.Sets/src/mage/cards/g/GrovetenderDruids.java index 9964ddf09b5..aedf0f54914 100644 --- a/Mage.Sets/src/mage/cards/g/GrovetenderDruids.java +++ b/Mage.Sets/src/mage/cards/g/GrovetenderDruids.java @@ -9,7 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.game.permanent.token.GrovetenderDruidsPlantToken; +import mage.game.permanent.token.Plant11Token; import java.util.UUID; @@ -29,7 +29,7 @@ public final class GrovetenderDruids extends CardImpl { // Rally-Whenever Grovetender Druids or another Ally you control enters, you may pay {1}. // If you do, create a 1/1 green Plant creature token. this.addAbility(new AllyEntersBattlefieldTriggeredAbility(new DoIfCostPaid( - new CreateTokenEffect(new GrovetenderDruidsPlantToken()), new GenericManaCost(1) + new CreateTokenEffect(new Plant11Token()), new GenericManaCost(1) ), false)); } diff --git a/Mage.Sets/src/mage/cards/i/InfernalGenesis.java b/Mage.Sets/src/mage/cards/i/InfernalGenesis.java index 36d9fea4d6c..b2bf94bdd3c 100644 --- a/Mage.Sets/src/mage/cards/i/InfernalGenesis.java +++ b/Mage.Sets/src/mage/cards/i/InfernalGenesis.java @@ -10,7 +10,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.TargetController; import mage.game.Game; -import mage.game.permanent.token.MinionToken2; +import mage.game.permanent.token.MinionToken; import mage.game.permanent.token.Token; import mage.players.Player; @@ -42,7 +42,7 @@ public final class InfernalGenesis extends CardImpl { class InfernalGenesisEffect extends OneShotEffect { - private static final Token token = new MinionToken2(); + private static final Token token = new MinionToken(); InfernalGenesisEffect() { super(Outcome.PutCreatureInPlay); diff --git a/Mage.Sets/src/mage/cards/m/MarathWillOfTheWild.java b/Mage.Sets/src/mage/cards/m/MarathWillOfTheWild.java index c6b1fb11ff2..d8709770525 100644 --- a/Mage.Sets/src/mage/cards/m/MarathWillOfTheWild.java +++ b/Mage.Sets/src/mage/cards/m/MarathWillOfTheWild.java @@ -15,6 +15,7 @@ import mage.abilities.dynamicvalue.common.ManaSpentToCastCount; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; @@ -27,11 +28,10 @@ import mage.constants.SuperType; import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.token.MarathWillOfTheWildElementalToken; -import mage.game.permanent.token.Token; import mage.players.Player; import mage.target.common.TargetAnyTarget; import mage.target.common.TargetCreaturePermanent; +import mage.game.permanent.token.ElementalXXGreenToken; import java.util.UUID; @@ -110,12 +110,8 @@ class MarathWillOfTheWildCreateTokenEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); if (player != null) { - int amount = GetXValue.instance.calculate(game, source, this); - Token token = new MarathWillOfTheWildElementalToken(); - token.setPower(amount); - token.setToughness(amount); - token.putOntoBattlefield(1, game, source, source.getControllerId()); - return true; + int xvalue = GetXValue.instance.calculate(game, source, this); + return new CreateTokenEffect(new ElementalXXGreenToken(xvalue)).apply(game, source); } return false; } diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianProcessor.java b/Mage.Sets/src/mage/cards/p/PhyrexianProcessor.java index 95d06e5249f..011c55daf31 100644 --- a/Mage.Sets/src/mage/cards/p/PhyrexianProcessor.java +++ b/Mage.Sets/src/mage/cards/p/PhyrexianProcessor.java @@ -8,6 +8,7 @@ import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -15,7 +16,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.token.MinionToken; +import mage.game.permanent.token.PhyrexianMinionToken; import mage.players.Player; import mage.util.CardUtil; @@ -110,12 +111,8 @@ class PhyrexianProcessorCreateTokenEffect extends OneShotEffect { String key = CardUtil.getCardZoneString("lifePaid", source.getSourceId(), game, true); Object object = game.getState().getValue(key); if (object instanceof Integer) { - int lifePaid = (int) object; - MinionToken token = new MinionToken(); - token.setPower(lifePaid); - token.setToughness(lifePaid); - token.putOntoBattlefield(1, game, source, source.getControllerId()); - return true; + int xvalue = (int) object; + return new CreateTokenEffect(new PhyrexianMinionToken(xvalue)).apply(game, source); } return false; } diff --git a/Mage.Sets/src/mage/cards/r/RallyTheHorde.java b/Mage.Sets/src/mage/cards/r/RallyTheHorde.java index af7546bc266..e407da6cb8c 100644 --- a/Mage.Sets/src/mage/cards/r/RallyTheHorde.java +++ b/Mage.Sets/src/mage/cards/r/RallyTheHorde.java @@ -12,7 +12,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; -import mage.game.permanent.token.RallyTheHordeWarriorToken; +import mage.game.permanent.token.RedWarriorToken; import mage.players.Player; /** @@ -72,7 +72,7 @@ class RallyTheHordeEffect extends OneShotEffect { nonLandCardsExiled += nonLands; } } - return new CreateTokenEffect(new RallyTheHordeWarriorToken(), nonLandCardsExiled).apply(game, source); + return new CreateTokenEffect(new RedWarriorToken(), nonLandCardsExiled).apply(game, source); } return false; diff --git a/Mage.Sets/src/mage/cards/s/SeedGuardian.java b/Mage.Sets/src/mage/cards/s/SeedGuardian.java index d1dec07dcb9..fb4935b78d2 100644 --- a/Mage.Sets/src/mage/cards/s/SeedGuardian.java +++ b/Mage.Sets/src/mage/cards/s/SeedGuardian.java @@ -14,7 +14,7 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.permanent.token.SeedGuardianToken; +import mage.game.permanent.token.ElementalXXGreenToken; import mage.players.Player; /** @@ -66,7 +66,7 @@ class SeedGuardianEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { int creaturesInGraveyard = controller.getGraveyard().count(StaticFilters.FILTER_CARD_CREATURE, game); - return new CreateTokenEffect(new SeedGuardianToken(creaturesInGraveyard)).apply(game, source); + return new CreateTokenEffect(new ElementalXXGreenToken(creaturesInGraveyard)).apply(game, source); } return false; } diff --git a/Mage.Sets/src/mage/cards/s/SorinSolemnVisitor.java b/Mage.Sets/src/mage/cards/s/SorinSolemnVisitor.java index ae295bea696..59e399ad294 100644 --- a/Mage.Sets/src/mage/cards/s/SorinSolemnVisitor.java +++ b/Mage.Sets/src/mage/cards/s/SorinSolemnVisitor.java @@ -17,7 +17,7 @@ import mage.constants.Duration; import mage.constants.SuperType; import mage.filter.StaticFilters; import mage.game.command.emblems.SorinSolemnVisitorEmblem; -import mage.game.permanent.token.SorinSolemnVisitorVampireToken; +import mage.game.permanent.token.VampireToken; /** * @@ -42,7 +42,7 @@ public final class SorinSolemnVisitor extends CardImpl { this.addAbility(loyaltyAbility); // -2: Create a 2/2 black Vampire creature token with flying. - this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new SorinSolemnVisitorVampireToken()), -2)); + this.addAbility(new LoyaltyAbility(new CreateTokenEffect(new VampireToken()), -2)); // -6: You get an emblem with "At the beginning of each opponent's upkeep, that player sacrifices a creature." this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new SorinSolemnVisitorEmblem()), -6)); diff --git a/Mage.Sets/src/mage/cards/s/SpoilsOfBlood.java b/Mage.Sets/src/mage/cards/s/SpoilsOfBlood.java index 4964fb93945..1a091662826 100644 --- a/Mage.Sets/src/mage/cards/s/SpoilsOfBlood.java +++ b/Mage.Sets/src/mage/cards/s/SpoilsOfBlood.java @@ -12,7 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.game.Game; -import mage.game.permanent.token.SpoilsOfBloodHorrorToken; +import mage.game.permanent.token.HorrorXXBlackToken; import mage.players.Player; /** @@ -54,7 +54,7 @@ class SpoilsOfBloodEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { new CreateTokenEffect( - new SpoilsOfBloodHorrorToken(CreaturesDiedThisTurnCount.instance.calculate(game, source, this))) + new HorrorXXBlackToken(CreaturesDiedThisTurnCount.instance.calculate(game, source, this))) .apply(game, source); return true; } diff --git a/Mage.Sets/src/mage/cards/t/TumbleweedRising.java b/Mage.Sets/src/mage/cards/t/TumbleweedRising.java index a553fc1d14b..fa0239749b6 100644 --- a/Mage.Sets/src/mage/cards/t/TumbleweedRising.java +++ b/Mage.Sets/src/mage/cards/t/TumbleweedRising.java @@ -10,7 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.game.Game; -import mage.game.permanent.token.SeedGuardianToken; +import mage.game.permanent.token.ElementalXXGreenToken; import java.util.UUID; @@ -61,6 +61,6 @@ class TumbleweedRisingEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int xvalue = GreatestPowerAmongControlledCreaturesValue.instance.calculate(game, source, this); - return new CreateTokenEffect(new SeedGuardianToken(xvalue)).apply(game, source); + return new CreateTokenEffect(new ElementalXXGreenToken(xvalue)).apply(game, source); } } diff --git a/Mage.Sets/src/mage/cards/w/WalkerOfTheGrove.java b/Mage.Sets/src/mage/cards/w/WalkerOfTheGrove.java index 98f5d3eba23..22005807dbf 100644 --- a/Mage.Sets/src/mage/cards/w/WalkerOfTheGrove.java +++ b/Mage.Sets/src/mage/cards/w/WalkerOfTheGrove.java @@ -10,7 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.game.permanent.token.WalkerOfTheGroveToken; +import mage.game.permanent.token.Elemental44GreenToken; /** * @@ -26,7 +26,7 @@ public final class WalkerOfTheGrove extends CardImpl { this.toughness = new MageInt(7); // When Walker of the Grove leaves the battlefield, create a 4/4 green Elemental creature token. - this.addAbility(new LeavesBattlefieldTriggeredAbility(new CreateTokenEffect(new WalkerOfTheGroveToken(), 1), false)); + this.addAbility(new LeavesBattlefieldTriggeredAbility(new CreateTokenEffect(new Elemental44GreenToken(), 1), false)); // Evoke {4}{G} this.addAbility(new EvokeAbility("{4}{G}")); } diff --git a/Mage/src/main/java/mage/game/permanent/token/CrushOfTentaclesToken.java b/Mage/src/main/java/mage/game/permanent/token/CrushOfTentaclesToken.java deleted file mode 100644 index 831465f93fc..00000000000 --- a/Mage/src/main/java/mage/game/permanent/token/CrushOfTentaclesToken.java +++ /dev/null @@ -1,29 +0,0 @@ -package mage.game.permanent.token; - -import mage.MageInt; -import mage.constants.CardType; -import mage.constants.SubType; - -/** - * @author spjspj - */ -public final class CrushOfTentaclesToken extends TokenImpl { - - public CrushOfTentaclesToken() { - super("Octopus Token", "8/8 blue Octopus creature"); - this.cardType.add(CardType.CREATURE); - this.color.setBlue(true); - this.subtype.add(SubType.OCTOPUS); - this.power = new MageInt(8); - this.toughness = new MageInt(8); - } - - private CrushOfTentaclesToken(final CrushOfTentaclesToken token) { - super(token); - } - - public CrushOfTentaclesToken copy() { - return new CrushOfTentaclesToken(this); - } - -} diff --git a/Mage/src/main/java/mage/game/permanent/token/DinDragonToken.java b/Mage/src/main/java/mage/game/permanent/token/DinDragonToken.java index c833b1d4418..87cac3c1118 100644 --- a/Mage/src/main/java/mage/game/permanent/token/DinDragonToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/DinDragonToken.java @@ -11,7 +11,7 @@ import mage.constants.SubType; public final class DinDragonToken extends TokenImpl { public DinDragonToken() { - super("Dragon Token", "4/4 red Dinosaur Dragon creature token with flying"); + super("Dinosaur Dragon Token", "4/4 red Dinosaur Dragon creature token with flying"); cardType.add(CardType.CREATURE); color.setRed(true); subtype.add(SubType.DINOSAUR); diff --git a/Mage/src/main/java/mage/game/permanent/token/EyesOfTheWisentElementalToken.java b/Mage/src/main/java/mage/game/permanent/token/Elemental44GreenToken.java similarity index 59% rename from Mage/src/main/java/mage/game/permanent/token/EyesOfTheWisentElementalToken.java rename to Mage/src/main/java/mage/game/permanent/token/Elemental44GreenToken.java index 15f41debc84..2d6ed9040f5 100644 --- a/Mage/src/main/java/mage/game/permanent/token/EyesOfTheWisentElementalToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/Elemental44GreenToken.java @@ -7,9 +7,9 @@ import mage.constants.SubType; /** * @author spjspj */ -public final class EyesOfTheWisentElementalToken extends TokenImpl { +public final class Elemental44GreenToken extends TokenImpl { - public EyesOfTheWisentElementalToken() { + public Elemental44GreenToken() { super("Elemental Token", "4/4 green Elemental creature token"); cardType.add(CardType.CREATURE); color.setGreen(true); @@ -18,11 +18,11 @@ public final class EyesOfTheWisentElementalToken extends TokenImpl { toughness = new MageInt(4); } - private EyesOfTheWisentElementalToken(final EyesOfTheWisentElementalToken token) { + private Elemental44GreenToken(final Elemental44GreenToken token) { super(token); } - public EyesOfTheWisentElementalToken copy() { - return new EyesOfTheWisentElementalToken(this); + public Elemental44GreenToken copy() { + return new Elemental44GreenToken(this); } } diff --git a/Mage/src/main/java/mage/game/permanent/token/SeedGuardianToken.java b/Mage/src/main/java/mage/game/permanent/token/ElementalXXGreenToken.java similarity index 60% rename from Mage/src/main/java/mage/game/permanent/token/SeedGuardianToken.java rename to Mage/src/main/java/mage/game/permanent/token/ElementalXXGreenToken.java index f0fa5a71677..3e1cfa21e86 100644 --- a/Mage/src/main/java/mage/game/permanent/token/SeedGuardianToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/ElementalXXGreenToken.java @@ -7,13 +7,13 @@ import mage.constants.SubType; /** * @author spjspj */ -public final class SeedGuardianToken extends TokenImpl { +public final class ElementalXXGreenToken extends TokenImpl { - public SeedGuardianToken() { + public ElementalXXGreenToken() { this(1); } - public SeedGuardianToken(int xValue) { + public ElementalXXGreenToken(int xValue) { super("Elemental Token", "X/X green Elemental creature token"); cardType.add(CardType.CREATURE); color.setGreen(true); @@ -22,11 +22,11 @@ public final class SeedGuardianToken extends TokenImpl { toughness = new MageInt(xValue); } - private SeedGuardianToken(final SeedGuardianToken token) { + private ElementalXXGreenToken(final ElementalXXGreenToken token) { super(token); } - public SeedGuardianToken copy() { - return new SeedGuardianToken(this); + public ElementalXXGreenToken copy() { + return new ElementalXXGreenToken(this); } } diff --git a/Mage/src/main/java/mage/game/permanent/token/GrovetenderDruidsPlantToken.java b/Mage/src/main/java/mage/game/permanent/token/GrovetenderDruidsPlantToken.java deleted file mode 100644 index cc98203651e..00000000000 --- a/Mage/src/main/java/mage/game/permanent/token/GrovetenderDruidsPlantToken.java +++ /dev/null @@ -1,28 +0,0 @@ -package mage.game.permanent.token; - -import mage.MageInt; -import mage.constants.CardType; -import mage.constants.SubType; - -/** - * @author spjspj - */ -public final class GrovetenderDruidsPlantToken extends TokenImpl { - - public GrovetenderDruidsPlantToken() { - super("Plant Token", "1/1 green Plant creature token"); - cardType.add(CardType.CREATURE); - color.setGreen(true); - subtype.add(SubType.PLANT); - power = new MageInt(1); - toughness = new MageInt(1); - } - - private GrovetenderDruidsPlantToken(final GrovetenderDruidsPlantToken token) { - super(token); - } - - public GrovetenderDruidsPlantToken copy() { - return new GrovetenderDruidsPlantToken(this); - } -} diff --git a/Mage/src/main/java/mage/game/permanent/token/FleshCarverHorrorToken.java b/Mage/src/main/java/mage/game/permanent/token/HorrorXXBlackToken.java similarity index 59% rename from Mage/src/main/java/mage/game/permanent/token/FleshCarverHorrorToken.java rename to Mage/src/main/java/mage/game/permanent/token/HorrorXXBlackToken.java index a91f64a5a8f..478a123e926 100644 --- a/Mage/src/main/java/mage/game/permanent/token/FleshCarverHorrorToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/HorrorXXBlackToken.java @@ -7,13 +7,13 @@ import mage.constants.SubType; /** * @author spjspj */ -public final class FleshCarverHorrorToken extends TokenImpl { +public final class HorrorXXBlackToken extends TokenImpl { - public FleshCarverHorrorToken() { + public HorrorXXBlackToken() { this(1); } - public FleshCarverHorrorToken(int xValue) { + public HorrorXXBlackToken(int xValue) { super("Horror Token", "X/X black Horror creature token"); cardType.add(CardType.CREATURE); color.setBlack(true); @@ -22,11 +22,11 @@ public final class FleshCarverHorrorToken extends TokenImpl { toughness = new MageInt(xValue); } - private FleshCarverHorrorToken(final FleshCarverHorrorToken token) { + private HorrorXXBlackToken(final HorrorXXBlackToken token) { super(token); } - public FleshCarverHorrorToken copy() { - return new FleshCarverHorrorToken(this); + public HorrorXXBlackToken copy() { + return new HorrorXXBlackToken(this); } } diff --git a/Mage/src/main/java/mage/game/permanent/token/HumanRogueToken.java b/Mage/src/main/java/mage/game/permanent/token/HumanRogueToken.java index a28573e5d3c..05e3708d76e 100644 --- a/Mage/src/main/java/mage/game/permanent/token/HumanRogueToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/HumanRogueToken.java @@ -10,7 +10,7 @@ import mage.constants.SubType; public final class HumanRogueToken extends TokenImpl { public HumanRogueToken() { - super("Human Token", "1/1 white Human Rogue creature token"); + super("Human Rogue Token", "1/1 white Human Rogue creature token"); cardType.add(CardType.CREATURE); color.setWhite(true); subtype.add(SubType.HUMAN); diff --git a/Mage/src/main/java/mage/game/permanent/token/MarathWillOfTheWildElementalToken.java b/Mage/src/main/java/mage/game/permanent/token/MarathWillOfTheWildElementalToken.java deleted file mode 100644 index ee2ed8f36b1..00000000000 --- a/Mage/src/main/java/mage/game/permanent/token/MarathWillOfTheWildElementalToken.java +++ /dev/null @@ -1,30 +0,0 @@ - - -package mage.game.permanent.token; - -import mage.constants.CardType; -import mage.constants.SubType; -import mage.MageInt; - -/** - * @author spjspj - */ -public final class MarathWillOfTheWildElementalToken extends TokenImpl { - - public MarathWillOfTheWildElementalToken() { - super("Elemental Token", "X/X green Elemental creature token"); - cardType.add(CardType.CREATURE); - subtype.add(SubType.ELEMENTAL); - color.setGreen(true); - power = new MageInt(0); - toughness = new MageInt(0); - } - - private MarathWillOfTheWildElementalToken(final MarathWillOfTheWildElementalToken token) { - super(token); - } - - public MarathWillOfTheWildElementalToken copy() { - return new MarathWillOfTheWildElementalToken(this); - } -} diff --git a/Mage/src/main/java/mage/game/permanent/token/MinionToken.java b/Mage/src/main/java/mage/game/permanent/token/MinionToken.java index 5b3862b9562..7d1c5f5b5ce 100644 --- a/Mage/src/main/java/mage/game/permanent/token/MinionToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/MinionToken.java @@ -5,25 +5,20 @@ import mage.constants.CardType; import mage.constants.SubType; /** - * @author FenrisulfrX + * @author Quercitron */ public final class MinionToken extends TokenImpl { public MinionToken() { - this("DDE"); - } - - public MinionToken(String setCode) { - super("Phyrexian Minion Token", "X/X black Phyrexian Minion creature token"); + super("Minion Token", "1/1 black Minion creature token"); cardType.add(CardType.CREATURE); - subtype.add(SubType.PHYREXIAN); subtype.add(SubType.MINION); color.setBlack(true); - power = new MageInt(0); - toughness = new MageInt(0); + power = new MageInt(1); + toughness = new MageInt(1); } - private MinionToken(final MinionToken token) { + protected MinionToken(final MinionToken token) { super(token); } diff --git a/Mage/src/main/java/mage/game/permanent/token/MinionToken2.java b/Mage/src/main/java/mage/game/permanent/token/MinionToken2.java deleted file mode 100644 index b35acab5e35..00000000000 --- a/Mage/src/main/java/mage/game/permanent/token/MinionToken2.java +++ /dev/null @@ -1,28 +0,0 @@ -package mage.game.permanent.token; - -import mage.MageInt; -import mage.constants.CardType; -import mage.constants.SubType; - -/** - * @author Quercitron - */ -public final class MinionToken2 extends TokenImpl { - - public MinionToken2() { - super("Minion Token", "1/1 black Minion creature token"); - cardType.add(CardType.CREATURE); - subtype.add(SubType.MINION); - color.setBlack(true); - power = new MageInt(1); - toughness = new MageInt(1); - } - - protected MinionToken2(final MinionToken2 token) { - super(token); - } - - public MinionToken2 copy() { - return new MinionToken2(this); - } -} diff --git a/Mage/src/main/java/mage/game/permanent/token/NighteyesTheDesecratorToken.java b/Mage/src/main/java/mage/game/permanent/token/NighteyesTheDesecratorToken.java deleted file mode 100644 index dc11eaf6383..00000000000 --- a/Mage/src/main/java/mage/game/permanent/token/NighteyesTheDesecratorToken.java +++ /dev/null @@ -1,44 +0,0 @@ - - -package mage.game.permanent.token; - -import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.constants.Zone; -import mage.filter.StaticFilters; -import mage.target.common.TargetCardInGraveyard; - -/** - * @author spjspj - */ -public final class NighteyesTheDesecratorToken extends TokenImpl { - - public NighteyesTheDesecratorToken() { - super("Nighteyes the Desecrator Token", ""); - this.supertype.add(SuperType.LEGENDARY); - cardType.add(CardType.CREATURE); - color.setBlack(true); - subtype.add(SubType.RAT); - subtype.add(SubType.WIZARD); - power = new MageInt(4); - toughness = new MageInt(2); - // {4}{B}: Put target creature card from a graveyard onto the battlefield under your control. - Ability ability = new SimpleActivatedAbility(new ReturnFromGraveyardToBattlefieldTargetEffect(), new ManaCostsImpl<>("{4}{B}")); - ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); - this.addAbility(ability); - } - - private NighteyesTheDesecratorToken(final NighteyesTheDesecratorToken token) { - super(token); - } - - public NighteyesTheDesecratorToken copy() { - return new NighteyesTheDesecratorToken(this); - } -} diff --git a/Mage/src/main/java/mage/game/permanent/token/PhyrexianMinionToken.java b/Mage/src/main/java/mage/game/permanent/token/PhyrexianMinionToken.java new file mode 100644 index 00000000000..488d2dd80f8 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/PhyrexianMinionToken.java @@ -0,0 +1,33 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author FenrisulfrX + */ +public final class PhyrexianMinionToken extends TokenImpl { + + public PhyrexianMinionToken() { + this(1); + } + + public PhyrexianMinionToken(int xValue) { + super("Phyrexian Minion Token", "X/X black Phyrexian Minion creature token"); + cardType.add(CardType.CREATURE); + subtype.add(SubType.PHYREXIAN); + subtype.add(SubType.MINION); + color.setBlack(true); + power = new MageInt(xValue); + toughness = new MageInt(xValue); + } + + private PhyrexianMinionToken(final PhyrexianMinionToken token) { + super(token); + } + + public PhyrexianMinionToken copy() { + return new PhyrexianMinionToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/GrismoldPlantToken.java b/Mage/src/main/java/mage/game/permanent/token/Plant11Token.java similarity index 62% rename from Mage/src/main/java/mage/game/permanent/token/GrismoldPlantToken.java rename to Mage/src/main/java/mage/game/permanent/token/Plant11Token.java index 67f3f33d09d..6967daf5c7b 100644 --- a/Mage/src/main/java/mage/game/permanent/token/GrismoldPlantToken.java +++ b/Mage/src/main/java/mage/game/permanent/token/Plant11Token.java @@ -4,9 +4,12 @@ import mage.MageInt; import mage.constants.CardType; import mage.constants.SubType; -public final class GrismoldPlantToken extends TokenImpl { +/** + * @author spjspj + */ +public final class Plant11Token extends TokenImpl { - public GrismoldPlantToken() { + public Plant11Token() { super("Plant Token", "1/1 green Plant creature token"); cardType.add(CardType.CREATURE); color.setGreen(true); @@ -15,11 +18,11 @@ public final class GrismoldPlantToken extends TokenImpl { toughness = new MageInt(1); } - private GrismoldPlantToken(final GrismoldPlantToken token) { + private Plant11Token(final Plant11Token token) { super(token); } - public GrismoldPlantToken copy() { - return new GrismoldPlantToken(this); + public Plant11Token copy() { + return new Plant11Token(this); } } diff --git a/Mage/src/main/java/mage/game/permanent/token/RallyTheHordeWarriorToken.java b/Mage/src/main/java/mage/game/permanent/token/RallyTheHordeWarriorToken.java deleted file mode 100644 index 6a1d3310496..00000000000 --- a/Mage/src/main/java/mage/game/permanent/token/RallyTheHordeWarriorToken.java +++ /dev/null @@ -1,29 +0,0 @@ - -package mage.game.permanent.token; - -import mage.constants.CardType; -import mage.constants.SubType; -import mage.MageInt; - -/** - * @author spjspj - */ -public final class RallyTheHordeWarriorToken extends TokenImpl { - - public RallyTheHordeWarriorToken() { - super("Warrior Token", "1/1 red Warrior creature token"); - cardType.add(CardType.CREATURE); - color.setRed(true); - subtype.add(SubType.WARRIOR); - power = new MageInt(1); - toughness = new MageInt(1); - } - - private RallyTheHordeWarriorToken(final RallyTheHordeWarriorToken token) { - super(token); - } - - public RallyTheHordeWarriorToken copy() { - return new RallyTheHordeWarriorToken(this); - } -} diff --git a/Mage/src/main/java/mage/game/permanent/token/SorinSolemnVisitorVampireToken.java b/Mage/src/main/java/mage/game/permanent/token/SorinSolemnVisitorVampireToken.java deleted file mode 100644 index 8ad1c68fe31..00000000000 --- a/Mage/src/main/java/mage/game/permanent/token/SorinSolemnVisitorVampireToken.java +++ /dev/null @@ -1,30 +0,0 @@ -package mage.game.permanent.token; - -import mage.MageInt; -import mage.abilities.keyword.FlyingAbility; -import mage.constants.CardType; -import mage.constants.SubType; - -/** - * @author spjspj - */ -public final class SorinSolemnVisitorVampireToken extends TokenImpl { - - public SorinSolemnVisitorVampireToken() { - super("Vampire Token", "2/2 black Vampire creature token with flying"); - cardType.add(CardType.CREATURE); - color.setBlack(true); - subtype.add(SubType.VAMPIRE); - power = new MageInt(2); - toughness = new MageInt(2); - addAbility(FlyingAbility.getInstance()); - } - - private SorinSolemnVisitorVampireToken(final SorinSolemnVisitorVampireToken token) { - super(token); - } - - public SorinSolemnVisitorVampireToken copy() { - return new SorinSolemnVisitorVampireToken(this); - } -} diff --git a/Mage/src/main/java/mage/game/permanent/token/SpoilsOfBloodHorrorToken.java b/Mage/src/main/java/mage/game/permanent/token/SpoilsOfBloodHorrorToken.java deleted file mode 100644 index db8966a8181..00000000000 --- a/Mage/src/main/java/mage/game/permanent/token/SpoilsOfBloodHorrorToken.java +++ /dev/null @@ -1,32 +0,0 @@ -package mage.game.permanent.token; - -import mage.MageInt; -import mage.constants.CardType; -import mage.constants.SubType; - -/** - * @author spjspj - */ -public final class SpoilsOfBloodHorrorToken extends TokenImpl { - - public SpoilsOfBloodHorrorToken() { - this(1); - } - - public SpoilsOfBloodHorrorToken(int xValue) { - super("Horror Token", "X/X black Horror creature token"); - cardType.add(CardType.CREATURE); - color.setBlack(true); - subtype.add(SubType.HORROR); - power = new MageInt(xValue); - toughness = new MageInt(xValue); - } - - private SpoilsOfBloodHorrorToken(final SpoilsOfBloodHorrorToken token) { - super(token); - } - - public SpoilsOfBloodHorrorToken copy() { - return new SpoilsOfBloodHorrorToken(this); - } -} diff --git a/Mage/src/main/java/mage/game/permanent/token/WalkerOfTheGroveToken.java b/Mage/src/main/java/mage/game/permanent/token/WalkerOfTheGroveToken.java deleted file mode 100644 index e660b616776..00000000000 --- a/Mage/src/main/java/mage/game/permanent/token/WalkerOfTheGroveToken.java +++ /dev/null @@ -1,29 +0,0 @@ -package mage.game.permanent.token; - -import mage.MageInt; -import mage.constants.CardType; -import mage.constants.SubType; - -/** - * @author spjspj - */ - -public final class WalkerOfTheGroveToken extends TokenImpl { - - public WalkerOfTheGroveToken() { - super("Elemental Token", "4/4 green Elemental creature token"); - cardType.add(CardType.CREATURE); - this.subtype.add(SubType.ELEMENTAL); - this.color.setGreen(true); - power = new MageInt(4); - toughness = new MageInt(4); - } - - private WalkerOfTheGroveToken(final WalkerOfTheGroveToken token) { - super(token); - } - - public WalkerOfTheGroveToken copy() { - return new WalkerOfTheGroveToken(this); - } -} diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index 39c9c643fd3..a4275e34e4c 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -242,7 +242,7 @@ |Generate|TOK:ARN|Djinn|||DjinnToken| |Generate|TOK:AVR|Angel|||AngelToken| |Generate|TOK:AVR|Demon|||DemonToken| -|Generate|TOK:AVR|Human|1||RedHumanToken| +|Generate|TOK:AVR|Human|1||ThatcherHumanToken| |Generate|TOK:AVR|Human|2||HumanToken| |Generate|TOK:AVR|Spirit|1||SpiritBlueToken| |Generate|TOK:AVR|Spirit|2||SpiritWhiteToken| @@ -257,7 +257,7 @@ |Generate|TOK:BFZ|Knight Ally|||KnightAllyToken| |Generate|TOK:BFZ|Kor Ally|||KorAllyToken| |Generate|TOK:BFZ|Octopus|||OctopusToken| -|Generate|TOK:BFZ|Plant|||GrovetenderDruidsPlantToken| +|Generate|TOK:BFZ|Plant|||Plant11Token| |Generate|TOK:BNG|Bird|1||EnchantmentBirdToken| |Generate|TOK:BNG|Bird|2||BirdToken| |Generate|TOK:BNG|Cat Soldier|||CatSoldierCreatureToken| @@ -284,7 +284,7 @@ |Generate|TOK:C14|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:C14|Goat|||GoatToken| |Generate|TOK:C14|Goblin|||GoblinToken| -|Generate|TOK:C14|Horror|||SpoilsOfBloodHorrorToken| +|Generate|TOK:C14|Horror|||HorrorXXBlackToken| |Generate|TOK:C14|Kor Soldier|||KorSoldierToken| |Generate|TOK:C14|Kraken|||Kraken99Token| |Generate|TOK:C14|Myr|||MyrToken| @@ -395,7 +395,7 @@ |Generate|TOK:C19|Phyrexian Horror|||PhyrexianRebirthHorrorToken| |Generate|TOK:C19|Human|||HumanToken| |Generate|TOK:C19|Pegasus|||PegasusToken| -|Generate|TOK:C19|Plant|||GrismoldPlantToken| +|Generate|TOK:C19|Plant|||Plant11Token| |Generate|TOK:C19|Rhino|||RhinoToken| |Generate|TOK:C19|Saproling|||SaprolingToken| |Generate|TOK:C19|Sculpture|||DoomedArtisanToken| @@ -457,7 +457,7 @@ |Generate|TOK:DDD|Beast|2||BeastToken2| |Generate|TOK:DDD|Elephant|||ElephantToken| |Generate|TOK:DDE|Hornet|||HornetToken| -|Generate|TOK:DDE|Minion|||MinionToken| +|Generate|TOK:DDE|Phyrexian Minion|||PhyrexianMinionToken| |Generate|TOK:DDE|Saproling|||SaprolingToken| |Generate|TOK:DDF|Soldier|||SoldierToken| |Generate|TOK:DDG|Goblin|||GoblinToken| @@ -612,7 +612,7 @@ # LGN don't have tokens, from wiki: A Sliver token for Brood Sliver and a Goblin token for Warbreak Trumpeter were featured as a Magic Player Reward. |Generate|TOK:LRW|Avatar|||AvatarToken| |Generate|TOK:LRW|Beast|||BeastToken| -|Generate|TOK:LRW|Elemental|1||WalkerOfTheGroveToken| +|Generate|TOK:LRW|Elemental|1||Elemental44GreenToken| |Generate|TOK:LRW|Elemental|2||WhiteElementalToken| |Generate|TOK:LRW|Elemental Shaman|||ElementalShamanToken| |Generate|TOK:LRW|Elf Warrior|||ElfWarriorToken| @@ -724,7 +724,7 @@ |Generate|TOK:MM3|Zombie|||ZombieToken| |Generate|TOK:MMA|Bat|||BatToken| |Generate|TOK:MMA|Dragon|||DragonToken| -|Generate|TOK:MMA|Elemental|||WalkerOfTheGroveToken| +|Generate|TOK:MMA|Elemental|||Elemental44GreenToken| |Generate|TOK:MMA|Faerie Rogue|||OonaQueenFaerieRogueToken| |Generate|TOK:MMA|Giant Warrior|||GiantWarriorToken| |Generate|TOK:MMA|Goblin Rogue|||GoblinRogueToken| @@ -757,7 +757,7 @@ |Generate|TOK:OGW|Eldrazi Scion|4||EldraziScionToken| |Generate|TOK:OGW|Eldrazi Scion|5||EldraziScionToken| |Generate|TOK:OGW|Eldrazi Scion|6||EldraziScionToken| -|Generate|TOK:OGW|Elemental|1||SeedGuardianToken| +|Generate|TOK:OGW|Elemental|1||ElementalXXGreenToken| |Generate|TOK:OGW|Elemental|2||ElementalTokenWithHaste| |Generate|TOK:OGW|Plant|||PlantToken| |Generate|TOK:OGW|Zombie|||ZombieToken| @@ -1073,7 +1073,7 @@ # OonaQueenFaerieRogueToken is FaerieRogueToken with additional blue color, but ZNC contains only one token - so don't use normal token for it #|Generate|TOK:ZNC|Faerie Rogue|||OonaQueenFaerieRogueToken| # Germ token uses in chest and antology, but scryfall put it here -#|Generate|TOK:ZNC|Phyrexian Germ|||PhyrexianGermToken| +|Generate|TOK:ZNC|Phyrexian Germ|||PhyrexianGermToken| # |Generate|TOK:ZNC|Goblin Rogue|||GoblinRogueToken| |Generate|TOK:ZNC|Kor Ally|||KorAllyToken| @@ -1308,7 +1308,7 @@ # UMA |Generate|TOK:UMA|Citizen|||CitizenToken| |Generate|TOK:UMA|Drake|||DrakeToken| -|Generate|TOK:UMA|Elemental|1||WalkerOfTheGroveToken| +|Generate|TOK:UMA|Elemental|1||Elemental44GreenToken| |Generate|TOK:UMA|Elemental|2||RedElementalToken| |Generate|TOK:UMA|Elemental|3||RedElementalToken| |Generate|TOK:UMA|Faerie Rogue|||FaerieRogueToken| @@ -1536,7 +1536,7 @@ |Generate|TOK:DDR|Eldrazi Scion|||EldraziScionToken| |Generate|TOK:DDR|Demon|||DemonToken| |Generate|TOK:DDR|Zombie Giant|||QuestForTheGravelordZombieToken| -|Generate|TOK:DDR|Elemental|||WalkerOfTheGroveToken| +|Generate|TOK:DDR|Elemental|||Elemental44GreenToken| |Generate|TOK:DDR|Plant|||PlantToken| # DDS @@ -2176,7 +2176,7 @@ |Generate|TOK:OTJ|Bird|||BlueBirdToken| |Generate|TOK:OTJ|Clue|||ClueArtifactToken| |Generate|TOK:OTJ|Dinosaur|||Dinosaur31Token| -|Generate|TOK:OTJ|Elemental|||SeedGuardianToken| +|Generate|TOK:OTJ|Elemental|||ElementalXXGreenToken| |Generate|TOK:OTJ|Elk|||ElkToken| |Generate|TOK:OTJ|Mercenary|||MercenaryToken| |Generate|TOK:OTJ|Meteorite|||MeteoriteToken| @@ -2460,7 +2460,7 @@ |Generate|TOK:DFT|Elephant|||ElephantToken| |Generate|TOK:DFT|Goblin|||GoblinToken| |Generate|TOK:DFT|Insect|||InsectToken| -|Generate|TOK:DFT|Pilot|||PilotCrewToken| +|Generate|TOK:DFT|Pilot|||PilotSaddleCrewToken| |Generate|TOK:DFT|Servo|||ServoToken| |Generate|TOK:DFT|Thopter|1||ThopterColorlessToken| |Generate|TOK:DFT|Thopter|2||ThopterColorlessToken| From b6421e4b6cfb6c094374352f5e08ab7ea6ce0a67 Mon Sep 17 00:00:00 2001 From: androosss <101566943+androosss@users.noreply.github.com> Date: Wed, 2 Apr 2025 13:13:25 +0200 Subject: [PATCH 16/59] images: added new tokens from promo and other sets, fixed miss images (#13492) * add missing tokens images * fix broken card images * remove tokens without image * remove copy tokens references (embalm, eternalize) --- .../card/dl/sources/GrabbagImageSource.java | 2 +- .../dl/sources/ScryfallImageSupportCards.java | 14 +- .../sources/ScryfallImageSupportTokens.java | 223 +++++++- .../game/ScryfallImagesDownloadTest.java | 4 +- .../src/mage/sets/AetherdriftCommander.java | 2 +- .../sets/MediaAndCollaborationPromos.java | 96 ++-- Mage/src/main/resources/tokens-database.txt | 525 ++++++++++++++---- 7 files changed, 703 insertions(+), 163 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java index 7db9fa2970e..ab56977114d 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GrabbagImageSource.java @@ -204,7 +204,7 @@ public enum GrabbagImageSource implements CardImageSource { singleLinks.put("SWS/Hazard Trooper", "ZOutamG.jpeg"); singleLinks.put("SWS/Head Hunting", "7OT1bGZ.jpeg"); singleLinks.put("SWS/Heavy Trooper", "HhZWs2N.jpeg"); - singleLinks.put("SWS/Hot Pursuit", "ih1GT5Z.jpeg"); + singleLinks.put("SWS/Hot Pursuit (Star Wars)", "ih1GT5Z.jpeg"); singleLinks.put("SWS/Hungry Dragonsnake", "23v7RTm.jpeg"); singleLinks.put("SWS/Hunt to Extinction", "3eJyfzZ.jpeg"); singleLinks.put("SWS/Hutt Crime Lord", "NAzK7Hp.jpeg"); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java index 1c64712ad5a..8ca97816777 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportCards.java @@ -439,7 +439,7 @@ public class ScryfallImageSupportCards { add("ELD"); // Throne of Eldraine //add("PTG"); // Ponies: The Galloping add("CMB1"); // Mystery Booster Playtest Cards 2019 - //add("MB1"); // Mystery Booster + add("MB1"); // Mystery Booster add("GN2"); // Game Night 2019 add("HA1"); // Historic Anthology 1 //add("HHO"); // Happy Holidays @@ -534,7 +534,7 @@ public class ScryfallImageSupportCards { add("ONE"); // Phyrexia: All Will Be One add("ONC"); // Phyrexia: All Will Be One Commander add("PL23"); // Year of the Rabbit 2023 - add("DA1"); // Unknown Event + add("UNK"); // Unknown Event add("SIS"); // Shadows of the Past add("SIR"); // Shadows over Innistrad Remastered add("SLP"); // Secret Lair Showdown @@ -683,8 +683,16 @@ public class ScryfallImageSupportCards { // CALC - custom alchemy version of cards. put("CALC/C-Pillar of the Paruns", "https://api.scryfall.com/cards/dis/176/"); + // MB1 + put("MB1/Goblin Trenches", "https://api.scryfall.com/cards/plst/EMA-203/"); + put("MB1/Prophetic Bolt", "https://api.scryfall.com/cards/plst/C15-231/"); + // LTR - 0 number for tokens only - put("LTR/The One Ring/001", "https://api.scryfall.com/cards/ltr/0/"); + // Scryfall has a bug, for some reason this link doesn't work with ?format=image even though it works with ?format=json + // and ?format=text. Base url fails because language is qya and not en and alternate url fails because of this bug + // TODO: This should be reverted when Scryfall fixes the bug + // put("LTR/The One Ring/001", "https://api.scryfall.com/cards/ltr/0/"); + put("LTR/The One Ring/001", "https://api.scryfall.com/cards/ltr/0/qya?format=image"); // REX - double faced lands (xmage uses two diff lands for it) put("REX/Command Tower/26b", "https://api.scryfall.com/cards/rex/26/en?format=image&face=back"); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java index 55602d438ad..9441b6aa677 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java @@ -39,7 +39,8 @@ public class ScryfallImageSupportTokens { putAll(TokenRepository.instance.prepareScryfallDownloadList()); // RIX - put("RIX/City's Blessing", "https://api.scryfall.com/cards/trix/6/en?format=image"); // TODO: missing from tokens data + // TODO: this should be readded when condition tokens are implemented + // put("RIX/City's Blessing", "https://api.scryfall.com/cards/trix/6/en?format=image"); put("RIX/Elemental/1", "https://api.scryfall.com/cards/trix/1/en?format=image"); put("RIX/Elemental/2", "https://api.scryfall.com/cards/trix/2/en?format=image"); put("RIX/Golem", "https://api.scryfall.com/cards/trix/4/en?format=image"); @@ -117,22 +118,6 @@ public class ScryfallImageSupportTokens { put("AKH/Warrior", "https://api.scryfall.com/cards/takh/17/en?format=image"); put("AKH/Wurm", "https://api.scryfall.com/cards/takh/24/en?format=image"); put("AKH/Zombie", "https://api.scryfall.com/cards/takh/20/en?format=image"); - // AKH - embalm ability (token from card) - put("AKH/Angel of Sanctions", "https://api.scryfall.com/cards/takh/1/en?format=image"); - put("AKH/Anointer Priest", "https://api.scryfall.com/cards/takh/2/en?format=image"); - put("AKH/Aven Initiate", "https://api.scryfall.com/cards/takh/3/en?format=image"); - put("AKH/Aven Wind Guide", "https://api.scryfall.com/cards/takh/4/en?format=image"); - put("AKH/Glyph Keeper", "https://api.scryfall.com/cards/takh/5/en?format=image"); - put("AKH/Heart-Piercer Manticore", "https://api.scryfall.com/cards/takh/6/en?format=image"); - put("AKH/Honored Hydra", "https://api.scryfall.com/cards/takh/7/en?format=image"); - put("AKH/Labyrinth Guardian", "https://api.scryfall.com/cards/takh/8/en?format=image"); - put("AKH/Oketra's Attendant", "https://api.scryfall.com/cards/takh/9/en?format=image"); - put("AKH/Sacred Cat", "https://api.scryfall.com/cards/takh/10/en?format=image"); - put("AKH/Tah-Crop Skirmisher", "https://api.scryfall.com/cards/takh/11/en?format=image"); - put("AKH/Temmet, Vizier of Naktamun", "https://api.scryfall.com/cards/takh/12/en?format=image"); - put("AKH/Trueheart Duelist", "https://api.scryfall.com/cards/takh/13/en?format=image"); - put("AKH/Unwavering Initiate", "https://api.scryfall.com/cards/takh/14/en?format=image"); - put("AKH/Vizier of Many Faces", "https://api.scryfall.com/cards/takh/15/en?format=image"); // AER put("AER/Etherium Cell", "https://api.scryfall.com/cards/taer/3/en?format=image"); @@ -501,7 +486,7 @@ public class ScryfallImageSupportTokens { put("ZNC/Elemental/1", "https://api.scryfall.com/cards/tznc/10/en?format=image"); // 5/5 put("ZNC/Elemental/2", "https://api.scryfall.com/cards/tznc/8/en?format=image"); // 2/2 put("ZNC/Faerie Rogue", "https://api.scryfall.com/cards/tznc/3/en?format=image"); - put("ZNC/Germ", "https://api.scryfall.com/cards/tznc/4/en?format=image"); // must be in chest or antology + put("ZNC/Phyrexian Germ", "https://api.scryfall.com/cards/tznc/4/en?format=image"); // must be in chest or antology put("ZNC/Goblin Rogue", "https://api.scryfall.com/cards/tznc/5/en?format=image"); put("ZNC/Kor Ally", "https://api.scryfall.com/cards/tznc/2/en?format=image"); put("ZNC/Rat", "https://api.scryfall.com/cards/tznc/6/en?format=image"); @@ -596,7 +581,6 @@ public class ScryfallImageSupportTokens { put("C21/Beast/1", "https://api.scryfall.com/cards/tc21/10/en?format=image"); // 3/3 put("C21/Beast/2", "https://api.scryfall.com/cards/tc21/11/en?format=image"); // 4/4 put("C21/Boar", "https://api.scryfall.com/cards/tc21/12/en?format=image"); - put("C21/Champion of Wits", "https://api.scryfall.com/cards/tc21/6/en?format=image"); put("C21/Construct/1", "https://api.scryfall.com/cards/tc21/22/en?format=image"); // x/x put("C21/Construct/2", "https://api.scryfall.com/cards/tc21/23/en?format=image"); // 0/0 put("C21/Demon", "https://api.scryfall.com/cards/tc21/7/en?format=image"); @@ -829,17 +813,45 @@ public class ScryfallImageSupportTokens { put("NEC/Thopter", "https://api.scryfall.com/cards/tnec/12/en?format=image"); // SLD + put("SLD/Angel", "https://api.scryfall.com/cards/sld/1340?format=image"); + put("SLD/Cat/1", "https://api.scryfall.com/cards/sld/1517?format=image"); + put("SLD/Cat/2", "https://api.scryfall.com/cards/sld/27?format=image"); + put("SLD/Cat/3", "https://api.scryfall.com/cards/sld/28?format=image"); put("SLD/Clue", "https://api.scryfall.com/cards/sld/348/en?format=image"); + put("SLD/Dog", "https://api.scryfall.com/cards/sld/1516?format=image"); + put("SLD/Egg", "https://api.scryfall.com/cards/sld/1398?format=image"); put("SLD/Faerie Rogue/1", "https://api.scryfall.com/cards/sld/13/en?format=image"); put("SLD/Faerie Rogue/2", "https://api.scryfall.com/cards/sld/14/en?format=image"); put("SLD/Faerie Rogue/3", "https://api.scryfall.com/cards/sld/15/en?format=image"); put("SLD/Faerie Rogue/4", "https://api.scryfall.com/cards/sld/16/en?format=image"); - put("SLD/Treasure", "https://api.scryfall.com/cards/sld/153/en?format=image"); + put("SLD/Food/1", "https://api.scryfall.com/cards/sld/1938?format=image"); + put("SLD/Food/2", "https://api.scryfall.com/cards/sld/2010?format=image"); + put("SLD/Food/3", "https://api.scryfall.com/cards/sld/2011?format=image"); + put("SLD/Food/4", "https://api.scryfall.com/cards/sld/2012?format=image"); + put("SLD/Food/5", "https://api.scryfall.com/cards/sld/2013?format=image"); + put("SLD/Goblin", "https://api.scryfall.com/cards/sld/219?format=image"); + put("SLD/Hydra", "https://api.scryfall.com/cards/sld/1334?format=image"); + put("SLD/Icingdeath, Frost Tongue", "https://api.scryfall.com/cards/sld/1018?format=image"); + put("SLD/Marit Lage", "https://api.scryfall.com/cards/sld/1681?format=image"); + put("SLD/Mechtitan", "https://api.scryfall.com/cards/sld/1969?format=image"); + put("SLD/Saproling", "https://api.scryfall.com/cards/sld/1139?format=image"); + put("SLD/Shrine", "https://api.scryfall.com/cards/sld/1835?format=image"); + put("SLD/Spirit/1", "https://api.scryfall.com/cards/sld/1341?format=image"); + put("SLD/Spirit/2", "https://api.scryfall.com/cards/sld/1852?format=image"); + put("SLD/Squirrel", "https://api.scryfall.com/cards/sld/200?format=image"); + put("SLD/Treasure/1", "https://api.scryfall.com/cards/sld/1432/en?format=image"); + put("SLD/Treasure/2", "https://api.scryfall.com/cards/sld/1736/en?format=image"); + put("SLD/Treasure/3", "https://api.scryfall.com/cards/sld/1507/en?format=image"); + put("SLD/Treasure/4", "https://api.scryfall.com/cards/sld/153/en?format=image"); put("SLD/Walker/1", "https://api.scryfall.com/cards/sld/148/en?format=image"); put("SLD/Walker/2", "https://api.scryfall.com/cards/sld/149/en?format=image"); put("SLD/Walker/3", "https://api.scryfall.com/cards/sld/150/en?format=image"); put("SLD/Walker/4", "https://api.scryfall.com/cards/sld/151/en?format=image"); put("SLD/Walker/5", "https://api.scryfall.com/cards/sld/152/en?format=image"); + put("SLD/Warrior", "https://api.scryfall.com/cards/sld/1752?format=image"); + put("SLD/Wolf", "https://api.scryfall.com/cards/sld/1613?format=image"); + put("SLD/Wurm", "https://api.scryfall.com/cards/sld/1306?format=image"); + put("SLD/Zombie", "https://api.scryfall.com/cards/sld/1357?format=image"); // 2XM put("2XM/Angel", "https://api.scryfall.com/cards/t2xm/3/en?format=image"); @@ -1707,6 +1719,7 @@ public class ScryfallImageSupportTokens { put("CLB/Squid", "https://api.scryfall.com/cards/tclb/29/en?format=image"); put("CLB/Squirrel", "https://api.scryfall.com/cards/tclb/15/en?format=image"); put("CLB/Treasure", "https://api.scryfall.com/cards/tclb/17/en?format=image"); + put("CLB/Undercity", "https://api.scryfall.com/cards/tclb/20/en?format=image"); put("CLB/Volo's Journal", "https://api.scryfall.com/cards/tclb/18/en?format=image"); put("CLB/Warrior", "https://api.scryfall.com/cards/tclb/32/en?format=image"); put("CLB/Emblem Will Kenrith", "https://api.scryfall.com/cards/tclb/50/en?format=image"); @@ -2168,12 +2181,33 @@ public class ScryfallImageSupportTokens { put("WOC/Virtuous", "https://api.scryfall.com/cards/twoc/3/en?format=image"); // WHO + put("WHO/Alien", "https://api.scryfall.com/cards/twho/2?format=image"); put("WHO/Alien Insect", "https://api.scryfall.com/cards/twho/19/en?format=image"); - put("WHO/Human Noble", "https://api.scryfall.com/cards/twho/7/en?format=image"); + put("WHO/Alien Salamander", "https://api.scryfall.com/cards/twho/16?format=image"); + put("WHO/Alien Warrior", "https://api.scryfall.com/cards/twho/14?format=image"); + put("WHO/Beast", "https://api.scryfall.com/cards/twho/17?format=image"); + put("WHO/Clue/1", "https://api.scryfall.com/cards/twho/21?format=image"); + put("WHO/Clue/2", "https://api.scryfall.com/cards/twho/22?format=image"); + put("WHO/Clue/3", "https://api.scryfall.com/cards/twho/23?format=image"); + put("WHO/Dalek", "https://api.scryfall.com/cards/twho/12?format=image"); + put("WHO/Dinosaur", "https://api.scryfall.com/cards/twho/20?format=image"); + put("WHO/Fish", "https://api.scryfall.com/cards/twho/10?format=image"); + put("WHO/Food/1", "https://api.scryfall.com/cards/twho/25?format=image"); + put("WHO/Food/2", "https://api.scryfall.com/cards/twho/26?format=image"); + put("WHO/Food/3", "https://api.scryfall.com/cards/twho/27?format=image"); put("WHO/Horse", "https://api.scryfall.com/cards/twho/4/en?format=image"); + put("WHO/Human", "https://api.scryfall.com/cards/twho/5?format=image"); + put("WHO/Human Noble", "https://api.scryfall.com/cards/twho/7/en?format=image"); + put("WHO/Mark of the Rani", "https://api.scryfall.com/cards/twho/15?format=image"); + put("WHO/Soldier", "https://api.scryfall.com/cards/twho/8?format=image"); + put("WHO/Treasure/1", "https://api.scryfall.com/cards/twho/28?format=image"); + put("WHO/Treasure/2", "https://api.scryfall.com/cards/twho/29?format=image"); + put("WHO/Treasure/3", "https://api.scryfall.com/cards/twho/30?format=image"); + put("WHO/Treasure/4", "https://api.scryfall.com/cards/twho/31?format=image"); + put("WHO/Warrior", "https://api.scryfall.com/cards/twho/9?format=image"); // 8ED - put("8ED/Rukh", "https://api.scryfall.com/cards/p03/7/en?format=image"); + put("8ED/Bird", "https://api.scryfall.com/cards/p03/7/en?format=image"); // LCI put("LCI/Angel", "https://api.scryfall.com/cards/tlci/2/en?format=image"); @@ -2494,7 +2528,18 @@ public class ScryfallImageSupportTokens { put("BLC/Wolf/2", "https://api.scryfall.com/cards/tblc/32/en?format=image"); // DSK + put("DSK/Beast", "https://api.scryfall.com/cards/tdsk/3?format=image"); put("DSK/Emblem Kaito", "https://api.scryfall.com/cards/tdsk/17/en?format=image"); + put("DSK/Everywhere", "https://api.scryfall.com/cards/tdsk/16?format=image"); + put("DSK/Glimmer", "https://api.scryfall.com/cards/tdsk/4?format=image"); + put("DSK/Gremlin", "https://api.scryfall.com/cards/tdsk/11?format=image"); + put("DSK/Insect/1", "https://api.scryfall.com/cards/tdsk/13?format=image"); + put("DSK/Insect/2", "https://api.scryfall.com/cards/tdsk/5?format=image"); + put("DSK/Primo, the Indivisible", "https://api.scryfall.com/cards/tdsk/14?format=image"); + put("DSK/Shard", "https://api.scryfall.com/cards/tdsk/2?format=image"); + put("DSK/Spider", "https://api.scryfall.com/cards/tdsk/12?format=image"); + put("DSK/Spirit", "https://api.scryfall.com/cards/tdsk/8?format=image"); + put("DSK/Treasure", "https://api.scryfall.com/cards/tdsk/15?format=image"); // DSC put("DSC/Angel", "https://api.scryfall.com/cards/tdsc/2/en?format=image"); @@ -2633,18 +2678,25 @@ public class ScryfallImageSupportTokens { // TDC put("TDC/Angel", "https://api.scryfall.com/cards/ttdc/2/en?format=image"); + put("TDC/Beast", "https://api.scryfall.com/cards/ttdc/20?format=image"); put("TDC/Citizen", "https://api.scryfall.com/cards/ttdc/26/en?format=image"); put("TDC/Dog", "https://api.scryfall.com/cards/ttdc/3/en?format=image"); + put("TDC/Dragon/1", "https://api.scryfall.com/cards/ttdc/13?format=image"); + put("TDC/Dragon/2", "https://api.scryfall.com/cards/ttdc/14?format=image"); + put("TDC/Dragon Egg", "https://api.scryfall.com/cards/ttdc/12?format=image"); put("TDC/Dragon Illusion", "https://api.scryfall.com/cards/ttdc/15/en?format=image"); put("TDC/Eldrazi", "https://api.scryfall.com/cards/ttdc/1/en?format=image"); put("TDC/Elemental/1", "https://api.scryfall.com/cards/ttdc/16/en?format=image"); put("TDC/Elemental/2", "https://api.scryfall.com/cards/ttdc/17/en?format=image"); put("TDC/Elemental/3", "https://api.scryfall.com/cards/ttdc/27/en?format=image"); put("TDC/First Mate Ragavan", "https://api.scryfall.com/cards/ttdc/18/en?format=image"); + put("TDC/Frog Lizard", "https://api.scryfall.com/cards/ttdc/21?format=image"); put("TDC/Goat", "https://api.scryfall.com/cards/ttdc/4/en?format=image"); put("TDC/Gold", "https://api.scryfall.com/cards/ttdc/29/en?format=image"); put("TDC/Human", "https://api.scryfall.com/cards/ttdc/5/en?format=image"); + put("TDC/Inkling", "https://api.scryfall.com/cards/ttdc/28?format=image"); put("TDC/Insect", "https://api.scryfall.com/cards/ttdc/22/en?format=image"); + put("TDC/Karox Bladewing", "https://api.scryfall.com/cards/ttdc/19?format=image"); put("TDC/Myr", "https://api.scryfall.com/cards/ttdc/30/en?format=image"); put("TDC/Plant", "https://api.scryfall.com/cards/ttdc/24/en?format=image"); put("TDC/Rat", "https://api.scryfall.com/cards/ttdc/9/en?format=image"); @@ -2652,9 +2704,136 @@ public class ScryfallImageSupportTokens { put("TDC/Servo", "https://api.scryfall.com/cards/ttdc/31/en?format=image"); put("TDC/Snake", "https://api.scryfall.com/cards/ttdc/10/en?format=image"); put("TDC/Soldier", "https://api.scryfall.com/cards/ttdc/32/en?format=image"); + put("TDC/Spider", "https://api.scryfall.com/cards/ttdc/25?format=image"); put("TDC/Spirit", "https://api.scryfall.com/cards/ttdc/6/en?format=image"); put("TDC/Thopter", "https://api.scryfall.com/cards/ttdc/33/en?format=image"); + // ACR + put("ACR/Assassin", "https://api.scryfall.com/cards/tacr/4?format=image"); + put("ACR/Emblem Capitoline Triad", "https://api.scryfall.com/cards/tacr/7/en?format=image"); + put("ACR/Human Rogue", "https://api.scryfall.com/cards/tacr/3?format=image"); + put("ACR/Phobos", "https://api.scryfall.com/cards/tacr/5?format=image"); + put("ACR/Shapeshifter", "https://api.scryfall.com/cards/tacr/2?format=image"); + put("ACR/Treasure", "https://api.scryfall.com/cards/tacr/6?format=image"); + + // DD2 + put("DD2/Elemental Shaman", "https://api.scryfall.com/cards/tdd2/1?format=image"); + + // FIN + put("FIN/Food", "https://api.scryfall.com/cards/tfin/22?format=image"); + + // JVC + put("JVC/Elemental Shaman", "https://api.scryfall.com/cards/tjvc/4?format=image"); + + // PIP + put("PIP/Alien", "https://api.scryfall.com/cards/tpip/6?format=image"); + put("PIP/Clue", "https://api.scryfall.com/cards/tpip/11?format=image"); + put("PIP/Food/1", "https://api.scryfall.com/cards/tpip/12?format=image"); + put("PIP/Food/2", "https://api.scryfall.com/cards/tpip/13?format=image"); + put("PIP/Food/3", "https://api.scryfall.com/cards/tpip/14?format=image"); + put("PIP/Human Knight", "https://api.scryfall.com/cards/tpip/2?format=image"); + put("PIP/Human Soldier", "https://api.scryfall.com/cards/tpip/3?format=image"); + put("PIP/Junk", "https://api.scryfall.com/cards/tpip/15?format=image"); + put("PIP/Robot", "https://api.scryfall.com/cards/tpip/16?format=image"); + put("PIP/Settlement", "https://api.scryfall.com/cards/tpip/8?format=image"); + put("PIP/Soldier/1", "https://api.scryfall.com/cards/tpip/10?format=image"); + put("PIP/Soldier/2", "https://api.scryfall.com/cards/tpip/4?format=image"); + put("PIP/Squirrel", "https://api.scryfall.com/cards/tpip/9?format=image"); + put("PIP/Thopter", "https://api.scryfall.com/cards/tpip/17?format=image"); + put("PIP/Treasure/1", "https://api.scryfall.com/cards/tpip/18?format=image"); + put("PIP/Treasure/2", "https://api.scryfall.com/cards/tpip/19?format=image"); + put("PIP/Warrior", "https://api.scryfall.com/cards/tpip/5?format=image"); + put("PIP/Wasteland Survival Guide", "https://api.scryfall.com/cards/tpip/20?format=image"); + put("PIP/Zombie Mutant", "https://api.scryfall.com/cards/tpip/7?format=image"); + + // REX + put("REX/Dinosaur", "https://api.scryfall.com/cards/trex/1?format=image"); + put("REX/Treasure", "https://api.scryfall.com/cards/trex/2?format=image"); + + // UGL + put("UGL/Goblin", "https://api.scryfall.com/cards/tugl/4?format=image"); + put("UGL/Pegasus", "https://api.scryfall.com/cards/tugl/1?format=image"); + put("UGL/Soldier", "https://api.scryfall.com/cards/tugl/2?format=image"); + put("UGL/Squirrel", "https://api.scryfall.com/cards/tugl/6?format=image"); + put("UGL/Zombie", "https://api.scryfall.com/cards/tugl/3?format=image"); + + // UST + put("UST/Angel", "https://api.scryfall.com/cards/tust/1?format=image"); + put("UST/Beast", "https://api.scryfall.com/cards/tust/13?format=image"); + put("UST/Brainiac", "https://api.scryfall.com/cards/tust/10?format=image"); + put("UST/Clue", "https://api.scryfall.com/cards/tust/18?format=image"); + put("UST/Dragon", "https://api.scryfall.com/cards/tust/16?format=image"); + put("UST/Elemental/1", "https://api.scryfall.com/cards/tust/11?format=image"); + put("UST/Elemental/2", "https://api.scryfall.com/cards/tust/17?format=image"); + put("UST/Gnome", "https://api.scryfall.com/cards/tust/20?format=image"); + put("UST/Goat", "https://api.scryfall.com/cards/tust/2?format=image"); + put("UST/Goblin", "https://api.scryfall.com/cards/tust/12?format=image"); + put("UST/Saproling", "https://api.scryfall.com/cards/tust/14?format=image"); + put("UST/Spirit", "https://api.scryfall.com/cards/tust/3?format=image"); + put("UST/Squirrel", "https://api.scryfall.com/cards/tust/15?format=image"); + put("UST/Storm Crow", "https://api.scryfall.com/cards/tust/5?format=image"); + put("UST/Thopter", "https://api.scryfall.com/cards/tust/6?format=image"); + put("UST/Vampire", "https://api.scryfall.com/cards/tust/8?format=image"); + put("UST/Zombie", "https://api.scryfall.com/cards/tust/9?format=image"); + + // F12 + put("F12/Human", "https://api.scryfall.com/cards/f12/1a?format=image"); + put("F12/Wolf", "https://api.scryfall.com/cards/f12/1a?format=image&face=back"); + + // F17 + put("F17/Dinosaur", "https://api.scryfall.com/cards/f17/11?format=image"); + put("F17/Pirate", "https://api.scryfall.com/cards/f17/12?format=image"); + put("F17/Vampire", "https://api.scryfall.com/cards/f17/10?format=image"); + put("F17/Treasure/1", "https://api.scryfall.com/cards/f17/11?format=image&face=back"); + put("F17/Treasure/2", "https://api.scryfall.com/cards/f17/12?format=image&face=back"); + put("F17/Treasure/3", "https://api.scryfall.com/cards/f17/10?format=image&face=back"); + + // HHO + put("HHO/Treasure", "https://api.scryfall.com/cards/hho/21★?format=image"); + + // J12 + put("J12/Centaur", "https://api.scryfall.com/cards/j12/9?format=image"); + + // J13 + put("J13/Golem", "https://api.scryfall.com/cards/j13/9?format=image"); + + // MPR + put("MPR/Bear", "https://api.scryfall.com/cards/mpr/7?format=image"); + put("MPR/Beast", "https://api.scryfall.com/cards/mpr/8?format=image"); + put("MPR/Bird", "https://api.scryfall.com/cards/mpr/4?format=image"); + put("MPR/Elephant", "https://api.scryfall.com/cards/mpr/3?format=image"); + put("MPR/Goblin Soldier", "https://api.scryfall.com/cards/mpr/6?format=image"); + put("MPR/Saproling", "https://api.scryfall.com/cards/mpr/2?format=image"); + put("MPR/Spirit", "https://api.scryfall.com/cards/mpr/5?format=image"); + + // P03 + put("P03/Bear", "https://api.scryfall.com/cards/p03/4?format=image"); + put("P03/Demon", "https://api.scryfall.com/cards/p03/6?format=image"); + put("P03/Goblin", "https://api.scryfall.com/cards/p03/5?format=image"); + put("P03/Insect", "https://api.scryfall.com/cards/p03/2?format=image"); + put("P03/Bird", "https://api.scryfall.com/cards/p03/7?format=image"); + put("P03/Sliver", "https://api.scryfall.com/cards/p03/3?format=image"); + + // P04 + put("P04/Angel", "https://api.scryfall.com/cards/p04/2?format=image"); + put("P04/Beast", "https://api.scryfall.com/cards/p04/5?format=image"); + put("P04/Myr", "https://api.scryfall.com/cards/p04/4?format=image"); + put("P04/Pentavite", "https://api.scryfall.com/cards/p04/3?format=image"); + put("P04/Spirit", "https://api.scryfall.com/cards/p04/6?format=image"); + + // PEMN + put("PEMN/Zombie/1", "https://api.scryfall.com/cards/pemn/1Z?format=image"); + put("PEMN/Zombie/2", "https://api.scryfall.com/cards/pemn/1Z?format=image&face=back"); + + // PHEL + put("PHEL/Angel", "https://api.scryfall.com/cards/phel/1★?format=image"); + + // PL21 + put("PL21/Minotaur", "https://api.scryfall.com/cards/pl21/2★?format=image"); + + // PL23 + put("PL23/Food", "https://api.scryfall.com/cards/pl23/2?format=image"); + // generate supported sets supportedSets.clear(); for (String cardName : this.keySet()) { diff --git a/Mage.Client/src/test/java/mage/client/game/ScryfallImagesDownloadTest.java b/Mage.Client/src/test/java/mage/client/game/ScryfallImagesDownloadTest.java index 003db308954..25b1455fe36 100644 --- a/Mage.Client/src/test/java/mage/client/game/ScryfallImagesDownloadTest.java +++ b/Mage.Client/src/test/java/mage/client/game/ScryfallImagesDownloadTest.java @@ -43,7 +43,7 @@ public class ScryfallImagesDownloadTest { .anyMatch(c -> c.getCardNumber().equals("001")) ); urls = imageSource.generateCardUrl(new CardDownloadData("The One Ring", "LTR", "001", false, 0)); - Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/en?format=image", urls.getBaseUrl()); + Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/qya?format=image", urls.getBaseUrl()); // added same tests for small images @@ -74,6 +74,6 @@ public class ScryfallImagesDownloadTest { .anyMatch(c -> c.getCardNumber().equals("001")) ); urls = imageSourceSmall.generateCardUrl(new CardDownloadData("The One Ring", "LTR", "001", false, 0)); - Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/en?format=image&version=small", urls.getBaseUrl()); + Assert.assertEquals("https://api.scryfall.com/cards/ltr/0/qya?format=image&version=small", urls.getBaseUrl()); } } diff --git a/Mage.Sets/src/mage/sets/AetherdriftCommander.java b/Mage.Sets/src/mage/sets/AetherdriftCommander.java index 5707ceedfc3..5cb696fed4c 100644 --- a/Mage.Sets/src/mage/sets/AetherdriftCommander.java +++ b/Mage.Sets/src/mage/sets/AetherdriftCommander.java @@ -118,7 +118,7 @@ public final class AetherdriftCommander extends ExpansionSet { cards.add(new SetCardInfo("Maskwood Nexus", 132, Rarity.RARE, mage.cards.m.MaskwoodNexus.class)); cards.add(new SetCardInfo("Midnight Clock", 79, Rarity.RARE, mage.cards.m.MidnightClock.class)); cards.add(new SetCardInfo("Midnight Reaper", 44, Rarity.RARE, mage.cards.m.MidnightReaper.class)); - cards.add(new SetCardInfo("Murderous Rider // Swift End", 45, Rarity.RARE, mage.cards.m.MurderousRider.class)); + cards.add(new SetCardInfo("Murderous Rider", 45, Rarity.RARE, mage.cards.m.MurderousRider.class)); cards.add(new SetCardInfo("Never // Return", 96, Rarity.RARE, mage.cards.n.NeverReturn.class)); cards.add(new SetCardInfo("Nissa, Worldsoul Speaker", 13, Rarity.RARE, mage.cards.n.NissaWorldsoulSpeaker.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Nissa, Worldsoul Speaker", 29, Rarity.RARE, mage.cards.n.NissaWorldsoulSpeaker.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java b/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java index 1454b11516d..e867776dd97 100644 --- a/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java +++ b/Mage.Sets/src/mage/sets/MediaAndCollaborationPromos.java @@ -22,37 +22,69 @@ public class MediaAndCollaborationPromos extends ExpansionSet { // some cards are non-English (most are Japanese, Jamuraan Lion has a German printing), but it's ok - scryfall can download it - cards.add(new SetCardInfo("Archangel", 29, Rarity.RARE, mage.cards.a.Archangel.class)); - cards.add(new SetCardInfo("Ascendant Evincar", 28, Rarity.RARE, mage.cards.a.AscendantEvincar.class)); - cards.add(new SetCardInfo("Blue Elemental Blast", 5, Rarity.COMMON, mage.cards.b.BlueElementalBlast.class)); - cards.add(new SetCardInfo("Cast Down", 30, Rarity.UNCOMMON, mage.cards.c.CastDown.class)); - cards.add(new SetCardInfo("Chandra's Outrage", 18, Rarity.COMMON, mage.cards.c.ChandrasOutrage.class)); - cards.add(new SetCardInfo("Chandra's Spitfire", 19, Rarity.UNCOMMON, mage.cards.c.ChandrasSpitfire.class)); - cards.add(new SetCardInfo("Cunning Sparkmage", 17, Rarity.UNCOMMON, mage.cards.c.CunningSparkmage.class)); - cards.add(new SetCardInfo("Darksteel Juggernaut", 16, Rarity.RARE, mage.cards.d.DarksteelJuggernaut.class)); - cards.add(new SetCardInfo("Daxos, Blessed by the Sun", 36, Rarity.UNCOMMON, mage.cards.d.DaxosBlessedByTheSun.class)); - cards.add(new SetCardInfo("Diabolic Edict", 31, Rarity.RARE, mage.cards.d.DiabolicEdict.class)); - cards.add(new SetCardInfo("Duress", 34, Rarity.RARE, mage.cards.d.Duress.class)); - cards.add(new SetCardInfo("Fireball", 4, Rarity.COMMON, mage.cards.f.Fireball.class)); - cards.add(new SetCardInfo("Jamuraan Lion", "10*", Rarity.COMMON, mage.cards.j.JamuraanLion.class)); - cards.add(new SetCardInfo("Kuldotha Phoenix", 20, Rarity.RARE, mage.cards.k.KuldothaPhoenix.class)); - cards.add(new SetCardInfo("Lava Coil", 33, Rarity.UNCOMMON, mage.cards.l.LavaCoil.class)); - cards.add(new SetCardInfo("Lightning Hounds", 10, Rarity.COMMON, mage.cards.l.LightningHounds.class)); - cards.add(new SetCardInfo("Parallax Dementia", 27, Rarity.COMMON, mage.cards.p.ParallaxDementia.class)); - cards.add(new SetCardInfo("Phantasmal Dragon", 21, Rarity.UNCOMMON, mage.cards.p.PhantasmalDragon.class)); - cards.add(new SetCardInfo("Phyrexian Rager", 14, Rarity.COMMON, mage.cards.p.PhyrexianRager.class)); - cards.add(new SetCardInfo("Sandbar Crocodile", 22, Rarity.COMMON, mage.cards.s.SandbarCrocodile.class)); - cards.add(new SetCardInfo("Scent of Cinder", 9, Rarity.COMMON, mage.cards.s.ScentOfCinder.class)); - cards.add(new SetCardInfo("Shivan Dragon", 15, Rarity.RARE, mage.cards.s.ShivanDragon.class)); - cards.add(new SetCardInfo("Shock", 32, Rarity.RARE, mage.cards.s.Shock.class)); - cards.add(new SetCardInfo("Shrieking Drake", 24, Rarity.COMMON, mage.cards.s.ShriekingDrake.class)); - cards.add(new SetCardInfo("Silver Drake", 13, Rarity.COMMON, mage.cards.s.SilverDrake.class)); - cards.add(new SetCardInfo("Spined Wurm", 11, Rarity.COMMON, mage.cards.s.SpinedWurm.class)); - cards.add(new SetCardInfo("Staggering Insight", 37, Rarity.RARE, mage.cards.s.StaggeringInsight.class)); - cards.add(new SetCardInfo("Stream of Life", 25, Rarity.COMMON, mage.cards.s.StreamOfLife.class)); - cards.add(new SetCardInfo("Thorn Elemental", 26, Rarity.RARE, mage.cards.t.ThornElemental.class)); - cards.add(new SetCardInfo("Voltaic Key", 35, Rarity.RARE, mage.cards.v.VoltaicKey.class)); - cards.add(new SetCardInfo("Warmonger", 12, Rarity.UNCOMMON, mage.cards.w.Warmonger.class)); - cards.add(new SetCardInfo("Zhalfirin Knight", 23, Rarity.COMMON, mage.cards.z.ZhalfirinKnight.class)); + cards.add(new SetCardInfo("Ajani, Mentor of Heroes", "2024-3", Rarity.MYTHIC, mage.cards.a.AjaniMentorOfHeroes.class)); + cards.add(new SetCardInfo("Ancestral Mask", "2025-2", Rarity.RARE, mage.cards.a.AncestralMask.class)); + cards.add(new SetCardInfo("Archangel", "2000-4", Rarity.RARE, mage.cards.a.Archangel.class)); + cards.add(new SetCardInfo("Ascendant Evincar", "2000-7", Rarity.RARE, mage.cards.a.AscendantEvincar.class)); + cards.add(new SetCardInfo("Avalanche Riders", "2023-5", Rarity.RARE, mage.cards.a.AvalancheRiders.class)); + cards.add(new SetCardInfo("Blue Elemental Blast", "1995-2", Rarity.COMMON, mage.cards.b.BlueElementalBlast.class)); + cards.add(new SetCardInfo("Bone Shredder", "2021-2", Rarity.RARE, mage.cards.b.BoneShredder.class)); + cards.add(new SetCardInfo("Cast Down", "2019-1", Rarity.UNCOMMON, mage.cards.c.CastDown.class)); + cards.add(new SetCardInfo("Chandra's Outrage", "2010-3", Rarity.COMMON, mage.cards.c.ChandrasOutrage.class)); + cards.add(new SetCardInfo("Chandra's Spitfire", "2010-4", Rarity.UNCOMMON, mage.cards.c.ChandrasSpitfire.class)); + cards.add(new SetCardInfo("Counterspell", "2021-1", Rarity.RARE, mage.cards.c.Counterspell.class)); + cards.add(new SetCardInfo("Crop Rotation", "2020-7ww", Rarity.RARE, mage.cards.c.CropRotation.class)); + cards.add(new SetCardInfo("Culling the Weak", "2023-8", Rarity.RARE, mage.cards.c.CullingTheWeak.class)); + cards.add(new SetCardInfo("Cunning Sparkmage", "2010-2", Rarity.UNCOMMON, mage.cards.c.CunningSparkmage.class)); + cards.add(new SetCardInfo("Dark Ritual", "2020-4", Rarity.RARE, mage.cards.d.DarkRitual.class)); + cards.add(new SetCardInfo("Darksteel Juggernaut", "2010-1", Rarity.RARE, mage.cards.d.DarksteelJuggernaut.class)); + cards.add(new SetCardInfo("Daxos, Blessed by the Sun", "2020-2", Rarity.UNCOMMON, mage.cards.d.DaxosBlessedByTheSun.class)); + cards.add(new SetCardInfo("Diabolic Edict", "2024-5", Rarity.RARE, mage.cards.d.DiabolicEdict.class)); + cards.add(new SetCardInfo("Disenchant", "2022-1", Rarity.RARE, mage.cards.d.Disenchant.class)); + cards.add(new SetCardInfo("Duress", "2025-7", Rarity.RARE, mage.cards.d.Duress.class)); + cards.add(new SetCardInfo("Fireball", "1995-1", Rarity.COMMON, mage.cards.f.Fireball.class)); + cards.add(new SetCardInfo("Frantic Search", "2022-4", Rarity.RARE, mage.cards.f.FranticSearch.class)); + cards.add(new SetCardInfo("Gingerbrute", "2023-3", Rarity.RARE, mage.cards.g.Gingerbrute.class)); + cards.add(new SetCardInfo("Gush", "2024-4", Rarity.RARE, mage.cards.g.Gush.class)); + cards.add(new SetCardInfo("Harald, King of Skemfar", "2021-3", Rarity.RARE, mage.cards.h.HaraldKingOfSkemfar.class)); + cards.add(new SetCardInfo("Heliod's Pilgrim", "2020-6", Rarity.RARE, mage.cards.h.HeliodsPilgrim.class)); + cards.add(new SetCardInfo("Hypnotic Sprite", "2019-5", Rarity.RARE, mage.cards.h.HypnoticSprite.class)); + cards.add(new SetCardInfo("Jace Beleren", "2009-1", Rarity.MYTHIC, mage.cards.j.JaceBeleren.class)); + cards.add(new SetCardInfo("Jace, Memory Adept", "2024-2", Rarity.MYTHIC, mage.cards.j.JaceMemoryAdept.class)); + cards.add(new SetCardInfo("Jamuraan Lion", "1996-3", Rarity.COMMON, mage.cards.j.JamuraanLion.class)); + cards.add(new SetCardInfo("Kuldotha Phoenix", "2010-5", Rarity.RARE, mage.cards.k.KuldothaPhoenix.class)); + cards.add(new SetCardInfo("Lava Coil", "2019-4", Rarity.UNCOMMON, mage.cards.l.LavaCoil.class)); + cards.add(new SetCardInfo("Lightning Hounds", "2000-1", Rarity.COMMON, mage.cards.l.LightningHounds.class)); + cards.add(new SetCardInfo("Liliana of the Dark Realms", "2024-8", Rarity.MYTHIC, mage.cards.l.LilianaOfTheDarkRealms.class)); + cards.add(new SetCardInfo("Mental Misstep", "2023-1", Rarity.RARE, mage.cards.m.MentalMisstep.class)); + cards.add(new SetCardInfo("Nicol Bolas, Planeswalker", "2025-10", Rarity.MYTHIC, mage.cards.n.NicolBolasPlaneswalker.class)); + cards.add(new SetCardInfo("Parallax Dementia", "2000-6", Rarity.COMMON, mage.cards.p.ParallaxDementia.class)); + cards.add(new SetCardInfo("Patchwork Banner", "2024-7", Rarity.RARE, mage.cards.p.PatchworkBanner.class)); + cards.add(new SetCardInfo("Phantasmal Dragon", "2011-1", Rarity.UNCOMMON, mage.cards.p.PhantasmalDragon.class)); + cards.add(new SetCardInfo("Phyrexian Rager", "2000-5", Rarity.COMMON, mage.cards.p.PhyrexianRager.class)); + cards.add(new SetCardInfo("Pyromancer's Gauntlet", "2023-6", Rarity.RARE, mage.cards.p.PyromancersGauntlet.class)); + cards.add(new SetCardInfo("Ruin Crab", "2023-4", Rarity.RARE, mage.cards.r.RuinCrab.class)); + cards.add(new SetCardInfo("Sandbar Crocodile", "1996-1", Rarity.COMMON, mage.cards.s.SandbarCrocodile.class)); + cards.add(new SetCardInfo("Scent of Cinder", "1999-1", Rarity.COMMON, mage.cards.s.ScentOfCinder.class)); + cards.add(new SetCardInfo("Shield Wall", "1997-3", Rarity.COMMON, mage.cards.s.ShieldWall.class)); + cards.add(new SetCardInfo("Shivan Dragon", "2001-2", Rarity.RARE, mage.cards.s.ShivanDragon.class)); + cards.add(new SetCardInfo("Shock", "2025-1", Rarity.RARE, mage.cards.s.Shock.class)); + cards.add(new SetCardInfo("Shrieking Drake", "1997-2", Rarity.COMMON, mage.cards.s.ShriekingDrake.class)); + cards.add(new SetCardInfo("Silver Drake", "2000-2", Rarity.COMMON, mage.cards.s.SilverDrake.class)); + cards.add(new SetCardInfo("Snuff Out", "2024-1", Rarity.RARE, mage.cards.s.SnuffOut.class)); + cards.add(new SetCardInfo("Spined Wurm", "2001-1", Rarity.COMMON, mage.cards.s.SpinedWurm.class)); + cards.add(new SetCardInfo("Sprite Dragon", "2020-5", Rarity.RARE, mage.cards.s.SpriteDragon.class)); + cards.add(new SetCardInfo("Staggering Insight", "2020-3", Rarity.RARE, mage.cards.s.StaggeringInsight.class)); + cards.add(new SetCardInfo("Stream of Life", "1997-4", Rarity.COMMON, mage.cards.s.StreamOfLife.class)); + cards.add(new SetCardInfo("Talruum Champion", "1997-1", Rarity.COMMON, mage.cards.t.TalruumChampion.class)); + cards.add(new SetCardInfo("Tangled Florahedron", "2020-8", Rarity.UNCOMMON, mage.cards.t.TangledFlorahedron.class)); + cards.add(new SetCardInfo("Thorn Elemental", "2000-3", Rarity.RARE, mage.cards.t.ThornElemental.class)); + cards.add(new SetCardInfo("Usher of the Fallen", "2022-3", Rarity.RARE, mage.cards.u.UsherOfTheFallen.class)); + cards.add(new SetCardInfo("Voltaic Key", "2024-6", Rarity.RARE, mage.cards.v.VoltaicKey.class)); + cards.add(new SetCardInfo("Warmonger", "1999-2", Rarity.UNCOMMON, mage.cards.w.Warmonger.class)); + cards.add(new SetCardInfo("Wild Growth", "2022-2", Rarity.RARE, mage.cards.w.WildGrowth.class)); + cards.add(new SetCardInfo("Winged Boots", "2023-7", Rarity.RARE, mage.cards.w.WingedBoots.class)); + cards.add(new SetCardInfo("Worn Powerstone", "2023-2", Rarity.RARE, mage.cards.w.WornPowerstone.class)); + cards.add(new SetCardInfo("Zhalfirin Knight", "1996-2", Rarity.COMMON, mage.cards.z.ZhalfirinKnight.class)); } } diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index a4275e34e4c..0b77e47469c 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -178,6 +178,7 @@ |Generate|DUNGEON:AFR|Tomb of Annihilation|||TombOfAnnihilationDungeon| |Generate|DUNGEON:AFR|Lost Mine of Phandelver|||LostMineOfPhandelverDungeon| |Generate|DUNGEON:AFR|Dungeon of the Mad Mage|||DungeonOfTheMadMageDungeon| +|Generate|DUNGEON:CLB|Undercity|||UndercityDungeon| # ALL TOKENS # Usage hints: @@ -209,23 +210,8 @@ |Generate|TOK:AKH|Warrior|||WarriorVigilantToken| |Generate|TOK:AKH|Wurm|||Wurm55Token| |Generate|TOK:AKH|Zombie|||ZombieToken| -#TOK:AKH - some tokens from real cards (see Embalm ability) -#|Generate|TOK:AKH|Angel of Sanctions|| -#|Generate|TOK:AKH|Anointer Priest|| -#|Generate|TOK:AKH|Aven Initiate|| -#|Generate|TOK:AKH|Aven Wind Guide|| -#|Generate|TOK:AKH|Glyph Keeper|| -#|Generate|TOK:AKH|Heart-Piercer Manticore|| -#|Generate|TOK:AKH|Honored Hydra|| -#|Generate|TOK:AKH|Labyrinth Guardian|| -#|Generate|TOK:AKH|Oketra's Attendant|| -#|Generate|TOK:AKH|Sacred Cat|| -#|Generate|TOK:AKH|Tah-Crop Skirmisher|| -#|Generate|TOK:AKH|Temmet, Vizier of Naktamun|| -#|Generate|TOK:AKH|Trueheart Duelist|| -#|Generate|TOK:AKH|Unwavering Initiate|| -#|Generate|TOK:AKH|Vizier of Many Faces|| +# ALA |Generate|TOK:ALA|Beast|||GodSireBeastToken| |Generate|TOK:ALA|Dragon|||DragonToken| |Generate|TOK:ALA|Goblin|||GoblinToken| @@ -236,10 +222,14 @@ |Generate|TOK:ALA|Soldier|||SoldierToken| |Generate|TOK:ALA|Thopter|||ThopterToken| |Generate|TOK:ALA|Zombie|||ZombieToken| + +# ARB |Generate|TOK:ARB|Bird Soldier|||BirdSoldierToken| |Generate|TOK:ARB|Dragon|||DragonBroodmotherDragonToken| |Generate|TOK:ARB|Lizard|||LizardToken| -|Generate|TOK:ARN|Djinn|||DjinnToken| +|Generate|TOK:ARB|Zombie Wizard|||ZombieWizardToken| + +# AVR |Generate|TOK:AVR|Angel|||AngelToken| |Generate|TOK:AVR|Demon|||DemonToken| |Generate|TOK:AVR|Human|1||ThatcherHumanToken| @@ -247,17 +237,21 @@ |Generate|TOK:AVR|Spirit|1||SpiritBlueToken| |Generate|TOK:AVR|Spirit|2||SpiritWhiteToken| |Generate|TOK:AVR|Zombie|||ZombieToken| + +# BFZ |Generate|TOK:BFZ|Dragon|||DragonToken2| +|Generate|TOK:BFZ|Eldrazi|||EldraziToken| |Generate|TOK:BFZ|Eldrazi Scion|1||EldraziScionToken| |Generate|TOK:BFZ|Eldrazi Scion|2||EldraziScionToken| |Generate|TOK:BFZ|Eldrazi Scion|3||EldraziScionToken| -|Generate|TOK:BFZ|Eldrazi|||EldraziToken| |Generate|TOK:BFZ|Elemental|1||OmnathElementalToken| |Generate|TOK:BFZ|Elemental|2||Elemental31TrampleHasteToken| |Generate|TOK:BFZ|Knight Ally|||KnightAllyToken| |Generate|TOK:BFZ|Kor Ally|||KorAllyToken| |Generate|TOK:BFZ|Octopus|||OctopusToken| |Generate|TOK:BFZ|Plant|||Plant11Token| + +# BNG |Generate|TOK:BNG|Bird|1||EnchantmentBirdToken| |Generate|TOK:BNG|Bird|2||BirdToken| |Generate|TOK:BNG|Cat Soldier|||CatSoldierCreatureToken| @@ -268,6 +262,8 @@ |Generate|TOK:BNG|Soldier|||GodFavoredGeneralSoldierToken| |Generate|TOK:BNG|Wolf|||WolfToken| |Generate|TOK:BNG|Zombie|||ForlornPseudammaZombieToken| + +# C14 |Generate|TOK:C14|Angel|||AngelToken| |Generate|TOK:C14|Ape|||ApeToken| |Generate|TOK:C14|Beast|1||BeastToken| @@ -281,7 +277,6 @@ |Generate|TOK:C14|Elf Warrior|||ElfWarriorToken| |Generate|TOK:C14|Fish|||ReefWormFishToken| |Generate|TOK:C14|Gargoyle|||GargoyleToken| -|Generate|TOK:C14|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:C14|Goat|||GoatToken| |Generate|TOK:C14|Goblin|||GoblinToken| |Generate|TOK:C14|Horror|||HorrorXXBlackToken| @@ -290,6 +285,9 @@ |Generate|TOK:C14|Myr|||MyrToken| |Generate|TOK:C14|Pegasus|||PegasusToken| |Generate|TOK:C14|Pentavite|||PentaviteToken| +|Generate|TOK:C14|Phyrexian Germ|||PhyrexianGermToken| +|Generate|TOK:C14|Phyrexian Wurm|1||WurmWithDeathtouchToken| +|Generate|TOK:C14|Phyrexian Wurm|2||WurmWithLifelinkToken| |Generate|TOK:C14|Soldier|||SoldierToken| |Generate|TOK:C14|Spirit|||SpiritWhiteToken| |Generate|TOK:C14|Stoneforged Blade|||NahiriTheLithomancerEquipmentToken| @@ -297,10 +295,10 @@ |Generate|TOK:C14|Tuktuk the Returned|||TuktukTheReturnedToken| |Generate|TOK:C14|Whale|||ReefWormWhaleToken| |Generate|TOK:C14|Wolf|||WolfToken| -|Generate|TOK:C14|Phyrexian Wurm|1||WurmWithDeathtouchToken| -|Generate|TOK:C14|Phyrexian Wurm|2||WurmWithLifelinkToken| |Generate|TOK:C14|Zombie|1||ZombieToken| |Generate|TOK:C14|Zombie|2||StitcherGeralfZombieToken| + +# C15 |Generate|TOK:C15|Angel|||AngelToken| |Generate|TOK:C15|Bear|||BearToken| |Generate|TOK:C15|Beast|||BeastToken2| @@ -311,11 +309,11 @@ |Generate|TOK:C15|Elemental Shaman|||ElementalShamanToken| |Generate|TOK:C15|Elephant|||ElephantToken| |Generate|TOK:C15|Frog Lizard|||FrogLizardToken| -|Generate|TOK:C15|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:C15|Gold|||GoldToken| |Generate|TOK:C15|Knight|1||HuntedDragonKnightToken| |Generate|TOK:C15|Knight|2||KnightToken| |Generate|TOK:C15|Lightning Rager|||LightningRagerToken| +|Generate|TOK:C15|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:C15|Saproling|||SaprolingToken| |Generate|TOK:C15|Shapeshifter|||CribSwapShapeshifterWhiteToken| |Generate|TOK:C15|Snake|1||SnakeToken| @@ -325,17 +323,19 @@ |Generate|TOK:C15|Spirit|2||WhiteBlackSpiritToken| |Generate|TOK:C15|Wolf|||WolfToken| |Generate|TOK:C15|Zombie|||ZombieToken| + +# C16 |Generate|TOK:C16|Beast|||BeastToken| |Generate|TOK:C16|Bird|1||SwanSongBirdToken| |Generate|TOK:C16|Bird|2||BirdToken| |Generate|TOK:C16|Elemental|||WhiteElementalToken| |Generate|TOK:C16|Elf Warrior|||ElfWarriorToken| -|Generate|TOK:C16|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:C16|Goat|||GoatToken| |Generate|TOK:C16|Goblin|||SpyMasterGoblinToken| -|Generate|TOK:C16|Phyrexian Horror|||PhyrexianRebirthHorrorToken| |Generate|TOK:C16|Myr|||MyrToken| |Generate|TOK:C16|Ogre|||OgreToken| +|Generate|TOK:C16|Phyrexian Germ|||PhyrexianGermToken| +|Generate|TOK:C16|Phyrexian Horror|||PhyrexianRebirthHorrorToken| |Generate|TOK:C16|Saproling|1||SaprolingToken| |Generate|TOK:C16|Saproling|2||SaprolingToken| |Generate|TOK:C16|Soldier|||SoldierToken| @@ -345,6 +345,8 @@ |Generate|TOK:C16|Thopter|||ThopterToken| |Generate|TOK:C16|Worm|||BlackGreenWormToken| |Generate|TOK:C16|Zombie|||ZombieToken| + +# C17 |Generate|TOK:C17|Bat|||BatToken| |Generate|TOK:C17|Cat|||CatToken| |Generate|TOK:C17|Cat Dragon|||WasitoraCatDragonToken| @@ -356,6 +358,8 @@ |Generate|TOK:C17|Rat|||DeathtouchRatToken| |Generate|TOK:C17|Vampire|||EdgarMarkovToken| |Generate|TOK:C17|Zombie|||ZombieToken| + +# C18 |Generate|TOK:C18|Angel|||AngelToken| |Generate|TOK:C18|Beast|1||BeastToken2| |Generate|TOK:C18|Beast|2||SpawningGroundsBeastToken| @@ -381,6 +385,8 @@ |Generate|TOK:C18|Thopter|3||ThopterToken| |Generate|TOK:C18|Worm|||BlackGreenWormToken| |Generate|TOK:C18|Zombie|||ZombieToken| + +# C19 |Generate|TOK:C19|Assassin|||AssassinToken| |Generate|TOK:C19|Beast|1||BeastToken| |Generate|TOK:C19|Beast|2||BeastToken2| @@ -392,9 +398,9 @@ |Generate|TOK:C19|Egg|||AtlaPalaniToken| |Generate|TOK:C19|Eldrazi|||EldraziToken| |Generate|TOK:C19|Gargoyle|||GargoyleToken| -|Generate|TOK:C19|Phyrexian Horror|||PhyrexianRebirthHorrorToken| |Generate|TOK:C19|Human|||HumanToken| |Generate|TOK:C19|Pegasus|||PegasusToken| +|Generate|TOK:C19|Phyrexian Horror|||PhyrexianRebirthHorrorToken| |Generate|TOK:C19|Plant|||Plant11Token| |Generate|TOK:C19|Rhino|||RhinoToken| |Generate|TOK:C19|Saproling|||SaprolingToken| @@ -405,6 +411,8 @@ |Generate|TOK:C19|Wurm|||WurmToken| |Generate|TOK:C19|Zombie|1||ZombieToken| |Generate|TOK:C19|Zombie|2||ZombieToken| + +# CMA |Generate|TOK:CMA|Beast|1||BeastToken| |Generate|TOK:CMA|Beast|2||BeastToken2| |Generate|TOK:CMA|Dragon|||DragonToken2| @@ -414,9 +422,9 @@ |Generate|TOK:CMA|Elf Druid|||ElfDruidToken| |Generate|TOK:CMA|Elf Warrior|||ElfWarriorToken| |Generate|TOK:CMA|Gargoyle|||GargoyleToken| -|Generate|TOK:CMA|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:CMA|Kithkin Soldier|||KithkinSoldierToken| |Generate|TOK:CMA|Knight|||KnightToken| +|Generate|TOK:CMA|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:CMA|Saproling|||SaprolingToken| |Generate|TOK:CMA|Spider|||SpiderToken| |Generate|TOK:CMA|Spirit|||SpiritWhiteToken| @@ -424,6 +432,8 @@ |Generate|TOK:CMA|Wolf|1||WolfToken| |Generate|TOK:CMA|Wolf|2||WolfToken| |Generate|TOK:CMA|Zombie|||ZombieToken| + +# CN2 |Generate|TOK:CN2|Assassin|||QueenMarchesaAssassinToken| |Generate|TOK:CN2|Beast|||BeastToken| |Generate|TOK:CN2|Construct|||DarettiConstructToken| @@ -433,6 +443,8 @@ |Generate|TOK:CN2|Soldier|||SoldierToken| |Generate|TOK:CN2|Spirit|||SpiritWhiteToken| |Generate|TOK:CN2|Zombie|||ZombieToken| + +# CNS |Generate|TOK:CNS|Construct|||DarettiConstructToken| |Generate|TOK:CNS|Demon|||DemonFlyingToken| |Generate|TOK:CNS|Elephant|||ElephantToken| @@ -441,48 +453,88 @@ |Generate|TOK:CNS|Squirrel|||SquirrelToken| |Generate|TOK:CNS|Wolf|||WolfToken| |Generate|TOK:CNS|Zombie|||ZombieToken| + +# CON |Generate|TOK:CON|Angel|||AngelToken| |Generate|TOK:CON|Elemental|||ElementalTokenWithHaste| + +# DVD |Generate|TOK:DVD|Demon|||DemonFlyingToken| |Generate|TOK:DVD|Spirit|||SpiritWhiteToken| |Generate|TOK:DVD|Thrull|||BreedingPitThrullToken| + +# GVL |Generate|TOK:GVL|Bat|||BatToken| |Generate|TOK:GVL|Beast|1||BeastToken| |Generate|TOK:GVL|Beast|2||BeastToken2| |Generate|TOK:GVL|Elephant|||ElephantToken| + +# DDC |Generate|TOK:DDC|Demon|||DemonFlyingToken| |Generate|TOK:DDC|Spirit|||SpiritWhiteToken| |Generate|TOK:DDC|Thrull|||BreedingPitThrullToken| + +# DDD |Generate|TOK:DDD|Beast|1||BeastToken| |Generate|TOK:DDD|Beast|2||BeastToken2| |Generate|TOK:DDD|Elephant|||ElephantToken| + +# DDE |Generate|TOK:DDE|Hornet|||HornetToken| |Generate|TOK:DDE|Phyrexian Minion|||PhyrexianMinionToken| |Generate|TOK:DDE|Saproling|||SaprolingToken| + +# DDF |Generate|TOK:DDF|Soldier|||SoldierToken| + +# DDG |Generate|TOK:DDG|Goblin|||GoblinToken| + +# DDH |Generate|TOK:DDH|Griffin|||GriffinToken| |Generate|TOK:DDH|Saproling|||SaprolingToken| + +# DDJ |Generate|TOK:DDJ|Saproling|||SaprolingToken| + +# DDK |Generate|TOK:DDK|Spirit|||SpiritWhiteToken| + +# DDL |Generate|TOK:DDL|Beast|||BeastToken| |Generate|TOK:DDL|Griffin|||GriffinToken| + +# DDM |Generate|TOK:DDM|Assassin|||AssassinToken| + +# DDN |Generate|TOK:DDN|Goblin|||GoblinToken| + +# DDO |Generate|TOK:DDO|Kraken|||Kraken99Token| |Generate|TOK:DDO|Soldier|||SoldierToken| + +# DDP |Generate|TOK:DDP|Eldrazi Spawn|1||EldraziSpawnToken| |Generate|TOK:DDP|Eldrazi Spawn|2||EldraziSpawnToken| |Generate|TOK:DDP|Eldrazi Spawn|3||EldraziSpawnToken| |Generate|TOK:DDP|Hellion|||HellionToken| |Generate|TOK:DDP|Plant|||PlantToken| + +# DDQ |Generate|TOK:DDQ|Angel|||AngelToken| |Generate|TOK:DDQ|Human|||HumanToken| |Generate|TOK:DDQ|Spirit|||SpiritWhiteToken| |Generate|TOK:DDQ|Zombie|||ZombieToken| + +# DGM |Generate|TOK:DGM|Elemental|||VoiceOfResurgenceToken| + +# DKA |Generate|TOK:DKA|Human|||HumanToken| |Generate|TOK:DKA|Vampire|||SorinLordOfInnistradVampireToken| + +# DOM |Generate|TOK:DOM|Cleric|||BelzenlokClericToken| |Generate|TOK:DOM|Construct|||KarnConstructToken| |Generate|TOK:DOM|Demon|||BelzenlokDemonToken| @@ -497,25 +549,33 @@ |Generate|TOK:DOM|Saproling|3||SaprolingToken| |Generate|TOK:DOM|Soldier|||SoldierToken| |Generate|TOK:DOM|Zombie Knight|||ZombieKnightToken| + +# DTK |Generate|TOK:DTK|Djinn Monk|||DjinnMonkToken| |Generate|TOK:DTK|Dragon|||DragonToken| |Generate|TOK:DTK|Goblin|||GoblinToken| |Generate|TOK:DTK|Warrior|||WarriorToken| -|Generate|TOK:DTK|Zombie Horror|||CorpseweftZombieToken| |Generate|TOK:DTK|Zombie|||ZombieToken| +|Generate|TOK:DTK|Zombie Horror|||CorpseweftZombieToken| + +# E01 |Generate|TOK:E01|Beast|1||BeastToken| |Generate|TOK:E01|Beast|2||BeastToken2| |Generate|TOK:E01|Soldier|||SoldierToken| |Generate|TOK:E01|Spirit|||SpiritWhiteToken| + +# E02 |Generate|TOK:E02|Saproling|||SaprolingToken| + +# EMA |Generate|TOK:EMA|Carnivore|||CarnivoreToken| |Generate|TOK:EMA|Dragon|||DragonEggDragonToken| |Generate|TOK:EMA|Elemental|1||RedElementalToken| |Generate|TOK:EMA|Elemental|2||CallTheSkyBreakerElementalToken| |Generate|TOK:EMA|Elephant|||ElephantToken| |Generate|TOK:EMA|Elf Warrior|||ElfWarriorToken| -|Generate|TOK:EMA|Goblin Soldier|||GoblinSoldierToken| |Generate|TOK:EMA|Goblin|||GoblinToken| +|Generate|TOK:EMA|Goblin Soldier|||GoblinSoldierToken| |Generate|TOK:EMA|Serf|||SerfToken| |Generate|TOK:EMA|Soldier|||SoldierToken| |Generate|TOK:EMA|Spirit|1||SpiritToken| @@ -523,20 +583,24 @@ |Generate|TOK:EMA|Wall|||TidalWaveWallToken| |Generate|TOK:EMA|Wurm|||WurmToken| |Generate|TOK:EMA|Zombie|||ZombieToken| + +# EMN +|Generate|TOK:EMN|Eldrazi Horror|||EldraziHorrorToken| +|Generate|TOK:EMN|Human|||RedHumanToken| +|Generate|TOK:EMN|Human Wizard|||HumanWizardToken| +|Generate|TOK:EMN|Spider|||SpiderToken| +|Generate|TOK:EMN|Zombie|1||ZombieToken| +|Generate|TOK:EMN|Zombie|2||ZombieToken| +|Generate|TOK:EMN|Zombie|3||ZombieToken| +|Generate|TOK:EMN|Zombie|4||ZombieToken2| #TOK:EMN - Human Soldier, Spirit, Devil, Insect and Wolf tokens from SOI set #|Generate|TOK:EMN|Devil|||DevilToken| #|Generate|TOK:EMN|Human Soldier|||HumanSoldierToken| #|Generate|TOK:EMN|Insect|||InsectToken| #|Generate|TOK:EMN|Spirit|||SpiritWhiteToken| #|Generate|TOK:EMN|Wolf|||WolfToken| -|Generate|TOK:EMN|Eldrazi Horror|||EldraziHorrorToken| -|Generate|TOK:EMN|Human Wizard|||HumanWizardToken| -|Generate|TOK:EMN|Human|||RedHumanToken| -|Generate|TOK:EMN|Spider|||SpiderToken| -|Generate|TOK:EMN|Zombie|1||ZombieToken| -|Generate|TOK:EMN|Zombie|2||ZombieToken| -|Generate|TOK:EMN|Zombie|3||ZombieToken| -|Generate|TOK:EMN|Zombie|4||ZombieToken2| + +# EVE |Generate|TOK:EVE|Beast|||BeastToken| |Generate|TOK:EVE|Bird|||BlueBirdToken| |Generate|TOK:EVE|Elemental|||CallTheSkyBreakerElementalToken| @@ -544,12 +608,18 @@ |Generate|TOK:EVE|Goblin Soldier|||GoblinSoldierToken| |Generate|TOK:EVE|Spirit|||WhiteBlackSpiritToken| |Generate|TOK:EVE|Worm|||BlackGreenWormToken| + +# EVG |Generate|TOK:EVG|Elemental|||VoiceOfTheWoodsElementalToken| |Generate|TOK:EVG|Elf Warrior|||ElfWarriorToken| |Generate|TOK:EVG|Goblin|||GoblinToken| + +# FRF |Generate|TOK:FRF|Monk|||MonasteryMentorToken| |Generate|TOK:FRF|Spirit|||SpiritWhiteToken| |Generate|TOK:FRF|Warrior|||MarduStrikeLeaderWarriorToken| + +# GTC |Generate|TOK:GTC|Angel|||AngelToken| |Generate|TOK:GTC|Cleric|||DeathpactAngelToken| |Generate|TOK:GTC|Frog Lizard|||FrogLizardToken| @@ -557,21 +627,16 @@ |Generate|TOK:GTC|Rat|||RatToken| |Generate|TOK:GTC|Soldier|||SoldierTokenWithHaste| |Generate|TOK:GTC|Spirit|||WhiteBlackSpiritToken| + +# H17 |Generate|TOK:H17|Dragon|||DragonTokenGold| + +# HOU |Generate|TOK:HOU|Horse|||CrestedSunmareToken| |Generate|TOK:HOU|Insect|||TheLocustGodInsectToken| |Generate|TOK:HOU|Snake|||RhonassLastStandToken| -#TOK:HOU - some tokens from real cards (see Eternalize ability) -#TOK:HOU - Cat, Warrior and Zombie tokens from AKH set -#|Generate|TOK:HOU|Adorned Pouncer|||| -#|Generate|TOK:HOU|Champion of Wits|||| -#|Generate|TOK:HOU|Dreamstealer|||| -#|Generate|TOK:HOU|Earthshaker Khenra|||| -#|Generate|TOK:HOU|Proven Combatant|||| -#|Generate|TOK:HOU|Resilient Khenra|||| -#|Generate|TOK:HOU|Sinuous Striker|||| -#|Generate|TOK:HOU|Steadfast Sentinel|||| -#|Generate|TOK:HOU|Sunscourge Champion|||| + +# ISD |Generate|TOK:ISD|Angel|||AngelToken| |Generate|TOK:ISD|Demon|||DemonToken| |Generate|TOK:ISD|Homunculus|||StitchersApprenticeHomunculusToken| @@ -584,12 +649,16 @@ |Generate|TOK:ISD|Zombie|1||ZombieToken| |Generate|TOK:ISD|Zombie|2||ZombieToken| |Generate|TOK:ISD|Zombie|3||ZombieToken| + +# JOU |Generate|TOK:JOU|Hydra|||HydraBroodmasterToken| |Generate|TOK:JOU|Minotaur|||MinotaurToken| |Generate|TOK:JOU|Snake|||PharikaSnakeToken| |Generate|TOK:JOU|Sphinx|||HourOfNeedSphinxToken| |Generate|TOK:JOU|Spider|||RenownedWeaverSpiderToken| |Generate|TOK:JOU|Zombie|||RitualOfTheReturnedZombieToken| + +# KLD |Generate|TOK:KLD|Beast|||ArchitectOfTheUntamedBeastToken| |Generate|TOK:KLD|Construct|1||OviyaPashiriSageLifecrafterToken| |Generate|TOK:KLD|Construct|2||MetallurgicSummoningsConstructToken| @@ -599,17 +668,20 @@ |Generate|TOK:KLD|Thopter|1||ThopterColorlessToken| |Generate|TOK:KLD|Thopter|2||ThopterColorlessToken| |Generate|TOK:KLD|Thopter|3||ThopterColorlessToken| + +# KTK |Generate|TOK:KTK|Bear|||BearsCompanionBearToken| |Generate|TOK:KTK|Bird|||WingmateRocToken| |Generate|TOK:KTK|Goblin|||GoblinToken| |Generate|TOK:KTK|Snake|||SnakeToken| -|Generate|TOK:KTK|Spirit Warrior|||SpiritWarriorToken| |Generate|TOK:KTK|Spirit|||SpiritWhiteToken| +|Generate|TOK:KTK|Spirit Warrior|||SpiritWarriorToken| |Generate|TOK:KTK|Vampire|||VampireToken| |Generate|TOK:KTK|Warrior|1||WarriorToken| |Generate|TOK:KTK|Warrior|2||WarriorToken| |Generate|TOK:KTK|Zombie|||ZombieToken| -# LGN don't have tokens, from wiki: A Sliver token for Brood Sliver and a Goblin token for Warbreak Trumpeter were featured as a Magic Player Reward. + +# LRW |Generate|TOK:LRW|Avatar|||AvatarToken| |Generate|TOK:LRW|Beast|||BeastToken| |Generate|TOK:LRW|Elemental|1||Elemental44GreenToken| @@ -621,6 +693,8 @@ |Generate|TOK:LRW|Merfolk Wizard|||MerfolkWizardToken| |Generate|TOK:LRW|Shapeshifter|||CribSwapShapeshifterWhiteToken| |Generate|TOK:LRW|Wolf|||WolfToken| + +# M10 |Generate|TOK:M10|Avatar|||AvatarToken| |Generate|TOK:M10|Beast|||BeastToken| |Generate|TOK:M10|Gargoyle|||GargoyleToken| @@ -629,12 +703,16 @@ |Generate|TOK:M10|Soldier|||SoldierToken| |Generate|TOK:M10|Wolf|||WolfToken| |Generate|TOK:M10|Zombie|||ZombieToken| + +# M11 |Generate|TOK:M11|Avatar|||AvatarToken| |Generate|TOK:M11|Beast|||BeastToken| |Generate|TOK:M11|Bird|||RocEggToken| |Generate|TOK:M11|Ooze|1||MitoticSlimeOozeToken| |Generate|TOK:M11|Ooze|2||OozeToken| |Generate|TOK:M11|Zombie|||ZombieToken| + +# M12 |Generate|TOK:M12|Beast|||BeastToken| |Generate|TOK:M12|Bird|||RocEggToken| |Generate|TOK:M12|Pentavite|||PentaviteToken| @@ -642,6 +720,8 @@ |Generate|TOK:M12|Soldier|||SoldierToken| |Generate|TOK:M12|Wurm|||WurmToken| |Generate|TOK:M12|Zombie|||ZombieToken| + +# M13 |Generate|TOK:M13|Beast|||BeastToken| |Generate|TOK:M13|Cat|||CatToken| |Generate|TOK:M13|Drake|||DrakeToken| @@ -652,6 +732,8 @@ |Generate|TOK:M13|Soldier|||SoldierToken| |Generate|TOK:M13|Wurm|||WurmToken| |Generate|TOK:M13|Zombie|||ZombieToken| + +# M14 |Generate|TOK:M14|Angel|||AngelToken| |Generate|TOK:M14|Beast|||BeastToken| |Generate|TOK:M14|Cat|||CatToken| @@ -663,6 +745,8 @@ |Generate|TOK:M14|Sliver|||SliverToken| |Generate|TOK:M14|Wolf|||WolfToken| |Generate|TOK:M14|Zombie|||ZombieToken| + +# M15 |Generate|TOK:M15|Beast|1||GarrukApexPredatorBeastToken| |Generate|TOK:M15|Beast|2||BeastToken| |Generate|TOK:M15|Dragon|||DragonEggDragonToken| @@ -675,26 +759,32 @@ |Generate|TOK:M15|Squid|||SquidToken| |Generate|TOK:M15|Treefolk Warrior|||KalonianTwingroveTreefolkWarriorToken| |Generate|TOK:M15|Zombie|||ZombieToken| -|Generate|TOK:MBS|Phyrexian Germ|||PhyrexianGermToken| + +# MBS |Generate|TOK:MBS|Golem|||TitanForgeGolemToken| +|Generate|TOK:MBS|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:MBS|Phyrexian Horror|||PhyrexianRebirthHorrorToken| |Generate|TOK:MBS|Thopter|||ThopterColorlessToken| |Generate|TOK:MBS|Zombie|||ZombieToken| + +# MED |Generate|TOK:MED|Beast|||GarrukApexPredatorBeastToken| |Generate|TOK:MED|Construct|1||KarnConstructToken| |Generate|TOK:MED|Construct|2||DarettiConstructToken| |Generate|TOK:MED|Dragon|||DragonToken| |Generate|TOK:MED|Soldier|||SoldierToken| |Generate|TOK:MED|Zombie|||ZombieToken| + +# MM2 |Generate|TOK:MM2|Eldrazi Spawn|1||EldraziSpawnToken| |Generate|TOK:MM2|Eldrazi Spawn|2||EldraziSpawnToken| |Generate|TOK:MM2|Eldrazi Spawn|3||EldraziSpawnToken| |Generate|TOK:MM2|Elephant|||ElephantToken| |Generate|TOK:MM2|Faerie Rogue|||FaerieRogueToken| -|Generate|TOK:MM2|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:MM2|Golem|||GolemToken| |Generate|TOK:MM2|Insect|||InsectToken| |Generate|TOK:MM2|Myr|||MyrToken| +|Generate|TOK:MM2|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:MM2|Saproling|||SaprolingToken| |Generate|TOK:MM2|Snake|||SnakeToken| |Generate|TOK:MM2|Soldier|||SoldierToken| @@ -702,6 +792,8 @@ |Generate|TOK:MM2|Thrull|||ThrullToken| |Generate|TOK:MM2|Wolf|||WolfToken| |Generate|TOK:MM2|Worm|||BlackGreenWormToken| + +# MM3 |Generate|TOK:MM3|Angel|||AngelToken| |Generate|TOK:MM3|Beast|1||BeastToken| |Generate|TOK:MM3|Beast|2||BeastToken2| @@ -713,8 +805,8 @@ |Generate|TOK:MM3|Giant Warrior|||GiantBaitingGiantWarriorToken| |Generate|TOK:MM3|Goblin|||GoblinToken| |Generate|TOK:MM3|Goblin Warrior|||GoblinWarriorToken| -|Generate|TOK:MM3|Phyrexian Golem|||PhyrexianGolemToken| |Generate|TOK:MM3|Ooze|||OozeToken| +|Generate|TOK:MM3|Phyrexian Golem|||PhyrexianGolemToken| |Generate|TOK:MM3|Saproling|||SaprolingToken| |Generate|TOK:MM3|Soldier|1||SoldierToken| |Generate|TOK:MM3|Soldier|2||SoldierTokenWithHaste| @@ -722,13 +814,15 @@ |Generate|TOK:MM3|Spirit|||SpiritWhiteToken| |Generate|TOK:MM3|Wurm|||WurmWithTrampleToken| |Generate|TOK:MM3|Zombie|||ZombieToken| + +# MMA |Generate|TOK:MMA|Bat|||BatToken| |Generate|TOK:MMA|Dragon|||DragonToken| |Generate|TOK:MMA|Elemental|||Elemental44GreenToken| |Generate|TOK:MMA|Faerie Rogue|||OonaQueenFaerieRogueToken| |Generate|TOK:MMA|Giant Warrior|||GiantWarriorToken| -|Generate|TOK:MMA|Goblin Rogue|||GoblinRogueToken| |Generate|TOK:MMA|Goblin|||GoblinToken| +|Generate|TOK:MMA|Goblin Rogue|||GoblinRogueToken| |Generate|TOK:MMA|Illusion|||MelokuTheCloudedMirrorToken| |Generate|TOK:MMA|Kithkin Soldier|||KithkinSoldierToken| |Generate|TOK:MMA|Saproling|||SaprolingToken| @@ -737,19 +831,27 @@ |Generate|TOK:MMA|Treefolk Shaman|||TreefolkShamanToken| |Generate|TOK:MMA|Worm|||BlackGreenWormToken| |Generate|TOK:MMA|Zombie|||ZombieToken| + +# MOR |Generate|TOK:MOR|Faerie Rogue|||FaerieRogueToken| |Generate|TOK:MOR|Giant Warrior|||GiantWarriorToken| |Generate|TOK:MOR|Treefolk Shaman|||TreefolkShamanToken| + +# NPH |Generate|TOK:NPH|Beast|||BeastToken| |Generate|TOK:NPH|Phyrexian Goblin|||PhyrexianGoblinHasteToken| |Generate|TOK:NPH|Phyrexian Golem|||PhyrexianGolemToken| |Generate|TOK:NPH|Phyrexian Myr|||PhyrexianMyrToken| + +# ODY |Generate|TOK:ODY|Bear|||BearToken| |Generate|TOK:ODY|Beast|||BeastToken2| |Generate|TOK:ODY|Elephant|||ElephantToken| |Generate|TOK:ODY|Squirrel|||SquirrelToken| |Generate|TOK:ODY|Wurm|||WurmToken| |Generate|TOK:ODY|Zombie|||ZombieToken| + +# OGW |Generate|TOK:OGW|Angel|||Angel33Token| |Generate|TOK:OGW|Eldrazi Scion|1||EldraziScionToken| |Generate|TOK:OGW|Eldrazi Scion|2||EldraziScionToken| @@ -761,6 +863,8 @@ |Generate|TOK:OGW|Elemental|2||ElementalTokenWithHaste| |Generate|TOK:OGW|Plant|||PlantToken| |Generate|TOK:OGW|Zombie|||ZombieToken| + +# ORI |Generate|TOK:ORI|Angel|||AngelToken| |Generate|TOK:ORI|Ashaya, the Awoken World|||NissaSageAnimistToken| |Generate|TOK:ORI|Demon|||DemonToken| @@ -772,10 +876,14 @@ |Generate|TOK:ORI|Thopter|1||ThopterColorlessToken| |Generate|TOK:ORI|Thopter|2||ThopterColorlessToken| |Generate|TOK:ORI|Zombie|||ZombieToken| + +# RIX |Generate|TOK:RIX|Elemental|1||RekindlingPhoenixToken| |Generate|TOK:RIX|Elemental|2||RedElementalToken| |Generate|TOK:RIX|Golem|||GoldForgeGarrisonGolemToken| |Generate|TOK:RIX|Saproling|||SaprolingToken| + +# ROE |Generate|TOK:ROE|Eldrazi Spawn|1||EldraziSpawnToken| |Generate|TOK:ROE|Eldrazi Spawn|2||EldraziSpawnToken| |Generate|TOK:ROE|Eldrazi Spawn|3||EldraziSpawnToken| @@ -783,6 +891,8 @@ |Generate|TOK:ROE|Hellion|||HellionToken| |Generate|TOK:ROE|Ooze|||OozeToken| |Generate|TOK:ROE|Tuktuk the Returned|||TuktukTheReturnedToken| + +# RTR |Generate|TOK:RTR|Assassin|||AssassinToken| |Generate|TOK:RTR|Bird|||BirdToken| |Generate|TOK:RTR|Centaur|||CentaurToken| @@ -795,6 +905,8 @@ |Generate|TOK:RTR|Saproling|||SaprolingToken| |Generate|TOK:RTR|Soldier|||SoldierToken| |Generate|TOK:RTR|Wurm|||WurmWithTrampleToken| + +# SHM |Generate|TOK:SHM|Elemental|1||DinOfTheFireherdToken| |Generate|TOK:SHM|Elemental|2||Elemental11HasteToken| |Generate|TOK:SHM|Elf Warrior|1||ElfWarriorToken| @@ -807,6 +919,8 @@ |Generate|TOK:SHM|Spider|||SpiderToken| |Generate|TOK:SHM|Spirit|||SpiritWhiteToken| |Generate|TOK:SHM|Wolf|||WolfToken| + +# SOI |Generate|TOK:SOI|Angel|||AngelToken| |Generate|TOK:SOI|Clue|1||ClueArtifactToken| |Generate|TOK:SOI|Clue|2||ClueArtifactToken| @@ -823,25 +937,31 @@ |Generate|TOK:SOI|Vampire Knight|||VampireKnightToken| |Generate|TOK:SOI|Wolf|||WolfToken| |Generate|TOK:SOI|Zombie|||ZombieToken| + +# SOM |Generate|TOK:SOM|Cat|||CatToken| |Generate|TOK:SOM|Goblin|||GoblinToken| |Generate|TOK:SOM|Golem|||GolemToken| -|Generate|TOK:SOM|Phyrexian Insect|||InsectInfectToken| |Generate|TOK:SOM|Myr|||MyrToken| -|Generate|TOK:SOM|Soldier|||SoldierToken| -|Generate|TOK:SOM|Wolf|||WolfToken| +|Generate|TOK:SOM|Phyrexian Insect|||InsectInfectToken| |Generate|TOK:SOM|Phyrexian Wurm|1||WurmWithDeathtouchToken| |Generate|TOK:SOM|Phyrexian Wurm|2||WurmWithLifelinkToken| -|Generate|TOK:SWS|Ewok|||EwokToken| -|Generate|TOK:SWS|B-Wing|||RebelStarshipToken| -|Generate|TOK:SWS|Hunter|||HunterToken| -|Generate|TOK:SWS|TIE Fighter|||TIEFighterToken| -|Generate|TOK:SWS|Trooper|||TrooperToken| +|Generate|TOK:SOM|Soldier|||SoldierToken| +|Generate|TOK:SOM|Wolf|||WolfToken| + +# SWS |Generate|TOK:SWS|AT-AT|||ATATToken| +|Generate|TOK:SWS|B-Wing|||RebelStarshipToken| +|Generate|TOK:SWS|Droid|||DroidToken| +|Generate|TOK:SWS|Ewok|||EwokToken| +|Generate|TOK:SWS|Hunter|||HunterToken| |Generate|TOK:SWS|Rebel|||RebelToken| |Generate|TOK:SWS|Royal Guard|||RoyalGuardToken| +|Generate|TOK:SWS|TIE Fighter|||TIEFighterToken| +|Generate|TOK:SWS|Trooper|||TrooperToken| |Generate|TOK:SWS|Tusken Raider|||TuskenRaiderToken| -|Generate|TOK:SWS|Droid|||DroidToken| + +# THS |Generate|TOK:THS|Bird|||SwanSongBirdToken| |Generate|TOK:THS|Boar|||Boar2Token| |Generate|TOK:THS|Cleric|||HeliodGodOfTheSunToken| @@ -852,14 +972,35 @@ |Generate|TOK:THS|Soldier|1||SoldierToken| |Generate|TOK:THS|Soldier|2||SoldierToken| |Generate|TOK:THS|Soldier|3||AkroanSoldierToken| + +# UST +|Generate|TOK:UST|Angel|||AngelToken| +|Generate|TOK:UST|Beast|||BeastToken| +|Generate|TOK:UST|Brainiac|||BrainiacToken| +|Generate|TOK:UST|Clue|||ClueToken| |Generate|TOK:UST|Dragon|||DragonTokenGold| +|Generate|TOK:UST|Elemental|1||RedElementalToken| +|Generate|TOK:UST|Elemental|2||VoiceOfResurgenceToken| +|Generate|TOK:UST|Gnome|||GnomeToken| +|Generate|TOK:UST|Goat|||GoatToken| +|Generate|TOK:UST|Goblin|||GoblinToken| +|Generate|TOK:UST|Saproling|||SaprolingToken| +|Generate|TOK:UST|Spirit|||SpiritWhiteToken| +|Generate|TOK:UST|Squirrel|||SquirrelToken| |Generate|TOK:UST|Storm Crow|||StormCrowToken| +|Generate|TOK:UST|Thopter|||ThopterToken| +|Generate|TOK:UST|Vampire|||VampireToken| +|Generate|TOK:UST|Zombie|||ZombieToken| + +# WWK |Generate|TOK:WWK|Construct|||StoneIdolToken| |Generate|TOK:WWK|Dragon|||DragonToken2| |Generate|TOK:WWK|Elephant|||ElephantToken| |Generate|TOK:WWK|Ogre|||OgreToken| |Generate|TOK:WWK|Plant|||PlantToken| |Generate|TOK:WWK|Soldier Ally|||JoinTheRanksSoldierToken| + +# XLN |Generate|TOK:XLN|Dinosaur|||DinosaurToken| |Generate|TOK:XLN|Illusion|||JaceCunningCastawayIllusionToken| |Generate|TOK:XLN|Merfolk|||MerfolkHexproofToken| @@ -870,6 +1011,8 @@ |Generate|TOK:XLN|Treasure|3||TreasureToken| |Generate|TOK:XLN|Treasure|4||TreasureToken| |Generate|TOK:XLN|Vampire|||IxalanVampireToken| + +# ZEN |Generate|TOK:ZEN|Angel|||AngelToken| |Generate|TOK:ZEN|Beast|||BeastToken2| |Generate|TOK:ZEN|Bird|||BirdToken| @@ -881,6 +1024,8 @@ |Generate|TOK:ZEN|Vampire|||KalitasVampireToken| |Generate|TOK:ZEN|Wolf|||WolfToken| |Generate|TOK:ZEN|Zombie Giant|||QuestForTheGravelordZombieToken| + +# RNA |Generate|TOK:RNA|Beast|||RedGreenBeastToken| |Generate|TOK:RNA|Centaur|||CentaurToken| |Generate|TOK:RNA|Frog Lizard|||FrogLizardToken| @@ -893,12 +1038,16 @@ |Generate|TOK:RNA|Thopter|||ThopterColorlessToken| |Generate|TOK:RNA|Treasure|||TreasureToken| |Generate|TOK:RNA|Zombie|||ZombieToken| + +# GRN |Generate|TOK:GRN|Angel|||AngelVigilanceToken| |Generate|TOK:GRN|Bird Illusion|||BirdIllusionToken| |Generate|TOK:GRN|Elf Knight|||ElfKnightToken| |Generate|TOK:GRN|Goblin|||GoblinToken| |Generate|TOK:GRN|Insect|||IzoniInsectToken| |Generate|TOK:GRN|Soldier|||SoldierLifelinkToken| + +# WAR |Generate|TOK:WAR|Angel|||AngelVigilanceToken| |Generate|TOK:WAR|Assassin|||AssassinToken2| |Generate|TOK:WAR|Citizen|||PlanewideCelebrationToken| @@ -913,11 +1062,12 @@ |Generate|TOK:WAR|Wizard|||WizardToken| |Generate|TOK:WAR|Wolf|||WolfToken| |Generate|TOK:WAR|Zombie|||ZombieToken| -|Generate|TOK:WAR|Zombie Warrior|||GodEternalOketraToken| |Generate|TOK:WAR|Zombie Army|1||ZombieArmyToken| |Generate|TOK:WAR|Zombie Army|2||ZombieArmyToken| |Generate|TOK:WAR|Zombie Army|3||ZombieArmyToken| -|Generate|TOK:MH1|Shapeshifter|||ShapeshifterToken| +|Generate|TOK:WAR|Zombie Warrior|||GodEternalOketraToken| + +# MH1 |Generate|TOK:MH1|Angel|||AngelVigilanceToken| |Generate|TOK:MH1|Bear|||BearToken| |Generate|TOK:MH1|Bird|||BirdToken| @@ -931,11 +1081,14 @@ |Generate|TOK:MH1|Marit Lage|||MaritLageToken| |Generate|TOK:MH1|Myr|||MyrToken| |Generate|TOK:MH1|Rhino|||RhinoToken| +|Generate|TOK:MH1|Shapeshifter|||ShapeshifterToken| |Generate|TOK:MH1|Soldier|||SoldierToken| |Generate|TOK:MH1|Spider|||SpiderToken| |Generate|TOK:MH1|Spirit|||WhiteBlackSpiritToken| |Generate|TOK:MH1|Squirrel|||SquirrelToken| |Generate|TOK:MH1|Zombie|||ZombieToken| + +# M19 |Generate|TOK:M19|Angel|||AngelVigilanceToken| |Generate|TOK:M19|Avatar|||AvatarToken2| |Generate|TOK:M19|Bat|||BatToken| @@ -950,6 +1103,8 @@ |Generate|TOK:M19|Soldier|||SoldierToken| |Generate|TOK:M19|Thopter|||ThopterColorlessToken| |Generate|TOK:M19|Zombie|||ZombieToken| + +# M20 |Generate|TOK:M20|Ajani's Pridemate|||AjanisPridemateToken| |Generate|TOK:M20|Demon|||DemonToken| |Generate|TOK:M20|Elemental|||RedElementalToken| @@ -982,27 +1137,27 @@ |Generate|TOK:ELD|Wolf|||GarrukCursedHuntsmanToken| # THB -|Generate|TOK:THB|Goat|||GoatToken| -|Generate|TOK:THB|Human Soldier|||HumanSoldierToken| -|Generate|TOK:THB|Pegasus|||PegasusToken2| -|Generate|TOK:THB|Kraken|||KrakenHexproofToken| -|Generate|TOK:THB|Reflection|||ReflectionBlueToken| -|Generate|TOK:THB|Tentacle|||TentacleToken| -|Generate|TOK:THB|Zombie|||ZombieToken| |Generate|TOK:THB|Elemental|||PurphorossInterventionToken| +|Generate|TOK:THB|Goat|||GoatToken| +|Generate|TOK:THB|Gold|||GoldToken| +|Generate|TOK:THB|Human Soldier|||HumanSoldierToken| +|Generate|TOK:THB|Kraken|||KrakenHexproofToken| +|Generate|TOK:THB|Nightmare|||AshiokNightmareMuseToken| +|Generate|TOK:THB|Pegasus|||PegasusToken2| +|Generate|TOK:THB|Reflection|||ReflectionBlueToken| |Generate|TOK:THB|Satyr|||SatyrCantBlockToken| |Generate|TOK:THB|Spider|||SpiderToken| -|Generate|TOK:THB|Wolf|||WolfToken| -|Generate|TOK:THB|Nightmare|||AshiokNightmareMuseToken| -|Generate|TOK:THB|Gold|||GoldToken| +|Generate|TOK:THB|Tentacle|||TentacleToken| |Generate|TOK:THB|Wall|||ArtifactWallToken| +|Generate|TOK:THB|Wolf|||WolfToken| +|Generate|TOK:THB|Zombie|||ZombieToken| # IKO |Generate|TOK:IKO|Beast|||BeastToken| -|Generate|TOK:IKO|Cat Bird|||CatBirdToken| |Generate|TOK:IKO|Cat|||CatToken2| -|Generate|TOK:IKO|Dinosaur Beast|||DinosaurBeastToken| +|Generate|TOK:IKO|Cat Bird|||CatBirdToken| |Generate|TOK:IKO|Dinosaur|||DinosaurHasteToken| +|Generate|TOK:IKO|Dinosaur Beast|||DinosaurBeastToken| |Generate|TOK:IKO|Feather|||FeatherToken| |Generate|TOK:IKO|Human Soldier|1||HumanSoldierToken| |Generate|TOK:IKO|Human Soldier|2||HumanSoldierToken| @@ -1055,8 +1210,8 @@ |Generate|TOK:ZNR|Cat|||CatToken3| |Generate|TOK:ZNR|Cat Beast|||CatBeastToken| |Generate|TOK:ZNR|Construct|||ConstructToken| -|Generate|TOK:ZNR|Goblin Construct|||RelicRobberToken| |Generate|TOK:ZNR|Drake|||DrakeToken| +|Generate|TOK:ZNR|Goblin Construct|||RelicRobberToken| |Generate|TOK:ZNR|Hydra|||GrakmawSkyclaveRavagerHydraToken| |Generate|TOK:ZNR|Illusion|||CustomIllusionToken| |Generate|TOK:ZNR|Insect|||InsectToken| @@ -1064,19 +1219,16 @@ |Generate|TOK:ZNR|Plant|||PlantToken| # ZNC -|Generate|TOK:ZNC|Bird|||BirdToken| |Generate|TOK:ZNC|Beast|||BeastToken2| +|Generate|TOK:ZNC|Bird|||BirdToken| |Generate|TOK:ZNC|Elemental|1||OmnathElementalToken| |Generate|TOK:ZNC|Elemental|2||ZendikarsRoilElementalToken| |Generate|TOK:ZNC|Faerie Rogue|||FaerieRogueToken| -# # OonaQueenFaerieRogueToken is FaerieRogueToken with additional blue color, but ZNC contains only one token - so don't use normal token for it #|Generate|TOK:ZNC|Faerie Rogue|||OonaQueenFaerieRogueToken| -# Germ token uses in chest and antology, but scryfall put it here -|Generate|TOK:ZNC|Phyrexian Germ|||PhyrexianGermToken| -# |Generate|TOK:ZNC|Goblin Rogue|||GoblinRogueToken| |Generate|TOK:ZNC|Kor Ally|||KorAllyToken| +|Generate|TOK:ZNC|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:ZNC|Rat|||RatToken| |Generate|TOK:ZNC|Saproling|||SaprolingToken| |Generate|TOK:ZNC|Thopter|||ThopterColorlessToken| @@ -1090,8 +1242,8 @@ |Generate|TOK:CMR|Elephant|||ElephantToken| |Generate|TOK:CMR|Elf Warrior|||ElfWarriorToken| |Generate|TOK:CMR|Golem|||GolemToken| -|Generate|TOK:CMR|Phyrexian Horror|||PhyrexianRebirthHorrorToken| |Generate|TOK:CMR|Illusion|||MelokuTheCloudedMirrorToken| +|Generate|TOK:CMR|Phyrexian Horror|||PhyrexianRebirthHorrorToken| |Generate|TOK:CMR|Plant|||PlantToken| |Generate|TOK:CMR|Rock|||RockToken| |Generate|TOK:CMR|Salamander Warrior|||SalamanderWarriorToken| @@ -1151,7 +1303,7 @@ |Generate|TOK:TSR|Soldier|||SoldierToken| |Generate|TOK:TSR|Spider|||PenumbraSpiderToken| -// STX +# STX |Generate|TOK:STX|Avatar|||BloodAvatarToken| |Generate|TOK:STX|Elemental|||Elemental44Token| |Generate|TOK:STX|Fractal|||FractalToken| @@ -1164,8 +1316,6 @@ |Generate|TOK:C21|Beast|1||BeastToken| |Generate|TOK:C21|Beast|2||BeastToken2| |Generate|TOK:C21|Boar|||Boar2Token| -# no need tokens for Eternalize ability, but scryfall have it: https://scryfall.com/card/tc21/6/champion-of-wits -# no need tokens for Copy, but scryfall have it: https://scryfall.com/card/tc21/30/copy |Generate|TOK:C21|Construct|1||MetallurgicSummoningsConstructToken| |Generate|TOK:C21|Construct|2||KarnConstructToken| |Generate|TOK:C21|Demon|||DemonFlyingToken| @@ -1192,7 +1342,7 @@ |Generate|TOK:C21|Wurm|||WurmToken| |Generate|TOK:C21|Zombie|||ZombieToken| -// MH2 +# MH2 |Generate|TOK:MH2|Beast|||BeastToken2| |Generate|TOK:MH2|Bird|||BirdToken| |Generate|TOK:MH2|Clue|1||ClueArtifactToken| @@ -1208,8 +1358,6 @@ |Generate|TOK:MH2|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:MH2|Squirrel|||SquirrelToken| |Generate|TOK:MH2|Thopter|||ThopterColorlessToken| -# no need tokens for Eternalize ability, but scryfall have it: https://scryfall.com/card/tmh2/4/timeless-dragon -# no need tokens for Eternalize ability, but scryfall have it: https://scryfall.com/card/tmh2/5/timeless-witness |Generate|TOK:MH2|Treasure|1||TreasureToken| |Generate|TOK:MH2|Treasure|2||TreasureToken| |Generate|TOK:MH2|Zombie|||ZombieToken| @@ -1235,7 +1383,6 @@ # AFC |Generate|TOK:AFC|Angel|||AngelToken| |Generate|TOK:AFC|Beast|||BeastToken| -# no need tokens for Eternalize ability, but scryfall have it: https://scryfall.com/card/tafc/4/champion-of-wits |Generate|TOK:AFC|Clue|||ClueArtifactToken| |Generate|TOK:AFC|Dragon|||DragonToken2| |Generate|TOK:AFC|Dragon Spirit|||VrondissRageOfAncientsToken| @@ -1357,18 +1504,45 @@ |Generate|TOK:NEC|Thopter|||ThopterColorlessToken| # SLD +|Generate|TOK:SLD|Angel|||AngelToken| +|Generate|TOK:SLD|Cat|1||GreenCatToken| +|Generate|TOK:SLD|Cat|2||CatToken2| +|Generate|TOK:SLD|Cat|3||CatToken2| |Generate|TOK:SLD|Clue|||ClueArtifactToken| +|Generate|TOK:SLD|Dog|||WhiteDogToken| +|Generate|TOK:SLD|Egg|||AtlaPalaniToken| |Generate|TOK:SLD|Faerie Rogue|1||FaerieRogueToken| |Generate|TOK:SLD|Faerie Rogue|2||FaerieRogueToken| |Generate|TOK:SLD|Faerie Rogue|3||FaerieRogueToken| |Generate|TOK:SLD|Faerie Rogue|4||FaerieRogueToken| -|Generate|TOK:SLD|Treasure|||TreasureToken| +|Generate|TOK:SLD|Food|1||FoodToken| +|Generate|TOK:SLD|Food|2||FoodToken| +|Generate|TOK:SLD|Food|3||FoodToken| +|Generate|TOK:SLD|Food|4||FoodToken| +|Generate|TOK:SLD|Food|5||FoodToken| +|Generate|TOK:SLD|Goblin|||GoblinToken| +|Generate|TOK:SLD|Hydra|||ZaxaraTheExemplaryHydraToken| +|Generate|TOK:SLD|Icingdeath, Frost Tongue|||IcingdeathFrostTongueToken| +|Generate|TOK:SLD|Marit Lage|||MaritLageToken| +|Generate|TOK:SLD|Mechtitan|||MechtitanToken| +|Generate|TOK:SLD|Saproling|||SaprolingToken| +|Generate|TOK:SLD|Shrine|||ShrineToken| +|Generate|TOK:SLD|Spirit|1||SpiritWhiteToken| +|Generate|TOK:SLD|Spirit|2||SpiritToken| +|Generate|TOK:SLD|Squirrel|||SquirrelToken| +|Generate|TOK:SLD|Treasure|1||TreasureToken| +|Generate|TOK:SLD|Treasure|2||TreasureToken| +|Generate|TOK:SLD|Treasure|3||TreasureToken| +|Generate|TOK:SLD|Treasure|4||TreasureToken| |Generate|TOK:SLD|Walker|1||WalkerToken| |Generate|TOK:SLD|Walker|2||WalkerToken| |Generate|TOK:SLD|Walker|3||WalkerToken| |Generate|TOK:SLD|Walker|4||WalkerToken| |Generate|TOK:SLD|Walker|5||WalkerToken| -# TODO: Add new SLD token images +|Generate|TOK:SLD|Warrior|||WarriorToken| +|Generate|TOK:SLD|Wolf|||WolfToken| +|Generate|TOK:SLD|Wurm|||WurmWithTrampleToken| +|Generate|TOK:SLD|Zombie|||ZombieToken| # 2XM |Generate|TOK:2XM|Angel|||AngelToken| @@ -1381,13 +1555,15 @@ |Generate|TOK:2XM|Elemental|||VoiceOfResurgenceToken| |Generate|TOK:2XM|Elephant|||ElephantToken| |Generate|TOK:2XM|Elf Warrior|||GreenWhiteElfWarriorToken| -|Generate|TOK:2XM|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:2XM|Golem|||GolemToken| |Generate|TOK:2XM|Human Soldier|||HumanSoldierToken| |Generate|TOK:2XM|Marit Lage|||MaritLageToken| |Generate|TOK:2XM|Myr|||MyrToken| -|Generate|TOK:2XM|Phyrexian Myr|||BrudicladTelchorMyrToken| |Generate|TOK:2XM|Ooze|||OozeToken| +|Generate|TOK:2XM|Phyrexian Germ|||PhyrexianGermToken| +|Generate|TOK:2XM|Phyrexian Myr|||BrudicladTelchorMyrToken| +|Generate|TOK:2XM|Phyrexian Wurm|1||WurmWithDeathtouchToken| +|Generate|TOK:2XM|Phyrexian Wurm|2||WurmWithLifelinkToken| |Generate|TOK:2XM|Plant|||PlantToken| |Generate|TOK:2XM|Saproling|||SaprolingToken| |Generate|TOK:2XM|Servo|||ServoToken| @@ -1399,8 +1575,6 @@ |Generate|TOK:2XM|Treasure|||TreasureToken| |Generate|TOK:2XM|Tuktuk the Returned|||TuktukTheReturnedToken| |Generate|TOK:2XM|Wolf|||WolfToken| -|Generate|TOK:2XM|Phyrexian Wurm|1||WurmWithDeathtouchToken| -|Generate|TOK:2XM|Phyrexian Wurm|2||WurmWithLifelinkToken| # SNC |Generate|TOK:SNC|Angel|||Angel33Token| @@ -1482,20 +1656,20 @@ # CM2 |Generate|TOK:CM2|Bird|||BirdToken| |Generate|TOK:CM2|Elemental Shaman|||ElementalShamanToken| -|Generate|TOK:CM2|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:CM2|Goat|||GoatToken| |Generate|TOK:CM2|Goblin|||GoblinToken| |Generate|TOK:CM2|Knight|||HuntedDragonKnightToken| |Generate|TOK:CM2|Lightning Rager|||LightningRagerToken| |Generate|TOK:CM2|Myr|||MyrToken| |Generate|TOK:CM2|Pentavite|||PentaviteToken| +|Generate|TOK:CM2|Phyrexian Germ|||PhyrexianGermToken| +|Generate|TOK:CM2|Phyrexian Wurm|1||WurmWithDeathtouchToken| +|Generate|TOK:CM2|Phyrexian Wurm|2||WurmWithLifelinkToken| |Generate|TOK:CM2|Saproling|||SaprolingToken| |Generate|TOK:CM2|Shapeshifter|||CribSwapShapeshifterWhiteToken| |Generate|TOK:CM2|Spirit|||SpiritWhiteToken| |Generate|TOK:CM2|Triskelavite|||TriskelaviteToken| |Generate|TOK:CM2|Tuktuk the Returned|||TuktukTheReturnedToken| -|Generate|TOK:CM2|Phyrexian Wurm|1||WurmWithDeathtouchToken| -|Generate|TOK:CM2|Phyrexian Wurm|2||WurmWithLifelinkToken| |Generate|TOK:CM2|Zombie|||ZombieToken| # PCA @@ -1507,13 +1681,13 @@ |Generate|TOK:PCA|Eldrazi Spawn|1||EldraziSpawnToken| |Generate|TOK:PCA|Eldrazi Spawn|2||EldraziSpawnToken| |Generate|TOK:PCA|Eldrazi Spawn|3||EldraziSpawnToken| -|Generate|TOK:PCA|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:PCA|Goat|||GoatToken| |Generate|TOK:PCA|Goblin|||GoblinToken| |Generate|TOK:PCA|Hellion|||HellionToken| |Generate|TOK:PCA|Insect|||InsectToken| |Generate|TOK:PCA|Ooze|1||OozeToken| |Generate|TOK:PCA|Ooze|2||MitoticSlimeOozeToken| +|Generate|TOK:PCA|Phyrexian Germ|||PhyrexianGermToken| |Generate|TOK:PCA|Plant|||PlantToken| |Generate|TOK:PCA|Saproling|||SaprolingToken| |Generate|TOK:PCA|Spider|||PenumbraSpiderToken| @@ -1533,11 +1707,11 @@ |Generate|TOK:V16|Marit Lage|||MaritLageToken| # DDR -|Generate|TOK:DDR|Eldrazi Scion|||EldraziScionToken| |Generate|TOK:DDR|Demon|||DemonToken| -|Generate|TOK:DDR|Zombie Giant|||QuestForTheGravelordZombieToken| +|Generate|TOK:DDR|Eldrazi Scion|||EldraziScionToken| |Generate|TOK:DDR|Elemental|||Elemental44GreenToken| |Generate|TOK:DDR|Plant|||PlantToken| +|Generate|TOK:DDR|Zombie Giant|||QuestForTheGravelordZombieToken| # DDS |Generate|TOK:DDS|Beast|||BeastToken2| @@ -2059,10 +2233,51 @@ |Generate|TOK:WOC|Virtuous|||VirtuousRoleToken| # WHO +|Generate|TOK:WHO|Alien|||AlienToken| |Generate|TOK:WHO|Alien Insect|||AlienInsectToken| +|Generate|TOK:WHO|Alien Salamander|||AlienSalamanderToken| +|Generate|TOK:WHO|Alien Warrior|||AlienWarriorToken| +|Generate|TOK:WHO|Beast|||BeastToken| +|Generate|TOK:WHO|Clue|1||ClueToken| +|Generate|TOK:WHO|Clue|2||ClueToken| +|Generate|TOK:WHO|Clue|3||ClueToken| +|Generate|TOK:WHO|Dalek|||DalekToken| +|Generate|TOK:WHO|Dinosaur|||DinosaurFlyingHasteToken| +|Generate|TOK:WHO|Fish|||FishNoAbilityToken| +|Generate|TOK:WHO|Food|1||FoodToken| +|Generate|TOK:WHO|Food|2||FoodToken| +|Generate|TOK:WHO|Food|3||FoodToken| +|Generate|TOK:WHO|Horse|||TheGirlInTheFireplaceHorseToken| +|Generate|TOK:WHO|Human|||TheEleventhHourToken| +|Generate|TOK:WHO|Human Noble|||TheGirlInTheFireplaceHumanNobleToken| +|Generate|TOK:WHO|Mark of the Rani|||MarkOfTheRaniToken| +|Generate|TOK:WHO|Soldier|||SoldierToken| +|Generate|TOK:WHO|Treasure|1||TreasureToken| +|Generate|TOK:WHO|Treasure|2||TreasureToken| +|Generate|TOK:WHO|Treasure|3||TreasureToken| +|Generate|TOK:WHO|Treasure|4||TreasureToken| +|Generate|TOK:WHO|Warrior|||WarriorToken| # PIP +|Generate|TOK:PIP|Alien|||Alien00Token| +|Generate|TOK:PIP|Clue|||ClueToken| +|Generate|TOK:PIP|Food|1||FoodToken| +|Generate|TOK:PIP|Food|2||FoodToken| +|Generate|TOK:PIP|Food|3||FoodToken| +|Generate|TOK:PIP|Human Knight|||ThePrydwenSteelFlagshipHumanKnightToken| +|Generate|TOK:PIP|Human Soldier|||HumanSoldierToken| +|Generate|TOK:PIP|Junk|||JunkToken| |Generate|TOK:PIP|Robot|||RobotToken| +|Generate|TOK:PIP|Settlement|||SettlementToken| +|Generate|TOK:PIP|Soldier|1||SoldierTokenWithHaste| +|Generate|TOK:PIP|Soldier|2||SoldierToken| +|Generate|TOK:PIP|Squirrel|||SquirrelToken| +|Generate|TOK:PIP|Thopter|||ThopterToken| +|Generate|TOK:PIP|Treasure|1||TreasureToken| +|Generate|TOK:PIP|Treasure|2||TreasureToken| +|Generate|TOK:PIP|Warrior|||WarriorToken| +|Generate|TOK:PIP|Wasteland Survival Guide|||WastelandSurvivalGuideToken| +|Generate|TOK:PIP|Zombie Mutant|||ZombieMutantToken| # LCI |Generate|TOK:LCI|Angel|||AngelVigilanceToken| @@ -2501,18 +2716,25 @@ # TDC |Generate|TOK:TDC|Angel|||AngelVigilanceToken| +|Generate|TOK:TDC|Beast|||BeastToken| |Generate|TOK:TDC|Citizen|||CitizenGreenWhiteToken| |Generate|TOK:TDC|Dog|||WhiteDogToken| +|Generate|TOK:TDC|Dragon|1||DragonEggDragonToken| +|Generate|TOK:TDC|Dragon|2||DragonToken2| +|Generate|TOK:TDC|Dragon Egg|||NestingDragonToken| |Generate|TOK:TDC|Dragon Illusion|||DragonIllusionToken| |Generate|TOK:TDC|Eldrazi|||EldraziToken| |Generate|TOK:TDC|Elemental|1||RedElementalToken| |Generate|TOK:TDC|Elemental|2||Elemental11HasteToken| |Generate|TOK:TDC|Elemental|3||Elemental44Token| |Generate|TOK:TDC|First Mate Ragavan|||FirstMateRagavanToken| +|Generate|TOK:TDC|Frog Lizard|||FrogLizardToken| |Generate|TOK:TDC|Goat|||GoatToken| |Generate|TOK:TDC|Gold|||GoldToken| |Generate|TOK:TDC|Human|||HumanToken| +|Generate|TOK:TDC|Inkling|||InklingToken| |Generate|TOK:TDC|Insect|||InsectToken| +|Generate|TOK:TDC|Karox Bladewing|||KaroxBladewingToken| |Generate|TOK:TDC|Myr|||MyrToken| |Generate|TOK:TDC|Plant|||PlantToken| |Generate|TOK:TDC|Rat|||RatToken| @@ -2520,5 +2742,104 @@ |Generate|TOK:TDC|Servo|||ServoToken| |Generate|TOK:TDC|Snake|||OphiomancerSnakeToken| |Generate|TOK:TDC|Soldier|||SoldierArtifactToken| +|Generate|TOK:TDC|Spider|||SpiderToken| |Generate|TOK:TDC|Spirit|||SpiritWhiteToken| |Generate|TOK:TDC|Thopter|||ThopterColorlessToken| + +# ACR +|Generate|TOK:ACR|Assassin|||AssassinMenaceToken| +|Generate|TOK:ACR|Human Rogue|||HumanRogueToken| +|Generate|TOK:ACR|Phobos|||PhobosToken| +|Generate|TOK:ACR|Shapeshifter|||Shapeshifter32Token| +|Generate|TOK:ACR|Treasure|||TreasureToken| + +# DD2 +|Generate|TOK:DD2|Elemental Shaman|||ElementalShamanToken| + +# DSK +|Generate|TOK:DSK|Beast|||BeastieToken| +|Generate|TOK:DSK|Everywhere|||EverywhereToken| +|Generate|TOK:DSK|Glimmer|||GlimmerToken| +|Generate|TOK:DSK|Gremlin|||Gremlin11Token| +|Generate|TOK:DSK|Insect|1||InsectBlackGreenFlyingToken| +|Generate|TOK:DSK|Insect|2||InsectWhiteToken| +|Generate|TOK:DSK|Primo, the Indivisible|||PrimoTheIndivisibleToken| +|Generate|TOK:DSK|Shard|||ShardToken| +|Generate|TOK:DSK|Spider|||Spider22Token| +|Generate|TOK:DSK|Spirit|||SpiritBlueToken| +|Generate|TOK:DSK|Treasure|||TreasureToken| + +# FIN +|Generate|TOK:FIN|Food|||FoodToken| + +# JVC +|Generate|TOK:JVC|Elemental Shaman|||ElementalShamanToken| + +# REX +|Generate|TOK:REX|Dinosaur|||DinosaurToken| +|Generate|TOK:REX|Treasure|||TreasureToken| + +# UGL +|Generate|TOK:UGL|Goblin|||GoblinToken| +|Generate|TOK:UGL|Pegasus|||PegasusToken| +|Generate|TOK:UGL|Soldier|||SoldierToken| +|Generate|TOK:UGL|Squirrel|||SquirrelToken| +|Generate|TOK:UGL|Zombie|||ZombieToken| + +# F12 +|Generate|TOK:F12|Human|||HumanToken| +|Generate|TOK:F12|Wolf|||WolfToken| + +# F17 +|Generate|TOK:F17|Dinosaur|||DinosaurToken| +|Generate|TOK:F17|Pirate|||PirateToken| +|Generate|TOK:F17|Treasure|1||TreasureToken| +|Generate|TOK:F17|Treasure|2||TreasureToken| +|Generate|TOK:F17|Treasure|3||TreasureToken| +|Generate|TOK:F17|Vampire|||IxalanVampireToken| + +# HHO +|Generate|TOK:HHO|Treasure|||TreasureToken| + +# J12 +|Generate|TOK:J12|Centaur|||CentaurToken| + +# J13 +|Generate|TOK:J13|Golem|||HammerOfPurphorosGolemToken| + +# MPR +|Generate|TOK:MPR|Bear|||BearToken| +|Generate|TOK:MPR|Beast|||BeastToken2| +|Generate|TOK:MPR|Bird|||BlueBirdToken| +|Generate|TOK:MPR|Elephant|||ElephantToken| +|Generate|TOK:MPR|Goblin Soldier|||GoblinSoldierToken| +|Generate|TOK:MPR|Saproling|||SaprolingToken| +|Generate|TOK:MPR|Spirit|||SpiritWhiteToken| + +# P03 +|Generate|TOK:P03|Bear|||BearToken| +|Generate|TOK:P03|Bird|||RukhEggBirdToken| +|Generate|TOK:P03|Demon|||DemonFlyingToken| +|Generate|TOK:P03|Goblin|||GoblinToken| +|Generate|TOK:P03|Insect|||InsectToken| +|Generate|TOK:P03|Sliver|||SliverToken| + +# P04 +|Generate|TOK:P04|Angel|||AngelToken| +|Generate|TOK:P04|Beast|||BeastToken| +|Generate|TOK:P04|Myr|||MyrToken| +|Generate|TOK:P04|Pentavite|||PentaviteToken| +|Generate|TOK:P04|Spirit|||SpiritToken| + +# PEMN +|Generate|TOK:PEMN|Zombie|1||ZombieToken| +|Generate|TOK:PEMN|Zombie|2||ZombieToken| + +# PHEL +|Generate|TOK:PHEL|Angel|||AngelToken| + +# PL21 +|Generate|TOK:PL21|Minotaur|||MinotaurToken| + +# PL23 +|Generate|TOK:PL23|Food|||FoodToken| From d29a244ebd59d3fc972f31dacef8219f5d5977e0 Mon Sep 17 00:00:00 2001 From: androosss <101566943+androosss@users.noreply.github.com> Date: Wed, 2 Apr 2025 20:31:02 +0200 Subject: [PATCH 17/59] [TDM] Implement Lotuslight Dancers (#13493) * Implemented Lotusligh Dancers * filter fix * author fix * fix verify test --- .../src/mage/cards/l/LotuslightDancers.java | 135 ++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 2 + 2 files changed, 137 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/l/LotuslightDancers.java diff --git a/Mage.Sets/src/mage/cards/l/LotuslightDancers.java b/Mage.Sets/src/mage/cards/l/LotuslightDancers.java new file mode 100644 index 00000000000..998672a320b --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LotuslightDancers.java @@ -0,0 +1,135 @@ +package mage.cards.l; + +import java.util.UUID; +import mage.MageInt; +import mage.ObjectColor; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; +import mage.abilities.Ability; +import mage.abilities.assignment.common.ColorAssignment; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; + +/** + * + * @author androosss + */ +public final class LotuslightDancers extends CardImpl { + + public LotuslightDancers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{G}{U}"); + + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.BARD); + this.power = new MageInt(3); + this.toughness = new MageInt(6); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // When this creature enters, search your library for a black card, a green card, and a blue card. Put those cards into your graveyard, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility(new LotuslightDancersEffect())); + } + + private LotuslightDancers(final LotuslightDancers card) { + super(card); + } + + @Override + public LotuslightDancers copy() { + return new LotuslightDancers(this); + } +} + +class LotuslightDancersEffect extends OneShotEffect { + + LotuslightDancersEffect() { + super(Outcome.Neutral); + staticText = "search your library for a black card, a green card, and a blue card. " + + "Put those cards into your graveyard, then shuffle."; + } + + private LotuslightDancersEffect(final LotuslightDancersEffect effect) { + super(effect); + } + + @Override + public LotuslightDancersEffect copy() { + return new LotuslightDancersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + TargetCardInLibrary target = new LotuslightDancersTarget(); + controller.searchLibrary(target, source, game); + Cards cards = new CardsImpl(target.getTargets()); + cards.retainZone(Zone.LIBRARY, game); + controller.moveCards(cards, Zone.GRAVEYARD, source, game); + controller.shuffleLibrary(source, game); + return true; + } +} + +class LotuslightDancersTarget extends TargetCardInLibrary { + + private static final FilterCard filter + = new FilterCard("a black card, a green card, and a blue card"); + + static { + filter.add(Predicates.or( + new ColorPredicate(ObjectColor.BLUE), + new ColorPredicate(ObjectColor.BLACK), + new ColorPredicate(ObjectColor.GREEN) + )); + } + + private static final ColorAssignment colorAssigner = new ColorAssignment("U", "B", "G"); + + LotuslightDancersTarget() { + super(0, 3, filter); + } + + private LotuslightDancersTarget(final LotuslightDancersTarget target) { + super(target); + } + + @Override + public LotuslightDancersTarget copy() { + return new LotuslightDancersTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { + return false; + } + Card card = game.getCard(id); + if (card == null) { + return false; + } + if (this.getTargets().isEmpty()) { + return true; + } + Cards cards = new CardsImpl(this.getTargets()); + cards.add(card); + return colorAssigner.getRoleCount(cards, game) >= cards.size(); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 1a0c64cf5d8..1e10c71f25f 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -121,6 +121,8 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Kotis, the Fangkeeper", 202, Rarity.RARE, mage.cards.k.KotisTheFangkeeper.class)); cards.add(new SetCardInfo("Krotiq Nestguard", 148, Rarity.COMMON, mage.cards.k.KrotiqNestguard.class)); cards.add(new SetCardInfo("Lightfoot Technique", 14, Rarity.COMMON, mage.cards.l.LightfootTechnique.class)); + cards.add(new SetCardInfo("Lotuslight Dancers", 204, Rarity.RARE, mage.cards.l.LotuslightDancers.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lotuslight Dancers", 363, Rarity.RARE, mage.cards.l.LotuslightDancers.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Loxodon Battle Priest", 15, Rarity.UNCOMMON, mage.cards.l.LoxodonBattlePriest.class)); cards.add(new SetCardInfo("Mammoth Bellow", 205, Rarity.UNCOMMON, mage.cards.m.MammothBellow.class)); cards.add(new SetCardInfo("Mardu Devotee", 16, Rarity.COMMON, mage.cards.m.MarduDevotee.class)); From 090f181e3e257daee4197302430c83237f39042b Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Wed, 2 Apr 2025 19:53:49 +0100 Subject: [PATCH 18/59] Fix Bandit's Talent hint text --- Mage.Sets/src/mage/cards/b/BanditsTalent.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BanditsTalent.java b/Mage.Sets/src/mage/cards/b/BanditsTalent.java index 49d95f90602..f9e840fbc0c 100644 --- a/Mage.Sets/src/mage/cards/b/BanditsTalent.java +++ b/Mage.Sets/src/mage/cards/b/BanditsTalent.java @@ -144,10 +144,9 @@ class BanditsTalentDiscardEffect extends OneShotEffect { } } - enum BanditsTalentValue implements DynamicValue { instance; - private static final Hint hint = new ValueHint("opponents who have one or fewer cards in hand", instance); + private static final Hint hint = new ValueHint("Opponents who have one or fewer cards in hand", instance); public static Hint getHint() { return hint; From c548a13fdbb68f42b83897b88c9c6b68e0cd856b Mon Sep 17 00:00:00 2001 From: theelk801 Date: Wed, 2 Apr 2025 14:40:15 -0400 Subject: [PATCH 19/59] [TDM] Implement Effortless Master --- .../src/mage/cards/e/EffortlessMaster.java | 68 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 69 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/EffortlessMaster.java diff --git a/Mage.Sets/src/mage/cards/e/EffortlessMaster.java b/Mage.Sets/src/mage/cards/e/EffortlessMaster.java new file mode 100644 index 00000000000..6ddf6b305f3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EffortlessMaster.java @@ -0,0 +1,68 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EffortlessMaster extends CardImpl { + + public EffortlessMaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{R}"); + + this.subtype.add(SubType.ORC); + this.subtype.add(SubType.MONK); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Menace + this.addAbility(new MenaceAbility()); + + // This creature enters with two +1/+1 counters on it if you've cast two or more spells this turn. + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)), + EffortlessMasterCondition.instance, null, + "with two +1/+1 counters on it if you've cast two or more spells this turn" + )); + } + + private EffortlessMaster(final EffortlessMaster card) { + super(card); + } + + @Override + public EffortlessMaster copy() { + return new EffortlessMaster(this); + } +} + +enum EffortlessMasterCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game + .getState() + .getWatcher(SpellsCastWatcher.class) + .getSpellsCastThisTurn(source.getControllerId()) + .size() >= 2; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 1e10c71f25f..6d11bb48e00 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -78,6 +78,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Dragonstorm Globe", 241, Rarity.COMMON, mage.cards.d.DragonstormGlobe.class)); cards.add(new SetCardInfo("Dusyut Earthcarver", 141, Rarity.COMMON, mage.cards.d.DusyutEarthcarver.class)); cards.add(new SetCardInfo("Duty Beyond Death", 10, Rarity.UNCOMMON, mage.cards.d.DutyBeyondDeath.class)); + cards.add(new SetCardInfo("Effortless Master", 181, Rarity.UNCOMMON, mage.cards.e.EffortlessMaster.class)); cards.add(new SetCardInfo("Elspeth, Storm Slayer", 11, Rarity.MYTHIC, mage.cards.e.ElspethStormSlayer.class)); cards.add(new SetCardInfo("Embermouth Sentinel", 242, Rarity.COMMON, mage.cards.e.EmbermouthSentinel.class)); cards.add(new SetCardInfo("Encroaching Dragonstorm", 142, Rarity.UNCOMMON, mage.cards.e.EncroachingDragonstorm.class)); From d490f5fb20f1ba1177e7eafb49e9a3d6b87461cd Mon Sep 17 00:00:00 2001 From: theelk801 Date: Wed, 2 Apr 2025 14:41:29 -0400 Subject: [PATCH 20/59] [TDM] Implement Flamehold Grappler --- .../src/mage/cards/f/FlameholdGrappler.java | 46 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 47 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FlameholdGrappler.java diff --git a/Mage.Sets/src/mage/cards/f/FlameholdGrappler.java b/Mage.Sets/src/mage/cards/f/FlameholdGrappler.java new file mode 100644 index 00000000000..2d96ae46614 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlameholdGrappler.java @@ -0,0 +1,46 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.CopyNextSpellDelayedTriggeredAbility; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FlameholdGrappler extends CardImpl { + + public FlameholdGrappler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // When this creature enters, copy the next spell you cast this turn when you cast it. You may choose new targets for the copy. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateDelayedTriggeredAbilityEffect( + new CopyNextSpellDelayedTriggeredAbility(StaticFilters.FILTER_SPELL) + ))); + } + + private FlameholdGrappler(final FlameholdGrappler card) { + super(card); + } + + @Override + public FlameholdGrappler copy() { + return new FlameholdGrappler(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 6d11bb48e00..418107a899d 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -88,6 +88,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Fangkeeper's Familiar", 183, Rarity.RARE, mage.cards.f.FangkeepersFamiliar.class)); cards.add(new SetCardInfo("Felothar, Dawn of the Abzan", 184, Rarity.RARE, mage.cards.f.FelotharDawnOfTheAbzan.class)); cards.add(new SetCardInfo("Fire-Rim Form", 107, Rarity.COMMON, mage.cards.f.FireRimForm.class)); + cards.add(new SetCardInfo("Flamehold Grappler", 185, Rarity.RARE, mage.cards.f.FlameholdGrappler.class)); cards.add(new SetCardInfo("Fleeting Effigy", 108, Rarity.UNCOMMON, mage.cards.f.FleetingEffigy.class)); cards.add(new SetCardInfo("Forest", 285, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fortress Kin-Guard", 12, Rarity.COMMON, mage.cards.f.FortressKinGuard.class)); From 4062f6d85a250c8d283e6dc6f93e83c94a033116 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Wed, 2 Apr 2025 14:45:46 -0400 Subject: [PATCH 21/59] [TDM] Implement Formation Breaker --- .../src/mage/cards/f/FormationBreaker.java | 65 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 66 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FormationBreaker.java diff --git a/Mage.Sets/src/mage/cards/f/FormationBreaker.java b/Mage.Sets/src/mage/cards/f/FormationBreaker.java new file mode 100644 index 00000000000..9510dbd3408 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FormationBreaker.java @@ -0,0 +1,65 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.CounterAnyPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FormationBreaker extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + private static final FilterPermanent filter2 = new FilterControlledCreaturePermanent(); + + static { + filter2.add(CounterAnyPredicate.instance); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter2); + private static final Hint hint = new ConditionHint(condition, "You control a creature with a counter on it"); + + public FormationBreaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.BEAST); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Creatures with power less than this creature's power can't block it. + this.addAbility(new SimpleStaticAbility(new CantBeBlockedByCreaturesSourceEffect(filter, Duration.WhileOnBattlefield) + .setText("creatures with power less than this creature's power can't block it"))); + + // As long as you control a creature with a counter on it, this creature gets +1/+2. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostSourceEffect(1, 2, Duration.WhileOnBattlefield), + condition, "as long as you control a creature with a counter on it, this creature gets +1/+2" + )).addHint(hint)); + } + + private FormationBreaker(final FormationBreaker card) { + super(card); + } + + @Override + public FormationBreaker copy() { + return new FormationBreaker(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 418107a899d..d1fff0bbf23 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -91,6 +91,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Flamehold Grappler", 185, Rarity.RARE, mage.cards.f.FlameholdGrappler.class)); cards.add(new SetCardInfo("Fleeting Effigy", 108, Rarity.UNCOMMON, mage.cards.f.FleetingEffigy.class)); cards.add(new SetCardInfo("Forest", 285, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Formation Breaker", 143, Rarity.UNCOMMON, mage.cards.f.FormationBreaker.class)); cards.add(new SetCardInfo("Fortress Kin-Guard", 12, Rarity.COMMON, mage.cards.f.FortressKinGuard.class)); cards.add(new SetCardInfo("Fresh Start", 46, Rarity.UNCOMMON, mage.cards.f.FreshStart.class)); cards.add(new SetCardInfo("Frontier Bivouac", 256, Rarity.UNCOMMON, mage.cards.f.FrontierBivouac.class)); From caf5984b62375b1f720b5e1dcfc54f728de5f2be Mon Sep 17 00:00:00 2001 From: theelk801 Date: Wed, 2 Apr 2025 14:49:02 -0400 Subject: [PATCH 22/59] [TDM] Implement Karakyk Guardian --- .../src/mage/cards/k/KarakykGuardian.java | 110 ++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 111 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/k/KarakykGuardian.java diff --git a/Mage.Sets/src/mage/cards/k/KarakykGuardian.java b/Mage.Sets/src/mage/cards/k/KarakykGuardian.java new file mode 100644 index 00000000000..6fd569725a6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KarakykGuardian.java @@ -0,0 +1,110 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KarakykGuardian extends CardImpl { + + public KarakykGuardian(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{U}{R}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // This creature has hexproof if it hasn't dealt damage yet. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new GainAbilitySourceEffect(HexproofAbility.getInstance()), + KarakykGuardianCondition.instance, "{this} has hexproof if it hasn't dealt damage yet" + )), new KarakykGuardianWatcher()); + } + + private KarakykGuardian(final KarakykGuardian card) { + super(card); + } + + @Override + public KarakykGuardian copy() { + return new KarakykGuardian(this); + } +} + +enum KarakykGuardianCondition implements Condition { + + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getSourceId()); + KarakykGuardianWatcher watcher = game.getState().getWatcher(KarakykGuardianWatcher.class); + return permanent != null && !watcher.getDamagers().contains(new MageObjectReference(permanent, game)); + } + + @Override + public String toString() { + return "{this} hasn't dealt damage yet"; + } + +} + +class KarakykGuardianWatcher extends Watcher { + + private final Set damagers = new HashSet<>(); + + public KarakykGuardianWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGED_PERMANENT: + case DAMAGED_PLAYER: + break; + default: + return; + } + Permanent permanent = game.getPermanent(event.getSourceId()); + if (permanent != null) { + damagers.add(new MageObjectReference(permanent, game)); + } + } + + public Set getDamagers() { + return damagers; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index d1fff0bbf23..818979e5a06 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -114,6 +114,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Jeskai Monument", 244, Rarity.UNCOMMON, mage.cards.j.JeskaiMonument.class)); cards.add(new SetCardInfo("Jeskai Shrinekeeper", 197, Rarity.UNCOMMON, mage.cards.j.JeskaiShrinekeeper.class)); cards.add(new SetCardInfo("Jungle Hollow", 258, Rarity.COMMON, mage.cards.j.JungleHollow.class)); + cards.add(new SetCardInfo("Karakyk Guardian", 198, Rarity.UNCOMMON, mage.cards.k.KarakykGuardian.class)); cards.add(new SetCardInfo("Kheru Goldkeeper", 199, Rarity.UNCOMMON, mage.cards.k.KheruGoldkeeper.class)); cards.add(new SetCardInfo("Kin-Tree Nurturer", 83, Rarity.COMMON, mage.cards.k.KinTreeNurturer.class)); cards.add(new SetCardInfo("Kin-Tree Severance", 200, Rarity.UNCOMMON, mage.cards.k.KinTreeSeverance.class)); From 32d0e3d74095a424953e664ee32854dc08dac4f1 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Wed, 2 Apr 2025 14:52:55 -0400 Subject: [PATCH 23/59] [TDM] Implement Dragonclaw Strike --- .../src/mage/cards/d/DragonclawStrike.java | 73 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 74 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/d/DragonclawStrike.java diff --git a/Mage.Sets/src/mage/cards/d/DragonclawStrike.java b/Mage.Sets/src/mage/cards/d/DragonclawStrike.java new file mode 100644 index 00000000000..c8318674d70 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DragonclawStrike.java @@ -0,0 +1,73 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.FightTargetsEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DragonclawStrike extends CardImpl { + + public DragonclawStrike(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2/U}{2/R}{2/G}"); + + // Double the power and toughness of target creature you control until end of turn. Then it fights up to one target creature an opponent controls. + this.getSpellAbility().addEffect(new DragonclawStrikeEffect()); + this.getSpellAbility().addEffect(new FightTargetsEffect() + .setText("Then it fights up to one target creature an opponent controls")); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetOpponentsCreaturePermanent(0, 1)); + } + + private DragonclawStrike(final DragonclawStrike card) { + super(card); + } + + @Override + public DragonclawStrike copy() { + return new DragonclawStrike(this); + } +} + +class DragonclawStrikeEffect extends OneShotEffect { + + DragonclawStrikeEffect() { + super(Outcome.Benefit); + staticText = "double the power and toughness of target creature you control until end of turn"; + } + + private DragonclawStrikeEffect(final DragonclawStrikeEffect effect) { + super(effect); + } + + @Override + public DragonclawStrikeEffect copy() { + return new DragonclawStrikeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + game.addEffect(new BoostTargetEffect( + permanent.getPower().getValue(), + permanent.getToughness().getValue() + ).setTargetPointer(new FixedTarget(permanent, game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 818979e5a06..e5fadd39e9d 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -73,6 +73,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Dragonback Assault", 179, Rarity.MYTHIC, mage.cards.d.DragonbackAssault.class)); cards.add(new SetCardInfo("Dragonback Lancer", 9, Rarity.COMMON, mage.cards.d.DragonbackLancer.class)); cards.add(new SetCardInfo("Dragonbroods' Relic", 140, Rarity.UNCOMMON, mage.cards.d.DragonbroodsRelic.class)); + cards.add(new SetCardInfo("Dragonclaw Strike", 180, Rarity.UNCOMMON, mage.cards.d.DragonclawStrike.class)); cards.add(new SetCardInfo("Dragonologist", 42, Rarity.RARE, mage.cards.d.Dragonologist.class)); cards.add(new SetCardInfo("Dragonstorm Forecaster", 43, Rarity.UNCOMMON, mage.cards.d.DragonstormForecaster.class)); cards.add(new SetCardInfo("Dragonstorm Globe", 241, Rarity.COMMON, mage.cards.d.DragonstormGlobe.class)); From 48173bee0056e1bb4f0b844f38f92069b3d5c159 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Wed, 2 Apr 2025 14:58:11 -0400 Subject: [PATCH 24/59] [TDM] Implement Clarion Conquerer --- .../src/mage/cards/c/ClarionConqueror.java | 72 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 73 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/ClarionConqueror.java diff --git a/Mage.Sets/src/mage/cards/c/ClarionConqueror.java b/Mage.Sets/src/mage/cards/c/ClarionConqueror.java new file mode 100644 index 00000000000..efaf4e73ab0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ClarionConqueror.java @@ -0,0 +1,72 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.RestrictionEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ClarionConqueror extends CardImpl { + + public ClarionConqueror(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Activated abilities of artifacts, creatures, and planeswalkers can't be activated. + this.addAbility(new SimpleStaticAbility(new ClarionConquerorEffect())); + } + + private ClarionConqueror(final ClarionConqueror card) { + super(card); + } + + @Override + public ClarionConqueror copy() { + return new ClarionConqueror(this); + } +} + +class ClarionConquerorEffect extends RestrictionEffect { + + ClarionConquerorEffect() { + super(Duration.WhileOnBattlefield); + staticText = "activated abilities of artifacts, creatures, and planeswalkers can't be activated"; + } + + private ClarionConquerorEffect(final ClarionConquerorEffect effect) { + super(effect); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + return permanent.isArtifact(game) || permanent.isCreature(game) || permanent.isPlaneswalker(game); + } + + @Override + public boolean canUseActivatedAbilities(Permanent permanent, Ability source, Game game, boolean canUseChooseDialogs) { + return false; + } + + @Override + public ClarionConquerorEffect copy() { + return new ClarionConquerorEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index e5fadd39e9d..00384deb02b 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -52,6 +52,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Caustic Exhale", 74, Rarity.COMMON, mage.cards.c.CausticExhale.class)); cards.add(new SetCardInfo("Champion of Dusan", 137, Rarity.COMMON, mage.cards.c.ChampionOfDusan.class)); cards.add(new SetCardInfo("Channeled Dragonfire", 102, Rarity.UNCOMMON, mage.cards.c.ChanneledDragonfire.class)); + cards.add(new SetCardInfo("Clarion Conqueror", 5, Rarity.RARE, mage.cards.c.ClarionConqueror.class)); cards.add(new SetCardInfo("Constrictor Sage", 39, Rarity.UNCOMMON, mage.cards.c.ConstrictorSage.class)); cards.add(new SetCardInfo("Coordinated Maneuver", 6, Rarity.COMMON, mage.cards.c.CoordinatedManeuver.class)); cards.add(new SetCardInfo("Cori Mountain Monastery", 252, Rarity.RARE, mage.cards.c.CoriMountainMonastery.class)); From e223e35e6da65dedc4919a7b1ef2ac8c7c6f9c23 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Wed, 2 Apr 2025 15:01:29 -0400 Subject: [PATCH 25/59] [TDM] add missing predicate to Formation Breaker --- .../src/mage/cards/f/FormationBreaker.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Mage.Sets/src/mage/cards/f/FormationBreaker.java b/Mage.Sets/src/mage/cards/f/FormationBreaker.java index 9510dbd3408..492a6fcc11e 100644 --- a/Mage.Sets/src/mage/cards/f/FormationBreaker.java +++ b/Mage.Sets/src/mage/cards/f/FormationBreaker.java @@ -1,6 +1,7 @@ package mage.cards.f; import mage.MageInt; +import mage.MageObject; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.Condition; import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; @@ -17,8 +18,13 @@ import mage.constants.SubType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.permanent.CounterAnyPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import java.util.Optional; import java.util.UUID; /** @@ -30,6 +36,7 @@ public final class FormationBreaker extends CardImpl { private static final FilterPermanent filter2 = new FilterControlledCreaturePermanent(); static { + filter.add(FormationBreakerPredicate.instance); filter2.add(CounterAnyPredicate.instance); } @@ -63,3 +70,17 @@ public final class FormationBreaker extends CardImpl { return new FormationBreaker(this); } } + +enum FormationBreakerPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return Optional + .ofNullable(input.getSource().getSourcePermanentIfItStillExists(game)) + .map(MageObject::getPower) + .map(MageInt::getValue) + .map(x -> input.getObject().getPower().getValue() < x) + .orElse(false); + } +} From bd7b66c17d31fd6ff5e10e184aac07538693b73d Mon Sep 17 00:00:00 2001 From: theelk801 Date: Wed, 2 Apr 2025 15:07:29 -0400 Subject: [PATCH 26/59] [TDM] Implement Starry-Eyed Skyrider --- .../src/mage/cards/s/StarryEyedSkyrider.java | 65 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 66 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/StarryEyedSkyrider.java diff --git a/Mage.Sets/src/mage/cards/s/StarryEyedSkyrider.java b/Mage.Sets/src/mage/cards/s/StarryEyedSkyrider.java new file mode 100644 index 00000000000..4e1325fcc25 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StarryEyedSkyrider.java @@ -0,0 +1,65 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.permanent.AttackingPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StarryEyedSkyrider extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("attacking tokens"); + + static { + filter.add(AttackingPredicate.instance); + filter.add(TokenPredicate.TRUE); + } + + public StarryEyedSkyrider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCOUT); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever this creature attacks, another target creature you control gains flying until end of turn. + Ability ability = new AttacksTriggeredAbility(new GainAbilityTargetEffect(FlyingAbility.getInstance())); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL)); + this.addAbility(ability); + + // Attacking tokens you control have flying. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.WhileOnBattlefield, filter + ))); + } + + private StarryEyedSkyrider(final StarryEyedSkyrider card) { + super(card); + } + + @Override + public StarryEyedSkyrider copy() { + return new StarryEyedSkyrider(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 00384deb02b..1b6d33d1b3c 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -191,6 +191,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Sonic Shrieker", 226, Rarity.UNCOMMON, mage.cards.s.SonicShrieker.class)); cards.add(new SetCardInfo("Spectral Denial", 58, Rarity.UNCOMMON, mage.cards.s.SpectralDenial.class)); cards.add(new SetCardInfo("Stadium Headliner", 122, Rarity.RARE, mage.cards.s.StadiumHeadliner.class)); + cards.add(new SetCardInfo("Starry-Eyed Skyrider", 25, Rarity.UNCOMMON, mage.cards.s.StarryEyedSkyrider.class)); cards.add(new SetCardInfo("Static Snare", 26, Rarity.UNCOMMON, mage.cards.s.StaticSnare.class)); cards.add(new SetCardInfo("Stormbeacon Blade", 27, Rarity.UNCOMMON, mage.cards.s.StormbeaconBlade.class)); cards.add(new SetCardInfo("Stormplain Detainment", 28, Rarity.COMMON, mage.cards.s.StormplainDetainment.class)); From 35b0ae8abd63d5c6534c974f84040b9e21a08c38 Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Wed, 2 Apr 2025 15:38:03 -0400 Subject: [PATCH 27/59] [TDC] Implement Elsha, Threefold Master --- .../mage/cards/e/ElshaThreefoldMaster.java | 51 +++++++++++++++++++ .../mage/sets/TarkirDragonstormCommander.java | 1 + 2 files changed, 52 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/ElshaThreefoldMaster.java diff --git a/Mage.Sets/src/mage/cards/e/ElshaThreefoldMaster.java b/Mage.Sets/src/mage/cards/e/ElshaThreefoldMaster.java new file mode 100644 index 00000000000..c6e328b9144 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElshaThreefoldMaster.java @@ -0,0 +1,51 @@ +package mage.cards.e; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.ProwessAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.MonasteryMentorToken; + +/** + * + * @author Grath + */ +public final class ElshaThreefoldMaster extends CardImpl { + + public ElshaThreefoldMaster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.DJINN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Prowess + this.addAbility(new ProwessAbility()); + + // Whenever Elsha deals combat damage to a player, create that many 1/1 white Monk creature tokens with prowess. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( + new CreateTokenEffect(new MonasteryMentorToken(), SavedDamageValue.MANY), false, true)); + } + + private ElshaThreefoldMaster(final ElshaThreefoldMaster card) { + super(card); + } + + @Override + public ElshaThreefoldMaster copy() { + return new ElshaThreefoldMaster(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java b/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java index 15997a8fce0..0cae8e90d6a 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java @@ -107,6 +107,7 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Electrodominance", 212, Rarity.RARE, mage.cards.e.Electrodominance.class)); cards.add(new SetCardInfo("Elemental Bond", 254, Rarity.UNCOMMON, mage.cards.e.ElementalBond.class)); cards.add(new SetCardInfo("Eliminate the Competition", 179, Rarity.RARE, mage.cards.e.EliminateTheCompetition.class)); + cards.add(new SetCardInfo("Elsha, Threefold Master", 2, Rarity.MYTHIC, mage.cards.e.ElshaThreefoldMaster.class)); cards.add(new SetCardInfo("Emeria Angel", 114, Rarity.RARE, mage.cards.e.EmeriaAngel.class)); cards.add(new SetCardInfo("Exotic Orchard", 360, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); cards.add(new SetCardInfo("Expansion // Explosion", 287, Rarity.RARE, mage.cards.e.ExpansionExplosion.class)); From dfb68964531d9d1416518ae90524842a1d4028b4 Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Thu, 3 Apr 2025 18:34:04 +0100 Subject: [PATCH 28/59] Skip prompting player with no blockers to select blockers (#13496) * Don't prompt creatureless player to select blockers * Move getting possible blockers back to while loop * Several preferences text improvements, always skip select blockers prompt if no blockers --- .../src/main/java/mage/client/dialog/PreferencesDialog.form | 6 +++--- .../src/main/java/mage/client/dialog/PreferencesDialog.java | 6 +++--- .../src/mage/player/human/HumanPlayer.java | 5 +++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form index 2fb5afdb761..0138479d0b1 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.form @@ -2621,19 +2621,19 @@ - + - + - + diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index 94238a49468..1a0b396cc53 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -2396,15 +2396,15 @@ public class PreferencesDialog extends javax.swing.JDialog { phases_stopSettings.add(cbStopAttack); cbStopBlockWithAny.setSelected(true); - cbStopBlockWithAny.setText("STOP skips on declare blockers if ANY blockers are available"); + cbStopBlockWithAny.setText("STOP skips when attacked and on declare blockers if ANY blockers are available"); cbStopBlockWithAny.setActionCommand(""); phases_stopSettings.add(cbStopBlockWithAny); - cbStopBlockWithZero.setText("STOP skips on declare blockers if ZERO blockers are available"); + cbStopBlockWithZero.setText("STOP skips when attacked if ZERO blockers are available"); cbStopBlockWithZero.setActionCommand(""); phases_stopSettings.add(cbStopBlockWithZero); - cbStopOnNewStackObjects.setText("Skip to STACK resolved (F10): stop on new objects added (on) or stop until empty (off)"); + cbStopOnNewStackObjects.setText("Skip to STACK resolved (F10): stop on new objects added (on) or stop when stack empty (off)"); cbStopOnNewStackObjects.setActionCommand(""); cbStopOnNewStackObjects.setPreferredSize(new java.awt.Dimension(300, 25)); phases_stopSettings.add(cbStopOnNewStackObjects); diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index f035a45fe6b..7115082ba57 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -2072,7 +2072,6 @@ public class HumanPlayer extends PlayerImpl { // stop skip on any/zero permanents available int possibleBlockersCount = game.getBattlefield().count(filter, playerId, source, game); boolean canStopOnAny = possibleBlockersCount != 0 && getControllingPlayersUserData(game).getUserSkipPrioritySteps().isStopOnDeclareBlockersWithAnyPermanents(); - boolean canStopOnZero = possibleBlockersCount == 0 && getControllingPlayersUserData(game).getUserSkipPrioritySteps().isStopOnDeclareBlockersWithZeroPermanents(); // skip declare blocker step // as opposed to declare attacker - it can be skipped by ANY skip button TODO: make same for declare attackers and rework skip buttons (normal and forced) @@ -2081,9 +2080,11 @@ public class HumanPlayer extends PlayerImpl { || passedTurn || passedUntilEndOfTurn || passedUntilNextMain; - if (skipButtonActivated && !canStopOnAny && !canStopOnZero) { + if (skipButtonActivated && !canStopOnAny) { return; } + // Skip prompt to select blockers if player has none + if (possibleBlockersCount == 0) return; while (canRespond()) { prepareForResponse(game); From 4f2279bd1d7669694c317650f854309be2d3905e Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Fri, 4 Apr 2025 03:37:56 +0100 Subject: [PATCH 29/59] Fix Fungal Plots exiling cards to source exile zone --- Mage.Sets/src/mage/cards/f/FungalPlots.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/f/FungalPlots.java b/Mage.Sets/src/mage/cards/f/FungalPlots.java index 4f9dc2b38d2..d833aba5152 100644 --- a/Mage.Sets/src/mage/cards/f/FungalPlots.java +++ b/Mage.Sets/src/mage/cards/f/FungalPlots.java @@ -1,4 +1,3 @@ - package mage.cards.f; import java.util.UUID; @@ -13,12 +12,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterCreatureCard; import mage.game.permanent.token.SaprolingToken; import mage.target.common.TargetCardInYourGraveyard; -import mage.target.common.TargetControlledPermanent; /** * @@ -40,7 +37,7 @@ public final class FungalPlots extends CardImpl { SimpleActivatedAbility ability = new SimpleActivatedAbility( new CreateTokenEffect(new SaprolingToken()), new ManaCostsImpl<>("{1}{G}")); - ability.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(filter))); + ability.addCost(new ExileFromGraveCost(new TargetCardInYourGraveyard(filter)).withSourceExileZone(false)); this.addAbility(ability); // Sacrifice two Saprolings: You gain 2 life and draw a card. From 41d3464b5c9bf8efcbc3fbfe48bfbb1ba489387f Mon Sep 17 00:00:00 2001 From: ilyagart <55062984+ilyagart@users.noreply.github.com> Date: Fri, 4 Apr 2025 20:41:32 +0300 Subject: [PATCH 30/59] [TDC] Implement Ureni of the Unwritten (#13497) --- .../src/mage/cards/u/UreniOfTheUnwritten.java | 94 +++++++++++++++++++ .../mage/sets/TarkirDragonstormCommander.java | 1 + 2 files changed, 95 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java diff --git a/Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java b/Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java new file mode 100644 index 00000000000..fa56c051e20 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java @@ -0,0 +1,94 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.common.FilterCreatureCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; + +import java.util.UUID; + +/** + * @author ilyagart + */ +public final class UreniOfTheUnwritten extends CardImpl { + + public UreniOfTheUnwritten(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Whenever Ureni enters or attacks, look at the top eight cards of your library. You may put a Dragon creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new UreniOfTheUnwrittenEffect(), false)); + } + + private UreniOfTheUnwritten(final UreniOfTheUnwritten card) { + super(card); + } + + @Override + public UreniOfTheUnwritten copy() { + return new UreniOfTheUnwritten(this); + } +} + +class UreniOfTheUnwrittenEffect extends OneShotEffect { + + UreniOfTheUnwrittenEffect() { + super(Outcome.Benefit); + this.staticText = "look at the top eight cards of your library. You may put a Dragon creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order."; + } + + private UreniOfTheUnwrittenEffect(final mage.cards.u.UreniOfTheUnwrittenEffect effect) { + super(effect); + } + + @Override + public mage.cards.u.UreniOfTheUnwrittenEffect copy() { + return new mage.cards.u.UreniOfTheUnwrittenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, 8)); + if (!cards.isEmpty()) { + FilterCreatureCard filter = new FilterCreatureCard("Dragon creature cards"); + filter.add(SubType.DRAGON.getPredicate()); + TargetCard targetCard = new TargetCard(0, 1, Zone.LIBRARY, filter); + targetCard.withNotTarget(true); + controller.choose(Outcome.PutCreatureInPlay, cards, targetCard, source, game); + controller.moveCards(game.getCard(targetCard.getFirstTarget()), Zone.BATTLEFIELD, source, game); + cards.retainZone(Zone.LIBRARY, game); + controller.putCardsOnBottomOfLibrary(cards, game, source, false); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java b/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java index 0cae8e90d6a..8cba642f9e3 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java @@ -321,6 +321,7 @@ public final class TarkirDragonstormCommander extends ExpansionSet { cards.add(new SetCardInfo("Tree of Redemption", 97, Rarity.MYTHIC, mage.cards.t.TreeOfRedemption.class)); cards.add(new SetCardInfo("Twilight Drover", 136, Rarity.RARE, mage.cards.t.TwilightDrover.class)); cards.add(new SetCardInfo("Twilight Mire", 409, Rarity.RARE, mage.cards.t.TwilightMire.class)); + cards.add(new SetCardInfo("Ureni of the Unwritten", 9, Rarity.MYTHIC, mage.cards.u.UreniOfTheUnwritten.class)); cards.add(new SetCardInfo("Vanquish the Horde", 91, Rarity.RARE, mage.cards.v.VanquishTheHorde.class)); cards.add(new SetCardInfo("Vault of the Archangel", 410, Rarity.RARE, mage.cards.v.VaultOfTheArchangel.class)); cards.add(new SetCardInfo("Velomachus Lorehold", 309, Rarity.MYTHIC, mage.cards.v.VelomachusLorehold.class)); From 8e1805c8742cdba1d4b5078facff8601116cc458 Mon Sep 17 00:00:00 2001 From: jmlundeen Date: Fri, 4 Apr 2025 23:25:53 -0500 Subject: [PATCH 31/59] Fix Ward batch event bug Fixes #13498 getTargetingStackObject wasn't processing all stackObjects in a batch event added tests for some related cards that also use the method - Agrus Kos, Eternal Soldier - Pawpatch Recruit - Ward Ability --- .../mage/cards/a/AgrusKosEternalSoldier.java | 2 +- .../src/mage/cards/p/PawpatchRecruit.java | 2 +- .../cards/abilities/keywords/WardTest.java | 50 +++++++++++++++++++ .../cards/single/blb/PawpatchRecruitTest.java | 41 +++++++++++++++ .../j22/AgrusKosEternalSoldierTest.java | 31 ++++++++++++ .../BecomesTargetAnyTriggeredAbility.java | 2 +- ...BecomesTargetAttachedTriggeredAbility.java | 2 +- ...comesTargetControllerTriggeredAbility.java | 2 +- .../BecomesTargetSourceTriggeredAbility.java | 2 +- .../mage/abilities/keyword/WardAbility.java | 2 +- Mage/src/main/java/mage/util/CardUtil.java | 13 ++++- ...rOfTimesPermanentTargetedATurnWatcher.java | 2 +- 12 files changed, 141 insertions(+), 10 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/blb/PawpatchRecruitTest.java diff --git a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java index 3789ce55f66..7253374c3df 100644 --- a/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java +++ b/Mage.Sets/src/mage/cards/a/AgrusKosEternalSoldier.java @@ -90,7 +90,7 @@ class AgrusKosEternalSoldierTriggeredAbility extends TriggeredAbilityImpl { if (!event.getTargetId().equals(getSourceId())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || targetingObject instanceof Spell) { return false; } diff --git a/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java b/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java index f6ea0a2222a..0a72089951c 100644 --- a/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java +++ b/Mage.Sets/src/mage/cards/p/PawpatchRecruit.java @@ -96,7 +96,7 @@ class PawpatchRecruitTriggeredAbility extends TriggeredAbilityImpl { if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java index b200bb76bfb..323d9b4cdd3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/WardTest.java @@ -30,4 +30,54 @@ public class WardTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Solitude", 1); assertPermanentCount(playerB, "Waterfall Aerialist", 1); } + + @Test + public void wardPanharmonicon() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Panharmonicon"); + addCard(Zone.BATTLEFIELD, playerA, "Young Red Dragon"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.BATTLEFIELD, playerB, "Roaming Throne"); + addCard(Zone.HAND, playerA, "Scourge of Valkas"); + + setChoice(playerB, "Dragon"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scourge of Valkas"); + setChoice(playerA, "Whenever {this} or another Dragon"); + addTarget(playerA, "Roaming Throne"); + addTarget(playerA, "Roaming Throne"); + setChoice(playerB, "ward {2}"); + setChoice(playerA, "Yes"); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertPermanentCount(playerB, "Roaming Throne", 1); + assertDamageReceived(playerB, "Roaming Throne", 2); + } + + @Test + public void wardPanharmoniconCounter() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Panharmonicon"); + addCard(Zone.BATTLEFIELD, playerA, "Young Red Dragon"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.BATTLEFIELD, playerB, "Roaming Throne"); + addCard(Zone.HAND, playerA, "Scourge of Valkas"); + + setChoice(playerB, "Dragon"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scourge of Valkas"); + setChoice(playerA, "Whenever {this} or another Dragon"); + addTarget(playerA, "Roaming Throne"); + addTarget(playerA, "Roaming Throne"); + setChoice(playerB, "ward {2}"); + setChoice(playerA, "No"); + setChoice(playerA, "No"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertPermanentCount(playerB, "Roaming Throne", 1); + assertDamageReceived(playerB, "Roaming Throne", 0); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/PawpatchRecruitTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/PawpatchRecruitTest.java new file mode 100644 index 00000000000..343d8267800 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/PawpatchRecruitTest.java @@ -0,0 +1,41 @@ +package org.mage.test.cards.single.blb; + +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 PawpatchRecruitTest extends CardTestPlayerBase { + + private static final String paw = "Pawpatch Recruit"; + private static final String cub = "Bear Cub"; + private static final String panharm = "Panharmonicon"; + private static final String prowler = "Chrome Prowler"; + + @Test + public void testCopiedTriggerAbility() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, paw); + addCard(Zone.BATTLEFIELD, playerB, cub); + addCard(Zone.BATTLEFIELD, playerA, panharm); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.HAND, playerA, prowler); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, prowler); + setChoice(playerA, "When {this} enters"); + addTarget(playerA, paw); + addTarget(playerA, cub); + setChoice(playerB, "Whenever a creature"); + addTarget(playerB, paw); + addTarget(playerB, cub); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertTapped(paw, true); + assertTapped(cub, true); + assertCounterCount(playerB, paw, CounterType.P1P1, 1); + assertCounterCount(playerB, cub, CounterType.P1P1, 1); + } +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java index 0df627d9194..349dda8af12 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/j22/AgrusKosEternalSoldierTest.java @@ -67,4 +67,35 @@ public class AgrusKosEternalSoldierTest extends CardTestPlayerBase { assertLife(playerB, 20); } + @Test + public void testCopiedTriggerAbility() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerB, agrus); + addCard(Zone.BATTLEFIELD, playerB, turtle); + addCard(Zone.BATTLEFIELD, playerB, firewalker); + addCard(Zone.BATTLEFIELD, playerB, "Plateau", 4); + addCard(Zone.BATTLEFIELD, playerA, "Panharmonicon"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7); + addCard(Zone.HAND, playerA, "Smoldering Werewolf"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Smoldering Werewolf"); + setChoice(playerA, "When {this} enters, it deals"); + addTarget(playerA, agrus); + addTarget(playerA, agrus); + setChoice(playerB, true); // gain life + setChoice(playerB, "Whenever {this} becomes"); + setChoice(playerB, true); // pay to copy + setChoice(playerB, true); // pay to copy + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertDamageReceived(playerB, agrus, 2); + assertDamageReceived(playerB, turtle, 2); + assertDamageReceived(playerB, firewalker, 0); + assertLife(playerA, 20); + assertLife(playerB, 21); + } + } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java index 9e5a7490e98..13377578b94 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAnyTriggeredAbility.java @@ -70,7 +70,7 @@ public class BecomesTargetAnyTriggeredAbility extends TriggeredAbilityImpl { if (permanent == null || !filterTarget.match(permanent, getControllerId(), this, game)) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java index 2995cacb11b..2c4c8d13a85 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetAttachedTriggeredAbility.java @@ -54,7 +54,7 @@ public class BecomesTargetAttachedTriggeredAbility extends TriggeredAbilityImpl if (enchantment == null || enchantment.getAttachedTo() == null || !event.getTargetId().equals(enchantment.getAttachedTo())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java index c31b39b22d2..9b60880a21d 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetControllerTriggeredAbility.java @@ -63,7 +63,7 @@ public class BecomesTargetControllerTriggeredAbility extends TriggeredAbilityImp return false; } } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filterStack.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java index e1fcc98a772..0b410c7e078 100644 --- a/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/BecomesTargetSourceTriggeredAbility.java @@ -57,7 +57,7 @@ public class BecomesTargetSourceTriggeredAbility extends TriggeredAbilityImpl { if (!event.getTargetId().equals(getSourceId())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !filter.match(targetingObject, getControllerId(), this, game)) { return false; } diff --git a/Mage/src/main/java/mage/abilities/keyword/WardAbility.java b/Mage/src/main/java/mage/abilities/keyword/WardAbility.java index b6bebf87769..c68ff475567 100644 --- a/Mage/src/main/java/mage/abilities/keyword/WardAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/WardAbility.java @@ -77,7 +77,7 @@ public class WardAbility extends TriggeredAbilityImpl { if (!getSourceId().equals(event.getTargetId())) { return false; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getId().toString(), event, game); if (targetingObject == null || !game.getOpponents(getControllerId()).contains(targetingObject.getControllerId())) { return false; } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 7ba17e4036f..b55d2a33da6 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1086,13 +1086,22 @@ public final class CardUtil { * @param game the Game from checkTrigger() or watch() * @return the StackObject which targeted the source, or null if not found */ - public static StackObject getTargetingStackObject(GameEvent event, Game game) { + public static StackObject getTargetingStackObject(String checkingReference, GameEvent event, Game game) { // In case of multiple simultaneous triggered abilities from the same source, // need to get the actual one that targeted, see #8026, #8378 // Also avoids triggering on cancelled selections, see #8802 + String stateKey = "targetedMap" + checkingReference; + Map> targetMap = (Map>) game.getState().getValue(stateKey); + // targetMap: key - targetId; value - Set of stackObject Ids + if (targetMap == null) { + targetMap = new HashMap<>(); + } else { + targetMap = new HashMap<>(targetMap); // must have new object reference if saved back to game state + } + Set targetingObjects = targetMap.computeIfAbsent(event.getTargetId(), k -> new HashSet<>()); for (StackObject stackObject : game.getStack()) { Ability stackAbility = stackObject.getStackAbility(); - if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId())) { + if (stackAbility == null || !stackAbility.getSourceId().equals(event.getSourceId()) || targetingObjects.contains(stackObject.getId())) { continue; } if (CardUtil.getAllSelectedTargets(stackAbility, game).contains(event.getTargetId())) { diff --git a/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java b/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java index c97cf9436f3..accc6b4628c 100644 --- a/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/NumberOfTimesPermanentTargetedATurnWatcher.java @@ -29,7 +29,7 @@ public class NumberOfTimesPermanentTargetedATurnWatcher extends Watcher { if (event.getType() != GameEvent.EventType.TARGETED) { return; } - StackObject targetingObject = CardUtil.getTargetingStackObject(event, game); + StackObject targetingObject = CardUtil.getTargetingStackObject(this.getKey(), event, game); if (targetingObject == null || CardUtil.checkTargetedEventAlreadyUsed(this.getKey(), targetingObject, event, game)) { return; } From 97b53416886d06dad0b2f1e111f763fa6278ef89 Mon Sep 17 00:00:00 2001 From: androosss <101566943+androosss@users.noreply.github.com> Date: Sat, 5 Apr 2025 19:44:16 +0200 Subject: [PATCH 32/59] - Implemented Highspire Bell-Ringer (#13495) - Commonized second spell condition --- .../src/mage/cards/h/HighspireBellRinger.java | 48 +++++++++++++++++++ .../src/mage/cards/r/RagingBattleMouse.java | 14 +----- .../src/mage/sets/TarkirDragonstorm.java | 1 + ...YouCastExactOneSpellThisTurnCondition.java | 19 ++++++++ 4 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/h/HighspireBellRinger.java create mode 100644 Mage/src/main/java/mage/abilities/condition/common/YouCastExactOneSpellThisTurnCondition.java diff --git a/Mage.Sets/src/mage/cards/h/HighspireBellRinger.java b/Mage.Sets/src/mage/cards/h/HighspireBellRinger.java new file mode 100644 index 00000000000..c42a4e7c72c --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HighspireBellRinger.java @@ -0,0 +1,48 @@ +package mage.cards.h; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.YouCastExactOneSpellThisTurnCondition; +import mage.abilities.decorator.ConditionalCostModificationEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author androosss + */ +public final class HighspireBellRinger extends CardImpl { + + public HighspireBellRinger(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.DJINN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(1); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // The second spell you cast each turn costs {1} less to cast. + this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( + new SpellsCostReductionControllerEffect(StaticFilters.FILTER_CARD, 1), + YouCastExactOneSpellThisTurnCondition.instance, "the second spell you cast each turn costs {1} less to cast" + ))); + } + + private HighspireBellRinger(final HighspireBellRinger card) { + super(card); + } + + @Override + public HighspireBellRinger copy() { + return new HighspireBellRinger(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/r/RagingBattleMouse.java b/Mage.Sets/src/mage/cards/r/RagingBattleMouse.java index 3f7ae7785d6..fbd10b2475b 100644 --- a/Mage.Sets/src/mage/cards/r/RagingBattleMouse.java +++ b/Mage.Sets/src/mage/cards/r/RagingBattleMouse.java @@ -4,7 +4,6 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.Condition; import mage.abilities.condition.common.CelebrationCondition; import mage.abilities.decorator.ConditionalCostModificationEffect; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; @@ -14,10 +13,9 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; -import mage.game.Game; import mage.target.common.TargetControlledCreaturePermanent; import mage.watchers.common.PermanentsEnteredBattlefieldWatcher; -import mage.watchers.common.SpellsCastWatcher; +import mage.abilities.condition.common.YouCastExactOneSpellThisTurnCondition; import java.util.UUID; @@ -63,13 +61,3 @@ public final class RagingBattleMouse extends CardImpl { return new RagingBattleMouse(this); } } - -enum YouCastExactOneSpellThisTurnCondition implements Condition { - instance; - - @Override - public boolean apply(Game game, Ability source) { - SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); - return watcher != null && watcher.getSpellsCastThisTurn(source.getControllerId()).size() == 1; - } -} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 1b6d33d1b3c..91bd8ef6d1d 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -104,6 +104,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Gurmag Rakshasa", 81, Rarity.UNCOMMON, mage.cards.g.GurmagRakshasa.class)); cards.add(new SetCardInfo("Hardened Tactician", 191, Rarity.UNCOMMON, mage.cards.h.HardenedTactician.class)); cards.add(new SetCardInfo("Heritage Reclamation", 145, Rarity.COMMON, mage.cards.h.HeritageReclamation.class)); + cards.add(new SetCardInfo("Highspire Bell-Ringer", 47, Rarity.COMMON, mage.cards.h.HighspireBellRinger.class)); cards.add(new SetCardInfo("Humbling Elder", 48, Rarity.COMMON, mage.cards.h.HumblingElder.class)); cards.add(new SetCardInfo("Iceridge Serpent", 49, Rarity.COMMON, mage.cards.i.IceridgeSerpent.class)); cards.add(new SetCardInfo("Inevitable Defeat", 194, Rarity.RARE, mage.cards.i.InevitableDefeat.class)); diff --git a/Mage/src/main/java/mage/abilities/condition/common/YouCastExactOneSpellThisTurnCondition.java b/Mage/src/main/java/mage/abilities/condition/common/YouCastExactOneSpellThisTurnCondition.java new file mode 100644 index 00000000000..98aa5c75dc0 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/YouCastExactOneSpellThisTurnCondition.java @@ -0,0 +1,19 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.game.Game; +import mage.watchers.common.SpellsCastWatcher; + +/** + * @author androosss + */ +public enum YouCastExactOneSpellThisTurnCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + return watcher != null && watcher.getSpellsCastThisTurn(source.getControllerId()).size() == 1; + } +} From fcba64b8c0432af395b96ea04629044cbf4c1c97 Mon Sep 17 00:00:00 2001 From: androosss <101566943+androosss@users.noreply.github.com> Date: Sat, 5 Apr 2025 19:45:58 +0200 Subject: [PATCH 33/59] [TDM] Implement Glacierwood Siege (#13499) * implemented glacierwood siege * GraphicCardInfo fix --- .../src/mage/cards/g/GlacierwoodSiege.java | 66 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 8 ++- 2 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/g/GlacierwoodSiege.java diff --git a/Mage.Sets/src/mage/cards/g/GlacierwoodSiege.java b/Mage.Sets/src/mage/cards/g/GlacierwoodSiege.java new file mode 100644 index 00000000000..693845f7cd6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GlacierwoodSiege.java @@ -0,0 +1,66 @@ +package mage.cards.g; + +import java.util.UUID; + +import mage.abilities.TriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.condition.common.ModeChoiceSourceCondition; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.ChooseModeEffect; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.effects.common.ruleModifying.PlayFromGraveyardControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; +import mage.target.TargetPlayer; + +/** + * + * @author androosss + */ +public final class GlacierwoodSiege extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("an instant or sorcery spell"); + + static { + filter.add(Predicates.or( + CardType.INSTANT.getPredicate(), + CardType.SORCERY.getPredicate())); + } + + public GlacierwoodSiege(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}{U}"); + + // As this enchantment enters, choose Temur or Sultai. + this.addAbility(new EntersBattlefieldAbility(new ChooseModeEffect("Temur or Sultai?", "Temur", "Sultai"), null, + "As {this} enters, choose Temur or Sultai.", "")); + + // * Temur -- Whenever you cast an instant or sorcery spell, target player mills four cards. + TriggeredAbility temurAbility = new SpellCastControllerTriggeredAbility(new MillCardsTargetEffect(4), filter, false); + temurAbility.addTarget(new TargetPlayer()); + this.addAbility(new ConditionalTriggeredAbility( + temurAbility, + new ModeChoiceSourceCondition("Temur"), + "• Temur — Whenever you cast an instant or sorcery spell, target player mills four cards.")); + + // * Sultai -- You may play lands from your graveyard. + this.addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect( + PlayFromGraveyardControllerEffect.playLands(), + new ModeChoiceSourceCondition("Sultai")).setText("• Sultai — You may play lands from your graveyard.") + )); + } + + private GlacierwoodSiege(final GlacierwoodSiege card) { + super(card); + } + + @Override + public GlacierwoodSiege copy() { + return new GlacierwoodSiege(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 91bd8ef6d1d..500d5c49860 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -1,12 +1,12 @@ package mage.sets; +import java.util.Arrays; +import java.util.List; + import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; -import java.util.Arrays; -import java.util.List; - /** * @author TheElk801 */ @@ -99,6 +99,8 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Frontier Bivouac", 256, Rarity.UNCOMMON, mage.cards.f.FrontierBivouac.class)); cards.add(new SetCardInfo("Frontline Rush", 186, Rarity.UNCOMMON, mage.cards.f.FrontlineRush.class)); cards.add(new SetCardInfo("Glacial Dragonhunt", 188, Rarity.UNCOMMON, mage.cards.g.GlacialDragonhunt.class)); + cards.add(new SetCardInfo("Glacierwood Siege", 189, Rarity.RARE, mage.cards.g.GlacierwoodSiege.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Glacierwood Siege", 386, Rarity.RARE, mage.cards.g.GlacierwoodSiege.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Great Arashin City", 257, Rarity.RARE, mage.cards.g.GreatArashinCity.class)); cards.add(new SetCardInfo("Gurmag Nightwatch", 190, Rarity.COMMON, mage.cards.g.GurmagNightwatch.class)); cards.add(new SetCardInfo("Gurmag Rakshasa", 81, Rarity.UNCOMMON, mage.cards.g.GurmagRakshasa.class)); From 5a377017da276fac485b79c8778b727791749242 Mon Sep 17 00:00:00 2001 From: androosss <101566943+androosss@users.noreply.github.com> Date: Sat, 5 Apr 2025 19:46:07 +0200 Subject: [PATCH 34/59] implemented frostcliff siege (#13500) --- .../src/mage/cards/f/FrostcliffSiege.java | 74 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 2 + 2 files changed, 76 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FrostcliffSiege.java diff --git a/Mage.Sets/src/mage/cards/f/FrostcliffSiege.java b/Mage.Sets/src/mage/cards/f/FrostcliffSiege.java new file mode 100644 index 00000000000..61ce64fc2b5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FrostcliffSiege.java @@ -0,0 +1,74 @@ +package mage.cards.f; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.OneOrMoreCombatDamagePlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.ModeChoiceSourceCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.ChooseModeEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.filter.StaticFilters; + +/** + * + * @author androosss + */ +public final class FrostcliffSiege extends CardImpl { + + private static final String rule1Trigger = "• Jeskai — Whenever one or more creatures you control deal combat damage to a player, draw a card."; + + public FrostcliffSiege(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}{R}"); + + + // As this enchantment enters, choose Jeskai or Temur. + this.addAbility(new EntersBattlefieldAbility(new ChooseModeEffect("Jeskai or Temur?", "Jeskai", "Temur"), null, + "As {this} enters, choose Jeskai or Temur.", "")); + + // * Jeskai -- Whenever one or more creatures you control deal combat damage to a player, draw a card. + this.addAbility(new ConditionalTriggeredAbility( + new OneOrMoreCombatDamagePlayerTriggeredAbility(new DrawCardSourceControllerEffect(1)), + new ModeChoiceSourceCondition("Jeskai"), + rule1Trigger)); + + // * Temur -- Creatures you control get +1/+0 and have trample and haste. + Ability temurAbility = new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostControlledEffect(1, 0, Duration.WhileOnBattlefield), + new ModeChoiceSourceCondition("Temur"), + "• Temur — Creatures you control get +1/+0" + )); + temurAbility.addEffect(new ConditionalContinuousEffect( + new GainAbilityControlledEffect(TrampleAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURES), + new ModeChoiceSourceCondition("Temur"), + "and have trample" + )); + temurAbility.addEffect(new ConditionalContinuousEffect( + new GainAbilityControlledEffect(HasteAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURES), + new ModeChoiceSourceCondition("Temur"), + "and haste." + )); + this.addAbility(temurAbility); + } + + private FrostcliffSiege(final FrostcliffSiege card) { + super(card); + } + + @Override + public FrostcliffSiege copy() { + return new FrostcliffSiege(this); + } + +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 500d5c49860..ebb85007cdf 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -98,6 +98,8 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Fresh Start", 46, Rarity.UNCOMMON, mage.cards.f.FreshStart.class)); cards.add(new SetCardInfo("Frontier Bivouac", 256, Rarity.UNCOMMON, mage.cards.f.FrontierBivouac.class)); cards.add(new SetCardInfo("Frontline Rush", 186, Rarity.UNCOMMON, mage.cards.f.FrontlineRush.class)); + cards.add(new SetCardInfo("Frostcliff Siege", 187, Rarity.RARE, mage.cards.f.FrostcliffSiege.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Frostcliff Siege", 385, Rarity.RARE, mage.cards.f.FrostcliffSiege.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Glacial Dragonhunt", 188, Rarity.UNCOMMON, mage.cards.g.GlacialDragonhunt.class)); cards.add(new SetCardInfo("Glacierwood Siege", 189, Rarity.RARE, mage.cards.g.GlacierwoodSiege.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Glacierwood Siege", 386, Rarity.RARE, mage.cards.g.GlacierwoodSiege.class, NON_FULL_USE_VARIOUS)); From 508c5348845c6fdf66068b81af7e818a3abc92db Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 5 Apr 2025 13:57:40 -0400 Subject: [PATCH 35/59] [TDM] Implement Temur Battlecrier --- .../src/mage/cards/t/TemurBattlecrier.java | 104 ++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 7 +- 2 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/t/TemurBattlecrier.java diff --git a/Mage.Sets/src/mage/cards/t/TemurBattlecrier.java b/Mage.Sets/src/mage/cards/t/TemurBattlecrier.java new file mode 100644 index 00000000000..03c8765bbcf --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TemurBattlecrier.java @@ -0,0 +1,104 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TemurBattlecrier extends CardImpl { + + public TemurBattlecrier(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}{R}"); + + this.subtype.add(SubType.ORC); + this.subtype.add(SubType.RANGER); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // During your turn, spells you cast cost {1} less to cast for each creature you control with power 4 or greater. + this.addAbility(new SimpleStaticAbility(new TemurBattlecrierEffect()).addHint(TemurBattlecrierEffect.getHint())); + } + + private TemurBattlecrier(final TemurBattlecrier card) { + super(card); + } + + @Override + public TemurBattlecrier copy() { + return new TemurBattlecrier(this); + } +} + +class TemurBattlecrierEffect extends CostModificationEffectImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, 3)); + } + + private static final Hint hint = new ValueHint( + "Creatures you control with power 4 or greater", new PermanentsOnBattlefieldCount(filter) + ); + + public static Hint getHint() { + return hint; + } + + TemurBattlecrierEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); + staticText = "during your turn, spells you cast cost {1} less to cast " + + "for each creature you control with power 4 or greater"; + } + + private TemurBattlecrierEffect(final TemurBattlecrierEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + Ability spellAbility = abilityToModify; + if (spellAbility == null) { + return false; + } + int amount = game.getBattlefield().count(filter, source.getControllerId(), source, game); + if (amount > 0) { + CardUtil.reduceCost(spellAbility, amount); + } + return true; + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + if (!(abilityToModify instanceof SpellAbility) + || !abilityToModify.isControlledBy(source.getControllerId()) + || !game.isActivePlayer(source.getControllerId())) { + return false; + } + Card spellCard = ((SpellAbility) abilityToModify).getCharacteristics(game); + return spellCard != null; + } + + @Override + public TemurBattlecrierEffect copy() { + return new TemurBattlecrierEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index ebb85007cdf..b5fe3c80cc2 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -1,12 +1,12 @@ package mage.sets; -import java.util.Arrays; -import java.util.List; - import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; +import java.util.Arrays; +import java.util.List; + /** * @author TheElk801 */ @@ -209,6 +209,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Swiftwater Cliffs", 268, Rarity.COMMON, mage.cards.s.SwiftwaterCliffs.class)); cards.add(new SetCardInfo("Teeming Dragonstorm", 30, Rarity.UNCOMMON, mage.cards.t.TeemingDragonstorm.class)); cards.add(new SetCardInfo("Tempest Hawk", 31, Rarity.COMMON, mage.cards.t.TempestHawk.class)); + cards.add(new SetCardInfo("Temur Battlecrier", 228, Rarity.RARE, mage.cards.t.TemurBattlecrier.class)); cards.add(new SetCardInfo("Temur Devotee", 61, Rarity.COMMON, mage.cards.t.TemurDevotee.class)); cards.add(new SetCardInfo("Temur Monument", 248, Rarity.UNCOMMON, mage.cards.t.TemurMonument.class)); cards.add(new SetCardInfo("Temur Tawnyback", 229, Rarity.COMMON, mage.cards.t.TemurTawnyback.class)); From eac265f4f7ca8682d994d84dfc1a001ee055ec03 Mon Sep 17 00:00:00 2001 From: padfoothelix Date: Sat, 5 Apr 2025 20:10:45 +0200 Subject: [PATCH 36/59] [WHO] Implement Fugitive of the Judoon (#13467) --- .../sources/ScryfallImageSupportTokens.java | 4 +- .../src/mage/cards/f/FugitiveOfTheJudoon.java | 82 +++++++++++++++++++ Mage.Sets/src/mage/sets/DoctorWho.java | 4 +- .../game/permanent/token/AlienRhinoToken.java | 34 ++++++++ .../token/Human11WithWard2Token.java | 33 ++++++++ Mage/src/main/resources/tokens-database.txt | 4 +- 6 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/f/FugitiveOfTheJudoon.java create mode 100644 Mage/src/main/java/mage/game/permanent/token/AlienRhinoToken.java create mode 100644 Mage/src/main/java/mage/game/permanent/token/Human11WithWard2Token.java diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java index 9441b6aa677..a6479a6c40a 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java @@ -2183,6 +2183,7 @@ public class ScryfallImageSupportTokens { // WHO put("WHO/Alien", "https://api.scryfall.com/cards/twho/2?format=image"); put("WHO/Alien Insect", "https://api.scryfall.com/cards/twho/19/en?format=image"); + put("WHO/Alien Rhino", "https://api.scryfall.com/cards/twho/3/en?format=image"); put("WHO/Alien Salamander", "https://api.scryfall.com/cards/twho/16?format=image"); put("WHO/Alien Warrior", "https://api.scryfall.com/cards/twho/14?format=image"); put("WHO/Beast", "https://api.scryfall.com/cards/twho/17?format=image"); @@ -2196,7 +2197,8 @@ public class ScryfallImageSupportTokens { put("WHO/Food/2", "https://api.scryfall.com/cards/twho/26?format=image"); put("WHO/Food/3", "https://api.scryfall.com/cards/twho/27?format=image"); put("WHO/Horse", "https://api.scryfall.com/cards/twho/4/en?format=image"); - put("WHO/Human", "https://api.scryfall.com/cards/twho/5?format=image"); + put("WHO/Human/1", "https://api.scryfall.com/cards/twho/6/en?format=image"); + put("WHO/Human/2", "https://api.scryfall.com/cards/twho/5/en?format=image"); put("WHO/Human Noble", "https://api.scryfall.com/cards/twho/7/en?format=image"); put("WHO/Mark of the Rani", "https://api.scryfall.com/cards/twho/15?format=image"); put("WHO/Soldier", "https://api.scryfall.com/cards/twho/8?format=image"); diff --git a/Mage.Sets/src/mage/cards/f/FugitiveOfTheJudoon.java b/Mage.Sets/src/mage/cards/f/FugitiveOfTheJudoon.java new file mode 100644 index 00000000000..05e2b69c45a --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FugitiveOfTheJudoon.java @@ -0,0 +1,82 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.abilities.common.SagaAbility; +import mage.abilities.costs.CompositeCost; +import mage.abilities.costs.common.ExileTargetCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SagaChapter; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledArtifactPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.AlienRhinoToken; +import mage.game.permanent.token.Human11WithWard2Token; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetControlledPermanent; + + +/** + * + * @author padfoothelix + */ +public final class FugitiveOfTheJudoon extends CardImpl { + + private static final FilterCard filter = new FilterCard("a Doctor card"); + private static final FilterControlledPermanent filterHuman = new FilterControlledPermanent(SubType.HUMAN,"a Human you control"); + private static final FilterControlledArtifactPermanent filterArtifact = new FilterControlledArtifactPermanent("an artifact you control"); + + static { + filter.add(SubType.DOCTOR.getPredicate()); + } + + public FugitiveOfTheJudoon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Create a 1/1 white Human creature token with ward {2} and a 4/4 white Alien Rhino creature token. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new CreateTokenEffect(new Human11WithWard2Token()).withAdditionalTokens(new AlienRhinoToken()) + ); + + // II -- Investigate. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, new InvestigateEffect()); + + // III -- You may exile a Human you control and an artifact you control. If you do, search your library for a Doctor card, put it onto the battlefield, then shuffle. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_III, + new DoIfCostPaid( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter)), + new CompositeCost( + new ExileTargetCost(new TargetControlledPermanent(1, 1, filterHuman, true)), + new ExileTargetCost(new TargetControlledPermanent(1, 1, filterArtifact, true)), + "exile a Human you control and an artifact you control" + ), + "Exile a Human and an artifact ?" + ) + ); + + this.addAbility(sagaAbility); + + } + + private FugitiveOfTheJudoon(final FugitiveOfTheJudoon card) { + super(card); + } + + @Override + public FugitiveOfTheJudoon copy() { + return new FugitiveOfTheJudoon(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DoctorWho.java b/Mage.Sets/src/mage/sets/DoctorWho.java index e03ac42718d..61e361fa3a2 100644 --- a/Mage.Sets/src/mage/sets/DoctorWho.java +++ b/Mage.Sets/src/mage/sets/DoctorWho.java @@ -376,8 +376,8 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Frostboil Snarl", 282, Rarity.RARE, mage.cards.f.FrostboilSnarl.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Frostboil Snarl", 498, Rarity.RARE, mage.cards.f.FrostboilSnarl.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Frostboil Snarl", 873, Rarity.RARE, mage.cards.f.FrostboilSnarl.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Fugitive of the Judoon", 103, Rarity.RARE, mage.cards.f.FugitiveOfTheJudoon.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Fugitive of the Judoon", 708, Rarity.RARE, mage.cards.f.FugitiveOfTheJudoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fugitive of the Judoon", 103, Rarity.RARE, mage.cards.f.FugitiveOfTheJudoon.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Fugitive of the Judoon", 708, Rarity.RARE, mage.cards.f.FugitiveOfTheJudoon.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Furycalm Snarl", 1090, Rarity.RARE, mage.cards.f.FurycalmSnarl.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Furycalm Snarl", 283, Rarity.RARE, mage.cards.f.FurycalmSnarl.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Furycalm Snarl", 499, Rarity.RARE, mage.cards.f.FurycalmSnarl.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage/src/main/java/mage/game/permanent/token/AlienRhinoToken.java b/Mage/src/main/java/mage/game/permanent/token/AlienRhinoToken.java new file mode 100644 index 00000000000..43b6fbd5da4 --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/AlienRhinoToken.java @@ -0,0 +1,34 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.PreventDamageToSourceEffect; +import mage.abilities.keyword.VanishingAbility; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; + +/** + * @author padfoothelix + */ +public final class AlienRhinoToken extends TokenImpl { + + public AlienRhinoToken() { + super("Alien Rhino Token", "4/4 white Alien Rhino creature token"); + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.ALIEN); + subtype.add(SubType.RHINO); + power = new MageInt(4); + toughness = new MageInt(4); + } + + private AlienRhinoToken(final AlienRhinoToken token) { + super(token); + } + + @Override + public AlienRhinoToken copy() { + return new AlienRhinoToken(this); + } +} diff --git a/Mage/src/main/java/mage/game/permanent/token/Human11WithWard2Token.java b/Mage/src/main/java/mage/game/permanent/token/Human11WithWard2Token.java new file mode 100644 index 00000000000..a39848fcb5c --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/Human11WithWard2Token.java @@ -0,0 +1,33 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.keyword.WardAbility; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; + +/** + * @author padfoothelix + */ +public final class Human11WithWard2Token extends TokenImpl { + + public Human11WithWard2Token() { + super("Human Token", "1/1 white Human creature token with ward {2}"); + cardType.add(CardType.CREATURE); + color.setWhite(true); + subtype.add(SubType.HUMAN); + power = new MageInt(1); + toughness = new MageInt(1); + this.addAbility(new WardAbility(new GenericManaCost(2))); + } + + private Human11WithWard2Token(final Human11WithWard2Token token) { + super(token); + } + + @Override + public Human11WithWard2Token copy() { + return new Human11WithWard2Token(this); + } +} diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index 0b77e47469c..8dc1ecf4e63 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -2235,6 +2235,7 @@ # WHO |Generate|TOK:WHO|Alien|||AlienToken| |Generate|TOK:WHO|Alien Insect|||AlienInsectToken| +|Generate|TOK:WHO|Alien Rhino|||AlienRhinoToken| |Generate|TOK:WHO|Alien Salamander|||AlienSalamanderToken| |Generate|TOK:WHO|Alien Warrior|||AlienWarriorToken| |Generate|TOK:WHO|Beast|||BeastToken| @@ -2248,7 +2249,8 @@ |Generate|TOK:WHO|Food|2||FoodToken| |Generate|TOK:WHO|Food|3||FoodToken| |Generate|TOK:WHO|Horse|||TheGirlInTheFireplaceHorseToken| -|Generate|TOK:WHO|Human|||TheEleventhHourToken| +|Generate|TOK:WHO|Human|1||Human11WithWard2Token| +|Generate|TOK:WHO|Human|2||TheEleventhHourToken| |Generate|TOK:WHO|Human Noble|||TheGirlInTheFireplaceHumanNobleToken| |Generate|TOK:WHO|Mark of the Rani|||MarkOfTheRaniToken| |Generate|TOK:WHO|Soldier|||SoldierToken| From b6667d7e458287d84b59811d6d701b1d9b5ac7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Krist=C3=B3f?= <20043803+balazskristof@users.noreply.github.com> Date: Sat, 5 Apr 2025 20:11:02 +0200 Subject: [PATCH 37/59] [ACR] Implement Layla Hassan (#13471) --- Mage.Sets/src/mage/cards/l/LaylaHassan.java | 103 ++++++++++++++++++++ Mage.Sets/src/mage/sets/AssassinsCreed.java | 6 +- 2 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/l/LaylaHassan.java diff --git a/Mage.Sets/src/mage/cards/l/LaylaHassan.java b/Mage.Sets/src/mage/cards/l/LaylaHassan.java new file mode 100644 index 00000000000..b3eaf7c2a2e --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LaylaHassan.java @@ -0,0 +1,103 @@ +package mage.cards.l; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.common.FilterHistoricCard; +import mage.game.Game; +import mage.game.events.DamagedBatchForOnePlayerEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.OneOrMoreDamagePlayerTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * @author balazskristof + */ +public final class LaylaHassan extends CardImpl { + + private static final FilterHistoricCard filter = new FilterHistoricCard("historic card from your graveyard"); + + public LaylaHassan(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.ASSASSIN); + this.power = new MageInt(3); + this.toughness = new MageInt(4); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // When Layla Hassan enters the battlefield and whenever one or more Assassins you control deal combat damage to a player, return target historic card from your graveyard to your hand. + Ability ability = new LaylaHassanTriggeredAbility(new ReturnFromGraveyardToHandTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + } + + private LaylaHassan(final LaylaHassan card) { + super(card); + } + + @Override + public LaylaHassan copy() { + return new LaylaHassan(this); + } +} + +class LaylaHassanTriggeredAbility extends OneOrMoreDamagePlayerTriggeredAbility { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Assassins"); + + static { + filter.add(SubType.ASSASSIN.getPredicate()); + } + + public LaylaHassanTriggeredAbility(Effect effect) { + super(effect, filter, true, true); + } + + private LaylaHassanTriggeredAbility(final LaylaHassanTriggeredAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD + || event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD + && event.getTargetId().equals(getSourceId())) { + return true; + } + if (event.getType() == GameEvent.EventType.DAMAGED_BATCH_FOR_ONE_PLAYER) { + return super.checkTrigger(event, game); + } + return false; + } + + @Override + public String getRule() { + return "When {this} enters the battlefield and whenever one or more Assassins you control deal combat damage to a player, return target historic card from your graveyard to your hand."; + } + + @Override + public LaylaHassanTriggeredAbility copy() { + return new LaylaHassanTriggeredAbility(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AssassinsCreed.java b/Mage.Sets/src/mage/sets/AssassinsCreed.java index 63b30bf8a39..7fb2fcf3838 100644 --- a/Mage.Sets/src/mage/sets/AssassinsCreed.java +++ b/Mage.Sets/src/mage/sets/AssassinsCreed.java @@ -189,9 +189,9 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Kassandra, Eagle Bearer", 59, Rarity.MYTHIC, mage.cards.k.KassandraEagleBearer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Keen-Eyed Raven", 279, Rarity.UNCOMMON, mage.cards.k.KeenEyedRaven.class)); cards.add(new SetCardInfo("Labyrinth Adversary", 290, Rarity.UNCOMMON, mage.cards.l.LabyrinthAdversary.class)); - //cards.add(new SetCardInfo("Layla Hassan", 127, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Layla Hassan", 177, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); - //cards.add(new SetCardInfo("Layla Hassan", 7, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Layla Hassan", 127, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Layla Hassan", 177, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Layla Hassan", 7, Rarity.RARE, mage.cards.l.LaylaHassan.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Leonardo da Vinci", "118z", Rarity.MYTHIC, mage.cards.l.LeonardoDaVinci.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Leonardo da Vinci", 118, Rarity.MYTHIC, mage.cards.l.LeonardoDaVinci.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Leonardo da Vinci", 193, Rarity.MYTHIC, mage.cards.l.LeonardoDaVinci.class, NON_FULL_USE_VARIOUS)); From cdebbe151bdc8266fc953e752574bd5dfcbd360e Mon Sep 17 00:00:00 2001 From: ssk97 Date: Sat, 5 Apr 2025 11:11:55 -0700 Subject: [PATCH 38/59] Fix for disconnect causing instant loss (#13390) --- Mage.Server/src/main/java/mage/server/Main.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Server/src/main/java/mage/server/Main.java b/Mage.Server/src/main/java/mage/server/Main.java index 9503aacb383..8a6e06ac66b 100644 --- a/Mage.Server/src/main/java/mage/server/Main.java +++ b/Mage.Server/src/main/java/mage/server/Main.java @@ -370,7 +370,7 @@ public final class Main { // no need to keep session logger.info("CLIENT DISCONNECTED - " + sessionInfo); logger.debug("- cause: client called disconnect command"); - managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.DisconnectedByUser, true); + managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.LostConnection, true); } else if (throwable == null) { // lease timeout (ping), so server lost connection with a client // must keep tables From 6f524b69c012352710e8c3a4e250f03b56a82966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Krist=C3=B3f?= <20043803+balazskristof@users.noreply.github.com> Date: Sat, 5 Apr 2025 20:13:23 +0200 Subject: [PATCH 39/59] [FIN] Implement Stiltzkin, Moogle Merchant (#13443) --- .../mage/cards/s/StiltzkinMoogleMerchant.java | 95 +++++++++++++++++++ Mage.Sets/src/mage/sets/FinalFantasy.java | 2 + .../src/main/java/mage/constants/SubType.java | 1 + 3 files changed, 98 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/StiltzkinMoogleMerchant.java diff --git a/Mage.Sets/src/mage/cards/s/StiltzkinMoogleMerchant.java b/Mage.Sets/src/mage/cards/s/StiltzkinMoogleMerchant.java new file mode 100644 index 00000000000..c361ffaa710 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StiltzkinMoogleMerchant.java @@ -0,0 +1,95 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTarget; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; + +/** + * @author balazskristof + */ +public final class StiltzkinMoogleMerchant extends CardImpl { + + public StiltzkinMoogleMerchant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.MOOGLE); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // {2}, {T}: Target opponent gains control of another target permanent you control. If they do, you draw a card. + Ability ability = new SimpleActivatedAbility(new StiltzkinMoogleMerchantEffect(), new GenericManaCost(2)); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetOpponent()); + ability.addTarget(new TargetControlledPermanent(StaticFilters.FILTER_CONTROLLED_ANOTHER_PERMANENT)); + this.addAbility(ability); + } + + private StiltzkinMoogleMerchant(final StiltzkinMoogleMerchant card) { + super(card); + } + + @Override + public StiltzkinMoogleMerchant copy() { + return new StiltzkinMoogleMerchant(this); + } +} + +class StiltzkinMoogleMerchantEffect extends OneShotEffect { + + StiltzkinMoogleMerchantEffect() { + super(Outcome.Benefit); + staticText = "Target opponent gains control of another target permanent you control. If they do, you draw a card."; + } + + private StiltzkinMoogleMerchantEffect(StiltzkinMoogleMerchantEffect effect) { + super(effect); + } + + @Override + public StiltzkinMoogleMerchantEffect copy() { + return new StiltzkinMoogleMerchantEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); + if (permanent == null) { + return false; + } + UUID opponent = getTargetPointer().getFirst(game, source); + game.addEffect(new GainControlTargetEffect( + Duration.Custom, true, opponent + ).setTargetPointer(new FixedTarget(permanent.getId(), game)), source); + game.processAction(); + if (permanent.isControlledBy(opponent)) { + new DrawCardSourceControllerEffect(1).apply(game, source); + return true; + } + return false; + } +} + diff --git a/Mage.Sets/src/mage/sets/FinalFantasy.java b/Mage.Sets/src/mage/sets/FinalFantasy.java index bde77f989dc..463445ffd72 100644 --- a/Mage.Sets/src/mage/sets/FinalFantasy.java +++ b/Mage.Sets/src/mage/sets/FinalFantasy.java @@ -26,6 +26,8 @@ public final class FinalFantasy extends ExpansionSet { cards.add(new SetCardInfo("Sin, Spira's Punishment", 242, Rarity.RARE, mage.cards.s.SinSpirasPunishment.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sin, Spira's Punishment", 348, Rarity.RARE, mage.cards.s.SinSpirasPunishment.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sin, Spira's Punishment", 508, Rarity.RARE, mage.cards.s.SinSpirasPunishment.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stiltzkin, Moogle Merchant", 34, Rarity.RARE, mage.cards.s.StiltzkinMoogleMerchant.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Stiltzkin, Moogle Merchant", 327, Rarity.RARE, mage.cards.s.StiltzkinMoogleMerchant.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Summon: Shiva", 78, Rarity.UNCOMMON, mage.cards.s.SummonShiva.class)); cards.add(new SetCardInfo("Tonberry", 122, Rarity.UNCOMMON, mage.cards.t.Tonberry.class)); } diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 5037f60d44a..0fcfafaa0c4 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -274,6 +274,7 @@ public enum SubType { MONGOOSE("Mongoose", SubTypeSet.CreatureType), MONK("Monk", SubTypeSet.CreatureType), MONKEY("Monkey", SubTypeSet.CreatureType), + MOOGLE("Moogle", SubTypeSet.CreatureType), MOONFOLK("Moonfolk", SubTypeSet.CreatureType), MOUNT("Mount", SubTypeSet.CreatureType), MOUSE("Mouse", SubTypeSet.CreatureType), From 6fcb592557c784376d75624d582be96f07b0c056 Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Sat, 5 Apr 2025 15:47:31 -0400 Subject: [PATCH 40/59] [TDM] Implement Sidisi, Regent of the Mire --- .../mage/cards/s/SidisiRegentOfTheMire.java | 130 ++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 131 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java diff --git a/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java b/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java new file mode 100644 index 00000000000..ac37da8f169 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java @@ -0,0 +1,130 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.VariableCostImpl; +import mage.abilities.costs.VariableCostType; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetadjustment.ManaValueTargetAdjuster; +import mage.util.CardUtil; + +/** + * + * @author Grath + */ +public final class SidisiRegentOfTheMire extends CardImpl { + + public SidisiRegentOfTheMire(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ZOMBIE); + this.subtype.add(SubType.SNAKE); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // {T}, Sacrifice a creature you control with mana value X other than Sidisi: Return target creature card with mana value X plus 1 from your graveyard to the battlefield. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect(), new TapSourceCost() + ); + ability.addCost(new SidisiRegentOfTheMireCost()); + ability.addTarget(new TargetCardInYourGraveyard(new FilterCreatureCard("creature card with mana value X plus 1 from your graveyard"))); + ability.setTargetAdjuster(new SidisiRegentOfTheMireAdjuster()); + this.addAbility(ability); + } + + private SidisiRegentOfTheMire(final SidisiRegentOfTheMire card) { + super(card); + } + + @Override + public SidisiRegentOfTheMire copy() { + return new SidisiRegentOfTheMire(this); + } +} + +enum GetXPlusOneValue implements DynamicValue { + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return CardUtil.getSourceCostsTag(game, sourceAbility, "X", 0) + 1; + } + + @Override + public GetXPlusOneValue copy() { + return GetXPlusOneValue.instance; + } + + @Override + public String toString() { + return "X + 1"; + } + + @Override + public String getMessage() { + return ""; + } +} + +class SidisiRegentOfTheMireAdjuster extends ManaValueTargetAdjuster { + + public SidisiRegentOfTheMireAdjuster() { + super(GetXPlusOneValue.instance, ComparisonType.EQUAL_TO); + } + +} + +class SidisiRegentOfTheMireCost extends VariableCostImpl { + + public SidisiRegentOfTheMireCost() { + super(VariableCostType.NORMAL, "mana value X"); + this.text = "Sacrifice a creature with mana value X"; + } + + protected SidisiRegentOfTheMireCost(final SidisiRegentOfTheMireCost cost) { + super(cost); + } + + @Override + public SidisiRegentOfTheMireCost copy() { + return new SidisiRegentOfTheMireCost(this); + } + + @Override + public Cost getFixedCostsFromAnnouncedValue(int xValue) { + FilterPermanent filter = new FilterControlledCreaturePermanent("another creature with mana value X"); + filter.add(AnotherPredicate.instance); + filter.add(new ManaValuePredicate(ComparisonType.EQUAL_TO, xValue)); + return new SacrificeTargetCost(filter); + } + + @Override + public int getMaxValue(Ability source, Game game) { + return game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE, + source.getControllerId(), source, game + ).stream().mapToInt(MageObject::getManaValue).max().orElse(0); + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index b5fe3c80cc2..878a2a6a5f4 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -187,6 +187,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Shock Brigade", 120, Rarity.COMMON, mage.cards.s.ShockBrigade.class)); cards.add(new SetCardInfo("Shocking Sharpshooter", 121, Rarity.UNCOMMON, mage.cards.s.ShockingSharpshooter.class)); cards.add(new SetCardInfo("Sibsig Appraiser", 56, Rarity.COMMON, mage.cards.s.SibsigAppraiser.class)); + cards.add(new SetCardInfo("Sidisi, Regent of the Mire", 92, Rarity.RARE, mage.cards.s.SidisiRegentOfTheMire.class)); cards.add(new SetCardInfo("Sinkhole Surveyor", 93, Rarity.RARE, mage.cards.s.SinkholeSurveyor.class)); cards.add(new SetCardInfo("Skirmish Rhino", 224, Rarity.UNCOMMON, mage.cards.s.SkirmishRhino.class)); cards.add(new SetCardInfo("Smile at Death", 24, Rarity.MYTHIC, mage.cards.s.SmileAtDeath.class)); From 95d5d373ce5d6e61fc5fb130dc49575766d478c9 Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Sat, 5 Apr 2025 18:47:00 -0400 Subject: [PATCH 41/59] Remove unnecessary custom Dynamic Value from SidisiRegentOfTheMire --- .../mage/cards/s/SidisiRegentOfTheMire.java | 32 +++---------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java b/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java index ac37da8f169..ac7656de699 100644 --- a/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java +++ b/Mage.Sets/src/mage/cards/s/SidisiRegentOfTheMire.java @@ -10,8 +10,9 @@ import mage.abilities.costs.VariableCostImpl; import mage.abilities.costs.VariableCostType; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.effects.Effect; +import mage.abilities.dynamicvalue.AdditiveDynamicValue; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.constants.*; import mage.cards.CardImpl; @@ -25,7 +26,6 @@ import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.game.Game; import mage.target.common.TargetCardInYourGraveyard; import mage.target.targetadjustment.ManaValueTargetAdjuster; -import mage.util.CardUtil; /** * @@ -63,34 +63,10 @@ public final class SidisiRegentOfTheMire extends CardImpl { } } -enum GetXPlusOneValue implements DynamicValue { - instance; - - @Override - public int calculate(Game game, Ability sourceAbility, Effect effect) { - return CardUtil.getSourceCostsTag(game, sourceAbility, "X", 0) + 1; - } - - @Override - public GetXPlusOneValue copy() { - return GetXPlusOneValue.instance; - } - - @Override - public String toString() { - return "X + 1"; - } - - @Override - public String getMessage() { - return ""; - } -} - class SidisiRegentOfTheMireAdjuster extends ManaValueTargetAdjuster { public SidisiRegentOfTheMireAdjuster() { - super(GetXPlusOneValue.instance, ComparisonType.EQUAL_TO); + super(new AdditiveDynamicValue(GetXValue.instance, StaticValue.get(1)), ComparisonType.EQUAL_TO); } } From 0ad8e8e181b03d95cc8e095782dc2307fbee1bff Mon Sep 17 00:00:00 2001 From: Mike Cunningham Date: Sun, 6 Apr 2025 01:14:36 -0400 Subject: [PATCH 42/59] Aggressive Negotiations implementation --- .../mage/cards/a/AggressiveNegotiations.java | 42 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 43 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java diff --git a/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java b/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java new file mode 100644 index 00000000000..92156e27a55 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java @@ -0,0 +1,42 @@ +package mage.cards.a; + +import java.util.UUID; +import mage.abilities.effects.common.ExileCardYouChooseTargetOpponentEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.TargetPlayer; +import mage.target.common.TargetCreaturePermanent; + +/** + * Aggressive Negotiations implementation + * Author: @mikejcunn + */ +public final class AggressiveNegotiations extends CardImpl { + + public AggressiveNegotiations(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // Target opponent reveals their hand. You choose a nonland card from it. That player exiles that card. + this.getSpellAbility().addTarget(new TargetPlayer()); + this.getSpellAbility().addEffect(new ExileCardYouChooseTargetOpponentEffect(StaticFilters.FILTER_CARD_NON_LAND)); + + // Put a +1/+1 counter on target creature you control. + TargetCreaturePermanent targetCreature = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(targetCreature); + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(1), Outcome.BoostCreature)); + } + + private AggressiveNegotiations(final AggressiveNegotiations card) { + super(card); + } + + @Override + public AggressiveNegotiations copy() { + return new AggressiveNegotiations(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 878a2a6a5f4..a1a5e1d34e7 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -29,6 +29,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Adorned Crocodile", 69, Rarity.COMMON, mage.cards.a.AdornedCrocodile.class)); cards.add(new SetCardInfo("Aegis Sculptor", 35, Rarity.UNCOMMON, mage.cards.a.AegisSculptor.class)); cards.add(new SetCardInfo("Agent of Kotis", 36, Rarity.COMMON, mage.cards.a.AgentOfKotis.class)); + cards.add(new SetCardInfo("Aggressive Negotiations", 70, Rarity.COMMON, mage.cards.a.AggressiveNegotiations.class)); cards.add(new SetCardInfo("Ainok Wayfarer", 134, Rarity.COMMON, mage.cards.a.AinokWayfarer.class)); cards.add(new SetCardInfo("Alchemist's Assistant", 71, Rarity.UNCOMMON, mage.cards.a.AlchemistsAssistant.class)); cards.add(new SetCardInfo("Alesha's Legacy", 72, Rarity.COMMON, mage.cards.a.AleshasLegacy.class)); From a186a6f21e9c3024cd51f430691fb92974fb030e Mon Sep 17 00:00:00 2001 From: Mike Cunningham Date: Sun, 6 Apr 2025 09:13:36 -0400 Subject: [PATCH 43/59] Reusing Essence Capture +1/+1 counter effect --- .../src/mage/cards/a/AggressiveNegotiations.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java b/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java index 92156e27a55..24303f4c694 100644 --- a/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java +++ b/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java @@ -1,6 +1,8 @@ package mage.cards.a; import java.util.UUID; + +import mage.abilities.effects.Effect; import mage.abilities.effects.common.ExileCardYouChooseTargetOpponentEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.CardImpl; @@ -10,7 +12,8 @@ import mage.constants.Outcome; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.target.TargetPlayer; -import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; /** * Aggressive Negotiations implementation @@ -22,13 +25,16 @@ public final class AggressiveNegotiations extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); // Target opponent reveals their hand. You choose a nonland card from it. That player exiles that card. + Effect effect1 = new ExileCardYouChooseTargetOpponentEffect(StaticFilters.FILTER_CARD_NON_LAND); + effect1.setText("Target player reveals their hand. You choose a nonland card from it and exile that card."); + this.getSpellAbility().addEffect(effect1); this.getSpellAbility().addTarget(new TargetPlayer()); - this.getSpellAbility().addEffect(new ExileCardYouChooseTargetOpponentEffect(StaticFilters.FILTER_CARD_NON_LAND)); // Put a +1/+1 counter on target creature you control. - TargetCreaturePermanent targetCreature = new TargetCreaturePermanent(); - this.getSpellAbility().addTarget(targetCreature); - this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(1), Outcome.BoostCreature)); + this.getSpellAbility().addEffect(new AddCountersTargetEffect( + CounterType.P1P1.createInstance() + ).setTargetPointer(new SecondTargetPointer())); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent(0, 1)); } private AggressiveNegotiations(final AggressiveNegotiations card) { From fd1e8144275b487122e3aeb15659557d115ffb84 Mon Sep 17 00:00:00 2001 From: jmlundeen Date: Sun, 6 Apr 2025 15:27:06 -0500 Subject: [PATCH 44/59] Update Ureni of the Unwritten replace custom effect with LookLibraryAndPickControllerEffect --- .../src/mage/cards/u/UreniOfTheUnwritten.java | 58 ++++--------------- 1 file changed, 11 insertions(+), 47 deletions(-) diff --git a/Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java b/Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java index fa56c051e20..0c00e9b48b5 100644 --- a/Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java +++ b/Mage.Sets/src/mage/cards/u/UreniOfTheUnwritten.java @@ -1,24 +1,18 @@ package mage.cards.u; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.LookLibraryAndPickControllerEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; import mage.constants.CardType; -import mage.constants.Outcome; +import mage.constants.PutCards; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.Zone; import mage.filter.common.FilterCreatureCard; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetCard; import java.util.UUID; @@ -27,6 +21,12 @@ import java.util.UUID; */ public final class UreniOfTheUnwritten extends CardImpl { + static final FilterCreatureCard filter = new FilterCreatureCard("Dragon creature card"); + + static { + filter.add(SubType.DRAGON.getPredicate()); + } + public UreniOfTheUnwritten(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}{U}{R}"); @@ -43,7 +43,8 @@ public final class UreniOfTheUnwritten extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Whenever Ureni enters or attacks, look at the top eight cards of your library. You may put a Dragon creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order. - this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new UreniOfTheUnwrittenEffect(), false)); + Effect effect = new LookLibraryAndPickControllerEffect(8, 1, filter, PutCards.BATTLEFIELD, PutCards.BOTTOM_RANDOM); + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(effect, false)); } private UreniOfTheUnwritten(final UreniOfTheUnwritten card) { @@ -54,41 +55,4 @@ public final class UreniOfTheUnwritten extends CardImpl { public UreniOfTheUnwritten copy() { return new UreniOfTheUnwritten(this); } -} - -class UreniOfTheUnwrittenEffect extends OneShotEffect { - - UreniOfTheUnwrittenEffect() { - super(Outcome.Benefit); - this.staticText = "look at the top eight cards of your library. You may put a Dragon creature card from among them onto the battlefield. Put the rest on the bottom of your library in a random order."; - } - - private UreniOfTheUnwrittenEffect(final mage.cards.u.UreniOfTheUnwrittenEffect effect) { - super(effect); - } - - @Override - public mage.cards.u.UreniOfTheUnwrittenEffect copy() { - return new mage.cards.u.UreniOfTheUnwrittenEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, 8)); - if (!cards.isEmpty()) { - FilterCreatureCard filter = new FilterCreatureCard("Dragon creature cards"); - filter.add(SubType.DRAGON.getPredicate()); - TargetCard targetCard = new TargetCard(0, 1, Zone.LIBRARY, filter); - targetCard.withNotTarget(true); - controller.choose(Outcome.PutCreatureInPlay, cards, targetCard, source, game); - controller.moveCards(game.getCard(targetCard.getFirstTarget()), Zone.BATTLEFIELD, source, game); - cards.retainZone(Zone.LIBRARY, game); - controller.putCardsOnBottomOfLibrary(cards, game, source, false); - } - return true; - } } \ No newline at end of file From 39a4e7644e1a41ff0cf6dd000fbb71e87adf1077 Mon Sep 17 00:00:00 2001 From: Mike Cunningham Date: Mon, 7 Apr 2025 00:28:08 -0400 Subject: [PATCH 45/59] Removing setText, updating filter --- Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java b/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java index 24303f4c694..d4563bdb910 100644 --- a/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java +++ b/Mage.Sets/src/mage/cards/a/AggressiveNegotiations.java @@ -25,8 +25,7 @@ public final class AggressiveNegotiations extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); // Target opponent reveals their hand. You choose a nonland card from it. That player exiles that card. - Effect effect1 = new ExileCardYouChooseTargetOpponentEffect(StaticFilters.FILTER_CARD_NON_LAND); - effect1.setText("Target player reveals their hand. You choose a nonland card from it and exile that card."); + Effect effect1 = new ExileCardYouChooseTargetOpponentEffect(StaticFilters.FILTER_CARD_A_NON_LAND); this.getSpellAbility().addEffect(effect1); this.getSpellAbility().addTarget(new TargetPlayer()); From b84143bfca966be76b2383fad63d747da4eebb9c Mon Sep 17 00:00:00 2001 From: androosss <101566943+androosss@users.noreply.github.com> Date: Mon, 7 Apr 2025 14:31:16 +0200 Subject: [PATCH 46/59] [TDM] Implement Lie in Wait (#13494) * Implemented Lie in Wait * code refactor * improve ability --- Mage.Sets/src/mage/cards/l/LieInWait.java | 86 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 87 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/l/LieInWait.java diff --git a/Mage.Sets/src/mage/cards/l/LieInWait.java b/Mage.Sets/src/mage/cards/l/LieInWait.java new file mode 100644 index 00000000000..249818aa62a --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LieInWait.java @@ -0,0 +1,86 @@ +package mage.cards.l; + +import java.util.List; +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.EachTargetPointer; + +/** + * + * @author androosss + */ +public final class LieInWait extends CardImpl { + + public LieInWait(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}{G}{U}"); + + + // Return target creature card from your graveyard to your hand. Lie in Wait deals damage equal to that card's power to target creature. + this.getSpellAbility().addEffect(new LieInWaitTargetEffect()); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE)); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE)); + } + + private LieInWait(final LieInWait card) { + super(card); + } + + @Override + public LieInWait copy() { + return new LieInWait(this); + } + +} + +class LieInWaitTargetEffect extends OneShotEffect { + + LieInWaitTargetEffect() { + super(Outcome.Benefit); + staticText = "Return target creature card from your graveyard to your hand. " + + "{this} deals damage equal to that card's power to target creature"; + setTargetPointer(new EachTargetPointer()); + } + + private LieInWaitTargetEffect(final LieInWaitTargetEffect effect) { + super(effect); + } + + @Override + public LieInWaitTargetEffect copy() { + return new LieInWaitTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + List targets = getTargetPointer().getTargets(game, source); + Card card = game.getCard(targets.get(0)); + if (card == null) { + return false; + } + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + boolean result = card.moveToZone(Zone.HAND, source, game,false); + if (result && targets.size() >= 2) { + int power = card.getPower().getValue(); + Permanent permanent = game.getPermanent(targets.get(1)); + permanent.damage(power, source, game); + } + return result; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index a1a5e1d34e7..25de9c8f542 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -132,6 +132,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Knockout Maneuver", 147, Rarity.UNCOMMON, mage.cards.k.KnockoutManeuver.class)); cards.add(new SetCardInfo("Kotis, the Fangkeeper", 202, Rarity.RARE, mage.cards.k.KotisTheFangkeeper.class)); cards.add(new SetCardInfo("Krotiq Nestguard", 148, Rarity.COMMON, mage.cards.k.KrotiqNestguard.class)); + cards.add(new SetCardInfo("Lie in Wait", 203, Rarity.UNCOMMON, mage.cards.l.LieInWait.class)); cards.add(new SetCardInfo("Lightfoot Technique", 14, Rarity.COMMON, mage.cards.l.LightfootTechnique.class)); cards.add(new SetCardInfo("Lotuslight Dancers", 204, Rarity.RARE, mage.cards.l.LotuslightDancers.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lotuslight Dancers", 363, Rarity.RARE, mage.cards.l.LotuslightDancers.class, NON_FULL_USE_VARIOUS)); From 6202e781bd9acc594f8d5b67490ddec69f398a49 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 7 Apr 2025 09:01:36 -0400 Subject: [PATCH 47/59] [TDM] Implement Eshki Dragonclaw --- .../src/mage/cards/e/EshkiDragonclaw.java | 156 ++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 157 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/EshkiDragonclaw.java diff --git a/Mage.Sets/src/mage/cards/e/EshkiDragonclaw.java b/Mage.Sets/src/mage/cards/e/EshkiDragonclaw.java new file mode 100644 index 00000000000..695026c5fa7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EshkiDragonclaw.java @@ -0,0 +1,156 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.keyword.WardAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.WatcherScope; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EshkiDragonclaw extends CardImpl { + + public EshkiDragonclaw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Ward {1} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{1}"))); + + // At the beginning of combat on your turn, if you've cast both a creature spell and a noncreature spell this turn, draw a card and put two +1/+1 counters on Eshki Dragonclaw. + Ability ability = new BeginningOfCombatTriggeredAbility(new DrawCardSourceControllerEffect(1)) + .withInterveningIf(EshkiDragonclawCondition.instance); + ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(2)).concatBy("and")); + this.addAbility(ability.addHint(EshkiDragonclawHint.instance), new EshkiDragonclawWatcher()); + } + + private EshkiDragonclaw(final EshkiDragonclaw card) { + super(card); + } + + @Override + public EshkiDragonclaw copy() { + return new EshkiDragonclaw(this); + } +} + +enum EshkiDragonclawCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return EshkiDragonclawWatcher.checkPlayer(source.getControllerId(), game) == 3; + } + + @Override + public String toString() { + return "you've cast both a creature spell and a noncreature spell this turn"; + } +} + +enum EshkiDragonclawHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + switch (EshkiDragonclawWatcher.checkPlayer(ability.getControllerId(), game)) { + case 0: + return null; + case 1: + return "You've cast a creature spell this turn"; + case 2: + return "You've cast a noncreature spell this turn"; + case 3: + return "You've cast a creature spell and a noncreature spell this turn"; + } + return null; + } + + @Override + public Hint copy() { + return this; + } +} + +class EshkiDragonclawWatcher extends Watcher { + + private final Map creatureCount = new HashMap<>(); + private final Map nonCreatureCount = new HashMap<>(); + + EshkiDragonclawWatcher() { + 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) { + return; + } + if (spell.isCreature(game)) { + creatureCount.compute(spell.getControllerId(), CardUtil::setOrIncrementValue); + } else { + nonCreatureCount.compute(spell.getControllerId(), CardUtil::setOrIncrementValue); + } + } + + @Override + public void reset() { + super.reset(); + creatureCount.clear(); + nonCreatureCount.clear(); + } + + private int checkCreature(UUID playerId) { + return creatureCount.getOrDefault(playerId, 0) > 0 ? 1 : 0; + } + + private int checkNonCreature(UUID playerId) { + return nonCreatureCount.getOrDefault(playerId, 0) > 0 ? 2 : 0; + } + + private int check(UUID playerId) { + return checkCreature(playerId) + checkNonCreature(playerId); + } + + static int checkPlayer(UUID playerId, Game game) { + return game.getState().getWatcher(EshkiDragonclawWatcher.class).check(playerId); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 25de9c8f542..4ac988fe272 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -86,6 +86,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Embermouth Sentinel", 242, Rarity.COMMON, mage.cards.e.EmbermouthSentinel.class)); cards.add(new SetCardInfo("Encroaching Dragonstorm", 142, Rarity.UNCOMMON, mage.cards.e.EncroachingDragonstorm.class)); cards.add(new SetCardInfo("Equilibrium Adept", 106, Rarity.UNCOMMON, mage.cards.e.EquilibriumAdept.class)); + cards.add(new SetCardInfo("Eshki Dragonclaw", 182, Rarity.RARE, mage.cards.e.EshkiDragonclaw.class)); cards.add(new SetCardInfo("Essence Anchor", 44, Rarity.UNCOMMON, mage.cards.e.EssenceAnchor.class)); cards.add(new SetCardInfo("Evolving Wilds", 255, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); cards.add(new SetCardInfo("Fangkeeper's Familiar", 183, Rarity.RARE, mage.cards.f.FangkeepersFamiliar.class)); From 08e31b860e49166a7b8dcb750e513d6a88e3758b Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 7 Apr 2025 09:14:42 -0400 Subject: [PATCH 48/59] [TDM] Implement Stillness in Motion --- .../src/mage/cards/s/StillnessInMotion.java | 80 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 81 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/StillnessInMotion.java diff --git a/Mage.Sets/src/mage/cards/s/StillnessInMotion.java b/Mage.Sets/src/mage/cards/s/StillnessInMotion.java new file mode 100644 index 00000000000..7a436bf0a0c --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StillnessInMotion.java @@ -0,0 +1,80 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StillnessInMotion extends CardImpl { + + public StillnessInMotion(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + + // At the beginning of your upkeep, mill three cards. Then if you have no cards in your library, exile this enchantment and put five cards from your graveyard on top of your library in any order. + Ability ability = new BeginningOfUpkeepTriggeredAbility(new MillCardsControllerEffect(3)); + ability.addEffect(new StillnessInMotionEffect()); + this.addAbility(ability); + } + + private StillnessInMotion(final StillnessInMotion card) { + super(card); + } + + @Override + public StillnessInMotion copy() { + return new StillnessInMotion(this); + } +} + +class StillnessInMotionEffect extends OneShotEffect { + + StillnessInMotionEffect() { + super(Outcome.Benefit); + staticText = "Then if you have no cards in your library, exile this enchantment " + + "and put five cards from your graveyard on top of your library in any order"; + } + + private StillnessInMotionEffect(final StillnessInMotionEffect effect) { + super(effect); + } + + @Override + public StillnessInMotionEffect copy() { + return new StillnessInMotionEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || player.getLibrary().hasCards()) { + return false; + } + Optional.ofNullable(source.getSourcePermanentIfItStillExists(game)) + .ifPresent(permanent -> player.moveCards(permanent, Zone.EXILED, source, game)); + int graveCount = Math.min(player.getGraveyard().size(), 5); + if (graveCount < 1) { + return true; + } + TargetCard target = new TargetCardInYourGraveyard(graveCount); + target.withNotTarget(true); + player.choose(outcome, target, source, game); + player.putCardsOnTopOfLibrary(new CardsImpl(target.getTargets()), game, source, true); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 4ac988fe272..5ad3cf523f3 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -202,6 +202,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Stadium Headliner", 122, Rarity.RARE, mage.cards.s.StadiumHeadliner.class)); cards.add(new SetCardInfo("Starry-Eyed Skyrider", 25, Rarity.UNCOMMON, mage.cards.s.StarryEyedSkyrider.class)); cards.add(new SetCardInfo("Static Snare", 26, Rarity.UNCOMMON, mage.cards.s.StaticSnare.class)); + cards.add(new SetCardInfo("Stillness in Motion", 59, Rarity.RARE, mage.cards.s.StillnessInMotion.class)); cards.add(new SetCardInfo("Stormbeacon Blade", 27, Rarity.UNCOMMON, mage.cards.s.StormbeaconBlade.class)); cards.add(new SetCardInfo("Stormplain Detainment", 28, Rarity.COMMON, mage.cards.s.StormplainDetainment.class)); cards.add(new SetCardInfo("Stormscale Scion", 123, Rarity.MYTHIC, mage.cards.s.StormscaleScion.class)); From 325b4a046115f35cf2921c7c3653ba4333ccaa46 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 7 Apr 2025 09:56:59 -0400 Subject: [PATCH 49/59] [TDM] Implement Lasyd Prowler --- Mage.Sets/src/mage/cards/l/LasydProwler.java | 67 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 68 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/l/LasydProwler.java diff --git a/Mage.Sets/src/mage/cards/l/LasydProwler.java b/Mage.Sets/src/mage/cards/l/LasydProwler.java new file mode 100644 index 00000000000..1971addf543 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LasydProwler.java @@ -0,0 +1,67 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.ExileSourceFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LasydProwler extends CardImpl { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_LAND, null); + private static final Hint hint = new ValueHint("Land cards in your graveyard", xValue); + + public LasydProwler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); + + this.subtype.add(SubType.SNAKE); + this.subtype.add(SubType.RANGER); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // When this creature enters, you may mill cards equal to the number of lands you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(LandsYouControlCount.instance) + .setText("mill cards equal to the number of lands you control"), true)); + + // Renew -- {1}{G}, Exile this card from your graveyard: Put X +1/+1 counters on target creature, where X is the number of land cards in your graveyard. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + Zone.GRAVEYARD, + new AddCountersTargetEffect(CounterType.P1P1.createInstance(0), xValue), + new ManaCostsImpl<>("{1}{G}") + ); + ability.addCost(new ExileSourceFromGraveCost()); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability.setAbilityWord(AbilityWord.RENEW).addHint(hint)); + } + + private LasydProwler(final LasydProwler card) { + super(card); + } + + @Override + public LasydProwler copy() { + return new LasydProwler(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 5ad3cf523f3..08f075c9440 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -133,6 +133,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Knockout Maneuver", 147, Rarity.UNCOMMON, mage.cards.k.KnockoutManeuver.class)); cards.add(new SetCardInfo("Kotis, the Fangkeeper", 202, Rarity.RARE, mage.cards.k.KotisTheFangkeeper.class)); cards.add(new SetCardInfo("Krotiq Nestguard", 148, Rarity.COMMON, mage.cards.k.KrotiqNestguard.class)); + cards.add(new SetCardInfo("Lasyd Prowler", 149, Rarity.RARE, mage.cards.l.LasydProwler.class)); cards.add(new SetCardInfo("Lie in Wait", 203, Rarity.UNCOMMON, mage.cards.l.LieInWait.class)); cards.add(new SetCardInfo("Lightfoot Technique", 14, Rarity.COMMON, mage.cards.l.LightfootTechnique.class)); cards.add(new SetCardInfo("Lotuslight Dancers", 204, Rarity.RARE, mage.cards.l.LotuslightDancers.class, NON_FULL_USE_VARIOUS)); From c9148aa2d32a3e3a40447b14349b4b166928c78d Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 7 Apr 2025 10:03:19 -0400 Subject: [PATCH 50/59] [TDM] Implement Naga Fleshcrafter --- .../src/mage/cards/n/NagaFleshcrafter.java | 102 ++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 103 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/n/NagaFleshcrafter.java diff --git a/Mage.Sets/src/mage/cards/n/NagaFleshcrafter.java b/Mage.Sets/src/mage/cards/n/NagaFleshcrafter.java new file mode 100644 index 00000000000..f160e54b9f9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NagaFleshcrafter.java @@ -0,0 +1,102 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.costs.common.ExileSourceFromGraveCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CopyPermanentEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NagaFleshcrafter extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent("nonlegendary creature you control"); + + static { + filter.add(Predicates.not(SuperType.LEGENDARY.getPredicate())); + } + + public NagaFleshcrafter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + + this.subtype.add(SubType.SNAKE); + this.subtype.add(SubType.SHAPESHIFTER); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // You may have this creature enter as a copy of any creature on the battlefield. + this.addAbility(new EntersBattlefieldAbility(new CopyPermanentEffect(), true)); + + // Renew -- {2}{U}, Exile this card from your graveyard: Put a +1/+1 counter on target nonlegendary creature you control. Each other creature you control becomes a copy of that creature until end of turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + Zone.GRAVEYARD, + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + new ManaCostsImpl<>("{2}{U}") + ); + ability.addCost(new ExileSourceFromGraveCost()); + ability.addEffect(new NagaFleshcrafterEffect()); + ability.addTarget(new TargetPermanent(filter)); + this.addAbility(ability.setAbilityWord(AbilityWord.RENEW)); + } + + private NagaFleshcrafter(final NagaFleshcrafter card) { + super(card); + } + + @Override + public NagaFleshcrafter copy() { + return new NagaFleshcrafter(this); + } +} + +class NagaFleshcrafterEffect extends OneShotEffect { + + NagaFleshcrafterEffect() { + super(Outcome.Benefit); + staticText = "each other creature you control becomes a copy of that creature until end of turn"; + } + + private NagaFleshcrafterEffect(final NagaFleshcrafterEffect effect) { + super(effect); + } + + @Override + public NagaFleshcrafterEffect copy() { + return new NagaFleshcrafterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + for (Permanent creature : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, + source.getControllerId(), source, game + )) { + if (!permanent.getId().equals(creature.getId())) { + game.copyPermanent(Duration.EndOfTurn, permanent, creature.getId(), source, null); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 08f075c9440..0a2b0331323 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -149,6 +149,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Mountain", 283, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mox Jasper", 246, Rarity.MYTHIC, mage.cards.m.MoxJasper.class)); cards.add(new SetCardInfo("Mystic Monastery", 262, Rarity.UNCOMMON, mage.cards.m.MysticMonastery.class)); + cards.add(new SetCardInfo("Naga Fleshcrafter", 52, Rarity.RARE, mage.cards.n.NagaFleshcrafter.class)); cards.add(new SetCardInfo("Narset's Rebuke", 114, Rarity.COMMON, mage.cards.n.NarsetsRebuke.class)); cards.add(new SetCardInfo("Narset, Jeskai Waymaster", 209, Rarity.RARE, mage.cards.n.NarsetJeskaiWaymaster.class)); cards.add(new SetCardInfo("Nature's Rhythm", 150, Rarity.RARE, mage.cards.n.NaturesRhythm.class)); From 97adeca9be8f07abfec8354f0c4255c30ed18b2b Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 7 Apr 2025 10:13:53 -0400 Subject: [PATCH 51/59] [TDM] Implement Riverwheel Sweep --- .../src/mage/cards/r/RiverwheelSweep.java | 100 ++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 101 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RiverwheelSweep.java diff --git a/Mage.Sets/src/mage/cards/r/RiverwheelSweep.java b/Mage.Sets/src/mage/cards/r/RiverwheelSweep.java new file mode 100644 index 00000000000..94273f90e3b --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RiverwheelSweep.java @@ -0,0 +1,100 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.cards.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInExile; +import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RiverwheelSweep extends CardImpl { + + public RiverwheelSweep(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2/U}{2/R}{2/W}"); + + // Tap target creature. Put three stun counters on it. + this.getSpellAbility().addEffect(new TapTargetEffect()); + this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance(3)) + .setText("put three stun counters on it")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + + // Exile the top two cards of your library. Choose one of them. Until the end of your next turn, you may play that card. + this.getSpellAbility().addEffect(new RiverwheelSweepEffect()); + } + + private RiverwheelSweep(final RiverwheelSweep card) { + super(card); + } + + @Override + public RiverwheelSweep copy() { + return new RiverwheelSweep(this); + } +} + +class RiverwheelSweepEffect extends OneShotEffect { + + RiverwheelSweepEffect() { + super(Outcome.Benefit); + staticText = "Exile the top two cards of your library. Choose one of them. " + + "Until the end of your next turn, you may play that card"; + this.concatBy("
"); + } + + private RiverwheelSweepEffect(final RiverwheelSweepEffect effect) { + super(effect); + } + + @Override + public RiverwheelSweepEffect copy() { + return new RiverwheelSweepEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, 2)); + player.moveCards(cards, Zone.EXILED, source, game); + cards.retainZone(Zone.EXILED, game); + Card card; + switch (cards.size()) { + case 0: + return false; + case 1: + card = cards.getRandom(game); + break; + default: + TargetCard target = new TargetCardInExile(StaticFilters.FILTER_CARD); + target.withNotTarget(true); + player.choose(Outcome.DrawCard, cards, target, source, game); + card = game.getCard(target.getFirstTarget()); + } + if (card == null) { + return false; + } + CardUtil.makeCardPlayable( + game, source, card, false, + Duration.UntilEndOfYourNextTurn, false + ); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 0a2b0331323..38771af781b 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -174,6 +174,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Revival of the Ancestors", 218, Rarity.RARE, mage.cards.r.RevivalOfTheAncestors.class)); cards.add(new SetCardInfo("Ringing Strike Mastery", 53, Rarity.COMMON, mage.cards.r.RingingStrikeMastery.class)); cards.add(new SetCardInfo("Riverwalk Technique", 54, Rarity.COMMON, mage.cards.r.RiverwalkTechnique.class)); + cards.add(new SetCardInfo("Riverwheel Sweep", 219, Rarity.UNCOMMON, mage.cards.r.RiverwheelSweep.class)); cards.add(new SetCardInfo("Roamer's Routine", 154, Rarity.COMMON, mage.cards.r.RoamersRoutine.class)); cards.add(new SetCardInfo("Roar of Endless Song", 220, Rarity.RARE, mage.cards.r.RoarOfEndlessSong.class)); cards.add(new SetCardInfo("Roiling Dragonstorm", 55, Rarity.UNCOMMON, mage.cards.r.RoilingDragonstorm.class)); From c5b62eb620c706c624ac6e8c33783bd9bd24ef17 Mon Sep 17 00:00:00 2001 From: Mike Cunningham Date: Mon, 7 Apr 2025 14:57:18 -0400 Subject: [PATCH 52/59] [TDM] Implement Wail of War (#13504) * [TDM] Implement Wail of War * Updating BoostControlledEffect call * Resolving test errors * Update debuff ability, adjust setText, update graveyard filter * Removing setText, updating targetOpponent --- Mage.Sets/src/mage/cards/w/WailOfWar.java | 55 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 56 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/w/WailOfWar.java diff --git a/Mage.Sets/src/mage/cards/w/WailOfWar.java b/Mage.Sets/src/mage/cards/w/WailOfWar.java new file mode 100644 index 00000000000..5c40127e78a --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WailOfWar.java @@ -0,0 +1,55 @@ +package mage.cards.w; + +import mage.abilities.Mode; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.TargetController; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * Represents the "Wail of War" instant card. + * Author: @mikejcunn + */ +public final class WailOfWar extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures target opponent controls"); + + static { + filter.add(TargetController.SOURCE_TARGETS.getControllerPredicate()); + } + + public WailOfWar(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); + + // Mode 1: Creatures target player controls get -1/-1 until end of turn. + this.getSpellAbility().addEffect(new BoostAllEffect( + -1, -1, Duration.EndOfTurn, filter, false + )); + this.getSpellAbility().addTarget(new TargetOpponent()); + + // Mode 2: Return creatures from graveyard + Mode returnCreaturesFromGraveyardMode = new Mode(new ReturnFromGraveyardToHandTargetEffect()); + returnCreaturesFromGraveyardMode.addTarget(new TargetCardInGraveyard( + 0, 2, StaticFilters.FILTER_CARD_CREATURES_YOUR_GRAVEYARD + )); + this.getSpellAbility().addMode(returnCreaturesFromGraveyardMode); + } + + private WailOfWar(final WailOfWar card) { + super(card); + } + + @Override + public WailOfWar copy() { + return new WailOfWar(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 38771af781b..c61f700b718 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -241,6 +241,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Veteran Ice Climber", 64, Rarity.UNCOMMON, mage.cards.v.VeteranIceClimber.class)); cards.add(new SetCardInfo("Voice of Victory", 33, Rarity.RARE, mage.cards.v.VoiceOfVictory.class)); cards.add(new SetCardInfo("War Effort", 131, Rarity.UNCOMMON, mage.cards.w.WarEffort.class)); + cards.add(new SetCardInfo("Wail of War", 98, Rarity.UNCOMMON, mage.cards.w.WailOfWar.class)); cards.add(new SetCardInfo("Watcher of the Wayside", 249, Rarity.COMMON, mage.cards.w.WatcherOfTheWayside.class)); cards.add(new SetCardInfo("Wayspeaker Bodyguard", 34, Rarity.UNCOMMON, mage.cards.w.WayspeakerBodyguard.class)); cards.add(new SetCardInfo("Wild Ride", 132, Rarity.COMMON, mage.cards.w.WildRide.class)); From 49d5d6ded1e2e895c768452ec5583f4fdff24712 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 7 Apr 2025 11:43:24 -0400 Subject: [PATCH 53/59] [TDM] Implement Tersa Lightshatter --- .../src/mage/cards/t/TersaLightshatter.java | 85 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 86 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TersaLightshatter.java diff --git a/Mage.Sets/src/mage/cards/t/TersaLightshatter.java b/Mage.Sets/src/mage/cards/t/TersaLightshatter.java new file mode 100644 index 00000000000..ea13914f87e --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TersaLightshatter.java @@ -0,0 +1,85 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.ThresholdCondition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.discard.DiscardAndDrawThatManyEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TersaLightshatter extends CardImpl { + + public TersaLightshatter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ORC); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When Tersa Lightshatter enters, discard up to two cards, then draw that many cards. + this.addAbility(new EntersBattlefieldTriggeredAbility(new DiscardAndDrawThatManyEffect(2))); + + // Whenever Tersa Lightshatter attacks, if there are seven or more cards in your graveyard, exile a card at random from your graveyard. You may play that card this turn. + this.addAbility(new AttacksTriggeredAbility(new TersaLightshatterEffect()).withInterveningIf(ThresholdCondition.instance)); + } + + private TersaLightshatter(final TersaLightshatter card) { + super(card); + } + + @Override + public TersaLightshatter copy() { + return new TersaLightshatter(this); + } +} + +class TersaLightshatterEffect extends OneShotEffect { + + TersaLightshatterEffect() { + super(Outcome.Benefit); + staticText = "exile a card at random from your graveyard. You may play that card this turn"; + } + + private TersaLightshatterEffect(final TersaLightshatterEffect effect) { + super(effect); + } + + @Override + public TersaLightshatterEffect copy() { + return new TersaLightshatterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Card card = player.getGraveyard().getRandom(game); + if (card == null) { + return false; + } + player.moveCards(card, Zone.EXILED, source, game); + CardUtil.makeCardPlayable(game, source, card, false, Duration.EndOfTurn, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index c61f700b718..1909add4f1f 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -221,6 +221,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Temur Devotee", 61, Rarity.COMMON, mage.cards.t.TemurDevotee.class)); cards.add(new SetCardInfo("Temur Monument", 248, Rarity.UNCOMMON, mage.cards.t.TemurMonument.class)); cards.add(new SetCardInfo("Temur Tawnyback", 229, Rarity.COMMON, mage.cards.t.TemurTawnyback.class)); + cards.add(new SetCardInfo("Tersa Lightshatter", 127, Rarity.RARE, mage.cards.t.TersaLightshatter.class)); cards.add(new SetCardInfo("The Sibsig Ceremony", 91, Rarity.RARE, mage.cards.t.TheSibsigCeremony.class)); cards.add(new SetCardInfo("Thornwood Falls", 269, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class)); cards.add(new SetCardInfo("Thunder of Unity", 231, Rarity.RARE, mage.cards.t.ThunderOfUnity.class)); From aad92581ce869afb6c2e91ea9ad6f2499915f448 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 7 Apr 2025 12:31:29 -0400 Subject: [PATCH 54/59] [TDM] Implement Ureni, the Song Unending --- .../mage/cards/u/UreniTheSongUnending.java | 65 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 66 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/u/UreniTheSongUnending.java diff --git a/Mage.Sets/src/mage/cards/u/UreniTheSongUnending.java b/Mage.Sets/src/mage/cards/u/UreniTheSongUnending.java new file mode 100644 index 00000000000..b1bd25b98a3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UreniTheSongUnending.java @@ -0,0 +1,65 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.LandsYouControlCount; +import mage.abilities.effects.common.DamageMultiEffect; +import mage.abilities.hint.common.LandsYouControlHint; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.ProtectionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent; +import mage.target.common.TargetPermanentAmount; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UreniTheSongUnending extends CardImpl { + + private static final FilterPermanent filter + = new FilterCreatureOrPlaneswalkerPermanent("creatures and/or planeswalkers your opponents control"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public UreniTheSongUnending(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(10); + this.toughness = new MageInt(10); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Protection from white and from black + this.addAbility(ProtectionAbility.from(ObjectColor.WHITE, ObjectColor.BLACK)); + + // When Ureni enters, it deals X damage divided as you choose among any number of target creatures and/or planeswalkers your opponents control, where X is the number of lands you control. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageMultiEffect("it")); + ability.addTarget(new TargetPermanentAmount(LandsYouControlCount.instance, 0, filter)); + this.addAbility(ability.addHint(LandsYouControlHint.instance)); + } + + private UreniTheSongUnending(final UreniTheSongUnending card) { + super(card); + } + + @Override + public UreniTheSongUnending copy() { + return new UreniTheSongUnending(this); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 1909add4f1f..c28827e6fdc 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -238,6 +238,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Unrooted Ancestor", 96, Rarity.UNCOMMON, mage.cards.u.UnrootedAncestor.class)); cards.add(new SetCardInfo("Unsparing Boltcaster", 130, Rarity.UNCOMMON, mage.cards.u.UnsparingBoltcaster.class)); cards.add(new SetCardInfo("Ureni's Rebuff", 63, Rarity.UNCOMMON, mage.cards.u.UrenisRebuff.class)); + cards.add(new SetCardInfo("Ureni, the Song Unending", 233, Rarity.MYTHIC, mage.cards.u.UreniTheSongUnending.class)); cards.add(new SetCardInfo("Venerated Stormsinger", 97, Rarity.UNCOMMON, mage.cards.v.VeneratedStormsinger.class)); cards.add(new SetCardInfo("Veteran Ice Climber", 64, Rarity.UNCOMMON, mage.cards.v.VeteranIceClimber.class)); cards.add(new SetCardInfo("Voice of Victory", 33, Rarity.RARE, mage.cards.v.VoiceOfVictory.class)); From 31f152836007c3d863e539a2cc37b2c132f42af1 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 7 Apr 2025 12:38:52 -0400 Subject: [PATCH 55/59] [TDM] Implement Sunpearl Kirin --- Mage.Sets/src/mage/cards/s/SunpearlKirin.java | 95 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 96 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SunpearlKirin.java diff --git a/Mage.Sets/src/mage/cards/s/SunpearlKirin.java b/Mage.Sets/src/mage/cards/s/SunpearlKirin.java new file mode 100644 index 00000000000..3283e9eac23 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SunpearlKirin.java @@ -0,0 +1,95 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterNonlandPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SunpearlKirin extends CardImpl { + + private static final FilterPermanent filter = new FilterNonlandPermanent("other target nonland permanent you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TargetController.YOU.getControllerPredicate()); + } + + public SunpearlKirin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.subtype.add(SubType.KIRIN); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When this creature enters, return up to one other target nonland permanent you control to its owner's hand. If it was a token, draw a card. + Ability ability = new EntersBattlefieldTriggeredAbility(new SunpearlKirinEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + } + + private SunpearlKirin(final SunpearlKirin card) { + super(card); + } + + @Override + public SunpearlKirin copy() { + return new SunpearlKirin(this); + } +} + +class SunpearlKirinEffect extends OneShotEffect { + + SunpearlKirinEffect() { + super(Outcome.Benefit); + staticText = "return up to one other target nonland permanent " + + "you control to its owner's hand. If it was a token, draw a card"; + } + + private SunpearlKirinEffect(final SunpearlKirinEffect effect) { + super(effect); + } + + @Override + public SunpearlKirinEffect copy() { + return new SunpearlKirinEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (player == null || permanent == null) { + return false; + } + boolean isToken = permanent instanceof PermanentToken; + player.moveCards(permanent, Zone.HAND, source, game); + if (isToken) { + player.drawCards(1, source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index c28827e6fdc..b17a1bd2d90 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -212,6 +212,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Sultai Devotee", 160, Rarity.COMMON, mage.cards.s.SultaiDevotee.class)); cards.add(new SetCardInfo("Sultai Monument", 247, Rarity.UNCOMMON, mage.cards.s.SultaiMonument.class)); cards.add(new SetCardInfo("Summit Intimidator", 125, Rarity.COMMON, mage.cards.s.SummitIntimidator.class)); + cards.add(new SetCardInfo("Sunpearl Kirin", 29, Rarity.UNCOMMON, mage.cards.s.SunpearlKirin.class)); cards.add(new SetCardInfo("Sunset Strikemaster", 126, Rarity.UNCOMMON, mage.cards.s.SunsetStrikemaster.class)); cards.add(new SetCardInfo("Swamp", 281, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swiftwater Cliffs", 268, Rarity.COMMON, mage.cards.s.SwiftwaterCliffs.class)); From e3937e31c1b366337a7d9fe7353765d06815ba71 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Mon, 7 Apr 2025 14:55:46 -0400 Subject: [PATCH 56/59] [TDM] Implement Strategic Betrayal --- .../src/mage/cards/s/StrategicBetrayal.java | 78 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + 2 files changed, 79 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/StrategicBetrayal.java diff --git a/Mage.Sets/src/mage/cards/s/StrategicBetrayal.java b/Mage.Sets/src/mage/cards/s/StrategicBetrayal.java new file mode 100644 index 00000000000..253659ce02d --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StrategicBetrayal.java @@ -0,0 +1,78 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class StrategicBetrayal extends CardImpl { + + public StrategicBetrayal(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); + + // Target opponent exiles a creature they control and their graveyard. + this.getSpellAbility().addEffect(new StrategicBetrayalEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + } + + private StrategicBetrayal(final StrategicBetrayal card) { + super(card); + } + + @Override + public StrategicBetrayal copy() { + return new StrategicBetrayal(this); + } +} + +class StrategicBetrayalEffect extends OneShotEffect { + + StrategicBetrayalEffect() { + super(Outcome.Benefit); + staticText = "target opponent exiles a creature they control and their graveyard"; + } + + private StrategicBetrayalEffect(final StrategicBetrayalEffect effect) { + super(effect); + } + + @Override + public StrategicBetrayalEffect copy() { + return new StrategicBetrayalEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (player == null) { + return false; + } + Cards cards = new CardsImpl(player.getGraveyard()); + if (game.getBattlefield().contains( + StaticFilters.FILTER_CONTROLLED_CREATURE, + player.getId(), source, game, 1 + )) { + TargetPermanent target = new TargetControlledCreaturePermanent(); + target.withNotTarget(true); + player.choose(Outcome.DestroyPermanent, target, source, game); + cards.add(target.getFirstTarget()); + } + return player.moveCards(cards, Zone.EXILED, source, game); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index b17a1bd2d90..170bef109a3 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -209,6 +209,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Stormbeacon Blade", 27, Rarity.UNCOMMON, mage.cards.s.StormbeaconBlade.class)); cards.add(new SetCardInfo("Stormplain Detainment", 28, Rarity.COMMON, mage.cards.s.StormplainDetainment.class)); cards.add(new SetCardInfo("Stormscale Scion", 123, Rarity.MYTHIC, mage.cards.s.StormscaleScion.class)); + cards.add(new SetCardInfo("Strategic Betrayal", 94, Rarity.UNCOMMON, mage.cards.s.StrategicBetrayal.class)); cards.add(new SetCardInfo("Sultai Devotee", 160, Rarity.COMMON, mage.cards.s.SultaiDevotee.class)); cards.add(new SetCardInfo("Sultai Monument", 247, Rarity.UNCOMMON, mage.cards.s.SultaiMonument.class)); cards.add(new SetCardInfo("Summit Intimidator", 125, Rarity.COMMON, mage.cards.s.SummitIntimidator.class)); From 0df5f17603c3df35167a17353f64601fa913adac Mon Sep 17 00:00:00 2001 From: Jmlundeen <98545818+Jmlundeen@users.noreply.github.com> Date: Tue, 8 Apr 2025 07:54:18 -0500 Subject: [PATCH 57/59] [TDM] Implement omen mechanic (#13501) * Abstract AdventureCard to SingleFaceSplitCard * Fix AdventureCardSpellImpl * Finish converting adventure card and adventure spell * Update Brightcap Badger change finalize call to adventure card * Update Darksteel Monolith being cast from hand condition referencing AdventureCardSpell * Update Tlincalli Hunter exiled creature condition referencing AdventureCardSpell * Update Twice Upon a Time finalizeAdventure called from Adventure card * Finish abstracting Adventure missed some more references to adventure cards * Implement Omen cards * Implement Dirgur Island Dragon * Missed some adventureSpellName references * OmenCardSpell had wrong comma symbol * Add tests for Omen Cards * Rename two part card components change from SingleFaceSplitCard to CardWithSpellOption * Update comments and variable name --- .../mage/card/arcane/ModernCardRenderer.java | 14 +- .../card/arcane/ModernSplitCardRenderer.java | 12 +- .../src/main/java/mage/view/CardView.java | 28 +-- .../src/mage/cards/b/BrightcapBadger.java | 2 +- .../src/mage/cards/d/DarksteelMonolith.java | 2 +- .../src/mage/cards/d/DirgurIslandDragon.java | 52 ++++++ .../src/mage/cards/t/TlincalliHunter.java | 2 +- .../src/mage/cards/t/TwiceUponATime.java | 2 +- .../src/mage/sets/TarkirDragonstorm.java | 1 + .../test/cards/cost/omen/OmenCardsTest.java | 77 ++++++++ .../java/mage/abilities/SpellAbility.java | 4 +- .../common/IsBeingCastFromHandCondition.java | 4 +- .../abilities/effects/ContinuousEffects.java | 6 +- .../common/ExileAdventureSpellEffect.java | 6 +- .../abilities/keyword/ForetellAbility.java | 16 +- .../mage/abilities/keyword/PlotAbility.java | 12 +- .../main/java/mage/cards/AdventureCard.java | 124 +------------ .../java/mage/cards/AdventureCardSpell.java | 12 -- ...SpellImpl.java => AdventureSpellCard.java} | 25 ++- Mage/src/main/java/mage/cards/CardImpl.java | 4 +- .../java/mage/cards/CardWithSpellOption.java | 131 +++++++++++++ Mage/src/main/java/mage/cards/OmenCard.java | 34 ++++ .../main/java/mage/cards/OmenSpellCard.java | 174 ++++++++++++++++++ .../main/java/mage/cards/SpellOptionCard.java | 18 ++ .../main/java/mage/cards/mock/MockCard.java | 14 +- .../java/mage/cards/repository/CardInfo.java | 26 +-- .../mage/cards/repository/CardRepository.java | 22 +-- .../java/mage/constants/SpellAbilityType.java | 3 +- .../src/main/java/mage/constants/SubType.java | 1 + .../predicate/card/CardTextPredicate.java | 13 +- Mage/src/main/java/mage/game/GameImpl.java | 4 +- Mage/src/main/java/mage/game/GameState.java | 8 +- Mage/src/main/java/mage/game/stack/Spell.java | 13 +- .../main/java/mage/players/PlayerImpl.java | 8 +- Mage/src/main/java/mage/util/CardUtil.java | 18 +- Mage/src/main/java/mage/util/ManaUtil.java | 9 +- 36 files changed, 637 insertions(+), 264 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/d/DirgurIslandDragon.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/cost/omen/OmenCardsTest.java delete mode 100644 Mage/src/main/java/mage/cards/AdventureCardSpell.java rename Mage/src/main/java/mage/cards/{AdventureCardSpellImpl.java => AdventureSpellCard.java} (88%) create mode 100644 Mage/src/main/java/mage/cards/CardWithSpellOption.java create mode 100644 Mage/src/main/java/mage/cards/OmenCard.java create mode 100644 Mage/src/main/java/mage/cards/OmenSpellCard.java create mode 100644 Mage/src/main/java/mage/cards/SpellOptionCard.java diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java index eb2643061dc..f188a9fbd61 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernCardRenderer.java @@ -123,6 +123,7 @@ public class ModernCardRenderer extends CardRenderer { public static final Color ERROR_COLOR = new Color(255, 0, 255); static String SUB_TYPE_ADVENTURE = "Adventure"; + static String SUB_TYPE_OMEN = "Omen"; /////////////////////////////////////////////////////////////////////////// // Layout metrics for modern border cards @@ -168,8 +169,8 @@ public class ModernCardRenderer extends CardRenderer { // Processed mana cost string protected String manaCostString; - // Is an adventure - protected boolean isAdventure = false; + // Is an adventure or omen + protected boolean isCardWithSpellOption = false; public ModernCardRenderer(CardView card) { // Pass off to parent @@ -179,12 +180,13 @@ public class ModernCardRenderer extends CardRenderer { manaCostString = ManaSymbols.getClearManaCost(cardView.getManaCostStr()); if (cardView.isSplitCard()) { - isAdventure = cardView.getRightSplitTypeLine().contains(SUB_TYPE_ADVENTURE); + isCardWithSpellOption = cardView.getRightSplitTypeLine().contains(SUB_TYPE_ADVENTURE) + || cardView.getRightSplitTypeLine().contains(SUB_TYPE_OMEN); } } - protected boolean isAdventure() { - return isAdventure; + protected boolean isCardWithSpellOption() { + return isCardWithSpellOption; } @Override @@ -660,7 +662,7 @@ public class ModernCardRenderer extends CardRenderer { drawRulesText(g, textboxKeywords, textboxRules, contentWidth / 2 + totalContentInset + 4, totalContentInset + boxHeight + 2, contentWidth / 2 - 8, typeLineY - totalContentInset - boxHeight - 6, false); - } else if (isAdventure) { + } else if (isCardWithSpellOption) { drawRulesText(g, textboxKeywords, textboxRules, contentWidth / 2 + totalContentInset + 4, typeLineY + boxHeight + 2, contentWidth / 2 - 8, cardHeight - typeLineY - boxHeight - 4 - borderWidth * 3, false); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java index e43dfc10662..291dad2d4ec 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java @@ -56,7 +56,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { private boolean isAftermath = false; private static String trimAdventure(String rule) { - if (rule.startsWith("Adventure")) { + if (rule.startsWith("Adventure") || rule.startsWith("Omen")) { return rule.substring(rule.lastIndexOf("—") + 8); } return rule; @@ -71,7 +71,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { rightHalf.color = new ObjectColor(cardView.getRightSplitCostsStr()); leftHalf.color = new ObjectColor(cardView.getLeftSplitCostsStr()); - if (isAdventure()) { + if (isCardWithSpellOption()) { List trimmedRules = new ArrayList<>(); for (String rule : view.getRightSplitRules()) { trimmedRules.add(trimAdventure(rule)); @@ -95,7 +95,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { // they "rotate" in opposite directions making consquence and normal split cards // have the "right" vs "left" as the top half. // Adventures are treated differently and not rotated at all. - if (isAdventure()) { + if (isCardWithSpellOption()) { manaCostString = leftHalf.manaCostString; textboxKeywords = leftHalf.keywords; textboxRules = leftHalf.rules; @@ -159,7 +159,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { protected void drawBackground(Graphics2D g) { if (cardView.isFaceDown()) { drawCardBackTexture(g); - } if (isAdventure()) { + } if (isCardWithSpellOption()) { super.drawBackground(g); } else { { // Left half background (top of the card) @@ -204,7 +204,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { @Override protected void drawArt(Graphics2D g) { - if (isAdventure) { + if (isCardWithSpellOption) { super.drawArt(g); } else if (artImage != null) { if (isAftermath()) { @@ -318,7 +318,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { @Override protected void drawFrame(Graphics2D g, CardPanelAttributes attribs, BufferedImage image, boolean lessOpaqueRulesTextBox) { - if (isAdventure()) { + if (isCardWithSpellOption()) { super.drawFrame(g, attribs, image, lessOpaqueRulesTextBox); CardPanelAttributes adventureAttribs = new CardPanelAttributes( diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index c13f283c768..84d55c232fb 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -432,21 +432,21 @@ public class CardView extends SimpleCardView { fullCardName = mainCard.getLeftHalfCard().getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + mainCard.getRightHalfCard().getName(); this.manaCostLeftStr = mainCard.getLeftHalfCard().getManaCostSymbols(); this.manaCostRightStr = mainCard.getRightHalfCard().getManaCostSymbols(); - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { this.isSplitCard = true; - AdventureCard adventureCard = ((AdventureCard) card); - leftSplitName = adventureCard.getName(); - leftSplitCostsStr = String.join("", adventureCard.getManaCostSymbols()); - leftSplitRules = adventureCard.getSharedRules(game); - leftSplitTypeLine = getCardTypeLine(game, adventureCard); - AdventureCardSpell adventureCardSpell = adventureCard.getSpellCard(); - rightSplitName = adventureCardSpell.getName(); - rightSplitCostsStr = String.join("", adventureCardSpell.getManaCostSymbols()); - rightSplitRules = adventureCardSpell.getRules(game); - rightSplitTypeLine = getCardTypeLine(game, adventureCardSpell); - fullCardName = adventureCard.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + adventureCardSpell.getName(); - this.manaCostLeftStr = adventureCard.getManaCostSymbols(); - this.manaCostRightStr = adventureCardSpell.getManaCostSymbols(); + CardWithSpellOption mainCard = ((CardWithSpellOption) card); + leftSplitName = mainCard.getName(); + leftSplitCostsStr = String.join("", mainCard.getManaCostSymbols()); + leftSplitRules = mainCard.getSharedRules(game); + leftSplitTypeLine = getCardTypeLine(game, mainCard); + SpellOptionCard splitCardSpell = mainCard.getSpellCard(); + rightSplitName = splitCardSpell.getName(); + rightSplitCostsStr = String.join("", splitCardSpell.getManaCostSymbols()); + rightSplitRules = splitCardSpell.getRules(game); + rightSplitTypeLine = getCardTypeLine(game, splitCardSpell); + fullCardName = mainCard.getName() + MockCard.CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + splitCardSpell.getName(); + this.manaCostLeftStr = mainCard.getManaCostSymbols(); + this.manaCostRightStr = splitCardSpell.getManaCostSymbols(); } else if (card instanceof MockCard) { // deck editor cards fullCardName = ((MockCard) card).getFullName(true); diff --git a/Mage.Sets/src/mage/cards/b/BrightcapBadger.java b/Mage.Sets/src/mage/cards/b/BrightcapBadger.java index 34e8561d800..14d1b89aee0 100644 --- a/Mage.Sets/src/mage/cards/b/BrightcapBadger.java +++ b/Mage.Sets/src/mage/cards/b/BrightcapBadger.java @@ -52,7 +52,7 @@ public final class BrightcapBadger extends AdventureCard { // Fungus Frolic // Create two 1/1 green Saproling creature tokens. this.getSpellCard().getSpellAbility().addEffect(new CreateTokenEffect(new SaprolingToken(), 2)); - this.getSpellCard().finalizeAdventure(); + this.finalizeAdventure(); } private BrightcapBadger(final BrightcapBadger card) { diff --git a/Mage.Sets/src/mage/cards/d/DarksteelMonolith.java b/Mage.Sets/src/mage/cards/d/DarksteelMonolith.java index 3ae3a10afc3..158d6a8747b 100644 --- a/Mage.Sets/src/mage/cards/d/DarksteelMonolith.java +++ b/Mage.Sets/src/mage/cards/d/DarksteelMonolith.java @@ -54,7 +54,7 @@ enum IsBeingCastFromHandCondition implements Condition { @Override public boolean apply(Game game, Ability source) { MageObject object = game.getObject(source); - if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) { + if (object instanceof SplitCardHalf || object instanceof SpellOptionCard || object instanceof ModalDoubleFacedCardHalf) { UUID mainCardId = ((Card) object).getMainCard().getId(); object = game.getObject(mainCardId); } diff --git a/Mage.Sets/src/mage/cards/d/DirgurIslandDragon.java b/Mage.Sets/src/mage/cards/d/DirgurIslandDragon.java new file mode 100644 index 00000000000..bd4685d7d1b --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DirgurIslandDragon.java @@ -0,0 +1,52 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardSetInfo; +import mage.cards.OmenCard; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class DirgurIslandDragon extends OmenCard { + + public DirgurIslandDragon(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.INSTANT}, "{5}{U}", "Skimming Strike", "{1}{U}"); + + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Ward {2} + this.addAbility(new WardAbility(new ManaCostsImpl<>("{2}"))); + + // Skimming Strike + // Tap up to one target creature. Draw a card. + this.getSpellCard().getSpellAbility().addEffect(new TapTargetEffect()); + this.getSpellCard().getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); + this.getSpellCard().getSpellAbility().addTarget(new TargetCreaturePermanent(0, 1)); + this.finalizeOmen(); + } + + private DirgurIslandDragon(final DirgurIslandDragon card) { + super(card); + } + + @Override + public DirgurIslandDragon copy() { + return new DirgurIslandDragon(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TlincalliHunter.java b/Mage.Sets/src/mage/cards/t/TlincalliHunter.java index 9cf8845dc43..732154314e4 100644 --- a/Mage.Sets/src/mage/cards/t/TlincalliHunter.java +++ b/Mage.Sets/src/mage/cards/t/TlincalliHunter.java @@ -65,7 +65,7 @@ enum ExiledCreatureSpellCondition implements Condition { @Override public boolean apply(Game game, Ability source) { MageObject object = game.getObject(source); - if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) { + if (object instanceof SplitCardHalf || object instanceof SpellOptionCard || object instanceof ModalDoubleFacedCardHalf) { UUID mainCardId = ((Card) object).getMainCard().getId(); object = game.getObject(mainCardId); } diff --git a/Mage.Sets/src/mage/cards/t/TwiceUponATime.java b/Mage.Sets/src/mage/cards/t/TwiceUponATime.java index 9bf61c5f838..90a46602fbd 100644 --- a/Mage.Sets/src/mage/cards/t/TwiceUponATime.java +++ b/Mage.Sets/src/mage/cards/t/TwiceUponATime.java @@ -47,7 +47,7 @@ public final class TwiceUponATime extends AdventureCard { // Unlikely Meeting // Search your library for a Doctor card, reveal it, put it into your hand, then shuffle. this.getSpellCard().getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter2), true)); - this.getSpellCard().finalizeAdventure(); + this.finalizeAdventure(); } private TwiceUponATime(final TwiceUponATime card) { diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 170bef109a3..9f498363e61 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -67,6 +67,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Delta Bloodflies", 77, Rarity.COMMON, mage.cards.d.DeltaBloodflies.class)); cards.add(new SetCardInfo("Descendant of Storms", 8, Rarity.UNCOMMON, mage.cards.d.DescendantOfStorms.class)); cards.add(new SetCardInfo("Devoted Duelist", 104, Rarity.COMMON, mage.cards.d.DevotedDuelist.class)); + cards.add(new SetCardInfo("Dirgur Island Dragon", 40, Rarity.COMMON, mage.cards.d.DirgurIslandDragon.class)); cards.add(new SetCardInfo("Dismal Backwater", 254, Rarity.COMMON, mage.cards.d.DismalBackwater.class)); cards.add(new SetCardInfo("Dispelling Exhale", 41, Rarity.COMMON, mage.cards.d.DispellingExhale.class)); cards.add(new SetCardInfo("Dracogenesis", 105, Rarity.MYTHIC, mage.cards.d.Dracogenesis.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/omen/OmenCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/omen/OmenCardsTest.java new file mode 100644 index 00000000000..e6572b126be --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/omen/OmenCardsTest.java @@ -0,0 +1,77 @@ +package org.mage.test.cards.cost.omen; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +public class OmenCardsTest extends CardTestPlayerBase { + + @Test + public void testDirgurIslandDragonShuffle() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.HAND, playerA, "Dirgur Island Dragon"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerB, "Bear Cub"); + addCard(Zone.LIBRARY, playerA, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Skimming Strike", "Bear Cub"); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + assertLibraryCount(playerA, "Dirgur Island Dragon", 1); + assertTapped("Bear Cub", true); + assertHandCount(playerA, 1); + } + + @Test + public void testDirgurIslandDragonShuffleAndPlay() { + setStrictChooseMode(true); + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + addCard(Zone.BATTLEFIELD, playerB, "Bear Cub"); + addCard(Zone.LIBRARY, playerA, "Mountain"); + addCard(Zone.HAND, playerA, "Dirgur Island Dragon"); + + castSpell(2, PhaseStep.BEGIN_COMBAT, playerA, "Skimming Strike", "Bear Cub"); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Dirgur Island Dragon"); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertPermanentCount(playerA, "Dirgur Island Dragon", 1); + } + + @Test + public void testCounteredInGraveyard() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerB,"Island", 4); + addCard(Zone.BATTLEFIELD, playerB,"Bear Cub"); + addCard(Zone.HAND, playerA, "Dirgur Island Dragon"); + addCard(Zone.HAND, playerB, "Counterspell"); + + castSpell(2, PhaseStep.BEGIN_COMBAT, playerA, "Skimming Strike", "Bear Cub"); + castSpell(2, PhaseStep.BEGIN_COMBAT, playerB, "Counterspell", "Skimming Strike", "Skimming Strike", StackClause.WHILE_ON_STACK); + attack(2, playerB, "Bear Cub"); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertGraveyardCount(playerA, "Dirgur Island Dragon", 1); + assertLife(playerA, 20 - 2); + } + + @Test + public void testGraveyardCast() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Kess, Dissident Mage"); + addCard(Zone.GRAVEYARD, playerA, "Dirgur Island Dragon"); + addCard(Zone.BATTLEFIELD, playerB, "Bear Cub"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Skimming Strike", "Bear Cub"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLibraryCount(playerA, "Dirgur Island Dragon", 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java index 2243a693b61..2c5c03ef220 100644 --- a/Mage/src/main/java/mage/abilities/SpellAbility.java +++ b/Mage/src/main/java/mage/abilities/SpellAbility.java @@ -6,8 +6,8 @@ import mage.MageObject; import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.VariableManaCost; import mage.abilities.keyword.FlashAbility; -import mage.cards.AdventureCardSpell; import mage.cards.Card; +import mage.cards.SpellOptionCard; import mage.cards.SplitCard; import mage.constants.*; import mage.game.Game; @@ -99,7 +99,7 @@ public class SpellAbility extends ActivatedAbilityImpl { // forced to cast (can be part id or main id) Set idsToCheck = new HashSet<>(); idsToCheck.add(object.getId()); - if (object instanceof Card && !(object instanceof AdventureCardSpell)) { + if (object instanceof Card && !(object instanceof SpellOptionCard)) { idsToCheck.add(((Card) object).getMainCard().getId()); } for (UUID idToCheck : idsToCheck) { diff --git a/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java b/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java index fe3ca1e23ce..6e0e5b65174 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/IsBeingCastFromHandCondition.java @@ -4,7 +4,7 @@ package mage.abilities.condition.common; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.condition.Condition; -import mage.cards.AdventureCardSpell; +import mage.cards.SpellOptionCard; import mage.cards.Card; import mage.cards.ModalDoubleFacedCardHalf; import mage.cards.SplitCardHalf; @@ -20,7 +20,7 @@ public enum IsBeingCastFromHandCondition implements Condition { @Override public boolean apply(Game game, Ability source) { MageObject object = game.getObject(source); - if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) { + if (object instanceof SplitCardHalf || object instanceof SpellOptionCard || object instanceof ModalDoubleFacedCardHalf) { UUID mainCardId = ((Card) object).getMainCard().getId(); object = game.getObject(mainCardId); } diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index ea47fe4f39e..90e911e06c8 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -549,9 +549,9 @@ public class ContinuousEffects implements Serializable { // rules: // 708.4. In every zone except the stack, the characteristics of a split card are those of its two halves combined. idToCheck = ((SplitCardHalf) objectToCheck).getMainCard().getId(); - } else if (!type.needPlayCardAbility() && objectToCheck instanceof AdventureCardSpell) { - // adventure spell uses alternative characteristics for spell/stack, all other cases must use main card - idToCheck = ((AdventureCardSpell) objectToCheck).getMainCard().getId(); + } else if (!type.needPlayCardAbility() && objectToCheck instanceof CardWithSpellOption) { + // adventure/omen spell uses alternative characteristics for spell/stack, all other cases must use main card + idToCheck = ((CardWithSpellOption) objectToCheck).getMainCard().getId(); } else if (!type.needPlayCardAbility() && objectToCheck instanceof ModalDoubleFacedCardHalf) { // each mdf side uses own characteristics to check for playing, all other cases must use main card // rules: diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java index 907881dea21..706af3cd62a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java @@ -5,7 +5,7 @@ import mage.abilities.MageSingleton; import mage.abilities.effects.AsThoughEffectImpl; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; -import mage.cards.AdventureCardSpell; +import mage.cards.AdventureSpellCard; import mage.cards.Card; import mage.constants.AsThoughEffectType; import mage.constants.Duration; @@ -51,10 +51,10 @@ public class ExileAdventureSpellEffect extends OneShotEffect implements MageSing Spell spell = game.getStack().getSpell(source.getId()); if (spell != null) { Card spellCard = spell.getCard(); - if (spellCard instanceof AdventureCardSpell) { + if (spellCard instanceof AdventureSpellCard) { UUID exileId = adventureExileId(controller.getId(), game); game.getExile().createZone(exileId, "On an Adventure from " + controller.getName()); - AdventureCardSpell adventureSpellCard = (AdventureCardSpell) spellCard; + AdventureSpellCard adventureSpellCard = (AdventureSpellCard) spellCard; Card parentCard = adventureSpellCard.getParentCard(); if (controller.moveCardsToExile(parentCard, source, game, true, exileId, "On an Adventure from " + controller.getName())) { ContinuousEffect effect = new AdventureCastFromExileEffect(); diff --git a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java index 9287ae99694..d094a49f537 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java @@ -257,7 +257,7 @@ public class ForetellAbility extends SpecialAction { game.getState().addOtherAbility(rightHalfCard, ability); } } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { if (foretellCost != null) { Card creatureCard = card.getMainCard(); ForetellCostAbility ability = new ForetellCostAbility(foretellCost); @@ -268,7 +268,7 @@ public class ForetellAbility extends SpecialAction { game.getState().addOtherAbility(creatureCard, ability); } if (foretellSplitCost != null) { - Card spellCard = ((AdventureCard) card).getSpellCard(); + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); ability.setSourceId(spellCard.getId()); ability.setControllerId(source.getControllerId()); @@ -360,11 +360,11 @@ public class ForetellAbility extends SpecialAction { } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { if (card.getMainCard().getName().equals(abilityName)) { return card.getMainCard().getSpellAbility().canActivate(playerId, game); - } else if (((AdventureCard) card).getSpellCard().getName().equals(abilityName)) { - return ((AdventureCard) card).getSpellCard().getSpellAbility().canActivate(playerId, game); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) { + return ((CardWithSpellOption) card).getSpellCard().getSpellAbility().canActivate(playerId, game); } } return card.getSpellAbility().canActivate(playerId, game); @@ -391,11 +391,11 @@ public class ForetellAbility extends SpecialAction { } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy(); } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { if (card.getMainCard().getName().equals(abilityName)) { spellAbilityCopy = card.getMainCard().getSpellAbility().copy(); - } else if (((AdventureCard) card).getSpellCard().getName().equals(abilityName)) { - spellAbilityCopy = ((AdventureCard) card).getSpellCard().getSpellAbility().copy(); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) { + spellAbilityCopy = ((CardWithSpellOption) card).getSpellCard().getSpellAbility().copy(); } } else { spellAbilityCopy = card.getSpellAbility().copy(); diff --git a/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java b/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java index c91a6f2cbbd..93375e704b3 100644 --- a/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/PlotAbility.java @@ -268,11 +268,11 @@ class PlotSpellAbility extends SpellAbility { } else if (((CardWithHalves) mainCard).getRightHalfCard().getName().equals(faceCardName)) { return ((CardWithHalves) mainCard).getRightHalfCard().getSpellAbility().canActivate(playerId, game); } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { if (card.getMainCard().getName().equals(faceCardName)) { return card.getMainCard().getSpellAbility().canActivate(playerId, game); - } else if (((AdventureCard) card).getSpellCard().getName().equals(faceCardName)) { - return ((AdventureCard) card).getSpellCard().getSpellAbility().canActivate(playerId, game); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(faceCardName)) { + return ((CardWithSpellOption) card).getSpellCard().getSpellAbility().canActivate(playerId, game); } } return card.getSpellAbility().canActivate(playerId, game); @@ -294,11 +294,11 @@ class PlotSpellAbility extends SpellAbility { } else if (((CardWithHalves) card).getRightHalfCard().getName().equals(faceCardName)) { spellAbilityCopy = ((CardWithHalves) card).getRightHalfCard().getSpellAbility().copy(); } - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { if (card.getMainCard().getName().equals(faceCardName)) { spellAbilityCopy = card.getMainCard().getSpellAbility().copy(); - } else if (((AdventureCard) card).getSpellCard().getName().equals(faceCardName)) { - spellAbilityCopy = ((AdventureCard) card).getSpellCard().getSpellAbility().copy(); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(faceCardName)) { + spellAbilityCopy = ((CardWithSpellOption) card).getSpellCard().getSpellAbility().copy(); } } else { spellAbilityCopy = card.getSpellAbility().copy(); diff --git a/Mage/src/main/java/mage/cards/AdventureCard.java b/Mage/src/main/java/mage/cards/AdventureCard.java index 413d9d6e026..af6e67ca7c1 100644 --- a/Mage/src/main/java/mage/cards/AdventureCard.java +++ b/Mage/src/main/java/mage/cards/AdventureCard.java @@ -1,98 +1,29 @@ package mage.cards; -import mage.abilities.Abilities; -import mage.abilities.AbilitiesImpl; -import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.constants.CardType; import mage.constants.SpellAbilityType; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.ZoneChangeEvent; -import mage.util.CardUtil; -import java.util.List; import java.util.UUID; /** * @author phulin */ -public abstract class AdventureCard extends CardImpl { - - /* The adventure spell card, i.e. Swift End. */ - protected AdventureCardSpell spellCard; +public abstract class AdventureCard extends CardWithSpellOption { public AdventureCard(UUID ownerId, CardSetInfo setInfo, CardType[] types, CardType[] typesSpell, String costs, String adventureName, String costsSpell) { super(ownerId, setInfo, types, costs); - this.spellCard = new AdventureCardSpellImpl(ownerId, setInfo, adventureName, typesSpell, costsSpell, this); + this.spellCard = new AdventureSpellCard(ownerId, setInfo, adventureName, typesSpell, costsSpell, this); + } + + public AdventureCard(AdventureCard card) { + super(card); } public void finalizeAdventure() { - spellCard.finalizeAdventure(); - } - - protected AdventureCard(final AdventureCard card) { - super(card); - this.spellCard = card.getSpellCard().copy(); - this.spellCard.setParentCard(this); - } - - public AdventureCardSpell getSpellCard() { - return spellCard; - } - - public void setParts(AdventureCardSpell cardSpell) { - // for card copy only - set new parts - this.spellCard = cardSpell; - cardSpell.setParentCard(this); - } - - @Override - public void assignNewId() { - super.assignNewId(); - spellCard.assignNewId(); - } - - @Override - public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { - if (super.moveToZone(toZone, source, game, flag, appliedEffects)) { - Zone currentZone = game.getState().getZone(getId()); - game.getState().setZone(getSpellCard().getId(), currentZone); - return true; - } - return false; - } - - @Override - public void setZone(Zone zone, Game game) { - super.setZone(zone, game); - game.setZone(getSpellCard().getId(), zone); - } - - @Override - public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { - if (super.moveToExile(exileId, name, source, game, appliedEffects)) { - Zone currentZone = game.getState().getZone(getId()); - game.getState().setZone(getSpellCard().getId(), currentZone); - return true; - } - return false; - } - - @Override - public boolean removeFromZone(Game game, Zone fromZone, Ability source) { - // zone contains only one main card - return super.removeFromZone(game, fromZone, source); - } - - @Override - public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { - if (isCopy()) { // same as meld cards - super.updateZoneChangeCounter(game, event); - return; - } - super.updateZoneChangeCounter(game, event); - getSpellCard().updateZoneChangeCounter(game, event); + spellCard.finalizeSpell(); } @Override @@ -103,45 +34,4 @@ public abstract class AdventureCard extends CardImpl { this.getSpellCard().getSpellAbility().setControllerId(controllerId); return super.cast(game, fromZone, ability, controllerId); } - - @Override - public Abilities getAbilities() { - Abilities allAbilities = new AbilitiesImpl<>(); - allAbilities.addAll(spellCard.getAbilities()); - allAbilities.addAll(super.getAbilities()); - return allAbilities; - } - - @Override - public Abilities getInitAbilities() { - // must init only parent related abilities, spell card must be init separately - return super.getAbilities(); - } - - @Override - public Abilities getAbilities(Game game) { - Abilities allAbilities = new AbilitiesImpl<>(); - allAbilities.addAll(spellCard.getAbilities(game)); - allAbilities.addAll(super.getAbilities(game)); - return allAbilities; - } - - public Abilities getSharedAbilities(Game game) { - // abilities without spellcard - return super.getAbilities(game); - } - - public List getSharedRules(Game game) { - // rules without spellcard - Abilities sourceAbilities = this.getSharedAbilities(game); - return CardUtil.getCardRulesWithAdditionalInfo(game, this, sourceAbilities, sourceAbilities); - } - - @Override - public void setOwnerId(UUID ownerId) { - super.setOwnerId(ownerId); - abilities.setControllerId(ownerId); - spellCard.getAbilities().setControllerId(ownerId); - spellCard.setOwnerId(ownerId); - } } diff --git a/Mage/src/main/java/mage/cards/AdventureCardSpell.java b/Mage/src/main/java/mage/cards/AdventureCardSpell.java deleted file mode 100644 index 27f14c5493a..00000000000 --- a/Mage/src/main/java/mage/cards/AdventureCardSpell.java +++ /dev/null @@ -1,12 +0,0 @@ -package mage.cards; - -/** - * @author phulin - */ -public interface AdventureCardSpell extends SubCard { - - @Override - AdventureCardSpell copy(); - - void finalizeAdventure(); -} diff --git a/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java b/Mage/src/main/java/mage/cards/AdventureSpellCard.java similarity index 88% rename from Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java rename to Mage/src/main/java/mage/cards/AdventureSpellCard.java index 2062b27ca9e..ed6efa5f1f1 100644 --- a/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java +++ b/Mage/src/main/java/mage/cards/AdventureSpellCard.java @@ -19,11 +19,11 @@ import java.util.stream.Collectors; /** * @author phulin */ -public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpell { +public class AdventureSpellCard extends CardImpl implements SpellOptionCard { private AdventureCard adventureCardParent; - public AdventureCardSpellImpl(UUID ownerId, CardSetInfo setInfo, String adventureName, CardType[] cardTypes, String costs, AdventureCard adventureCardParent) { + public AdventureSpellCard(UUID ownerId, CardSetInfo setInfo, String adventureName, CardType[] cardTypes, String costs, AdventureCard adventureCardParent) { super(ownerId, setInfo, cardTypes, costs, SpellAbilityType.ADVENTURE_SPELL); this.subtype.add(SubType.ADVENTURE); @@ -35,13 +35,13 @@ public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpe this.adventureCardParent = adventureCardParent; } - public void finalizeAdventure() { + public void finalizeSpell() { if (spellAbility instanceof AdventureCardSpellAbility) { ((AdventureCardSpellAbility) spellAbility).finalizeAdventure(); } } - protected AdventureCardSpellImpl(final AdventureCardSpellImpl card) { + protected AdventureSpellCard(final AdventureSpellCard card) { super(card); this.adventureCardParent = card.adventureCardParent; } @@ -83,13 +83,13 @@ public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpe } @Override - public AdventureCardSpellImpl copy() { - return new AdventureCardSpellImpl(this); + public AdventureSpellCard copy() { + return new AdventureSpellCard(this); } @Override - public void setParentCard(AdventureCard card) { - this.adventureCardParent = card; + public void setParentCard(CardWithSpellOption card) { + this.adventureCardParent = (AdventureCard) card; } @Override @@ -102,6 +102,11 @@ public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpe // id must send to main card (popup card hint in game logs) return getName() + " [" + adventureCardParent.getId().toString().substring(0, 3) + ']'; } + + @Override + public String getSpellType() { + return "Adventure"; + } } class AdventureCardSpellAbility extends SpellAbility { @@ -141,8 +146,8 @@ class AdventureCardSpellAbility extends SpellAbility { public ActivationStatus canActivate(UUID playerId, Game game) { ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(playerId, game)); Card spellCard = game.getCard(this.getSourceId()); - if (spellCard instanceof AdventureCardSpell) { - Card card = ((AdventureCardSpell) spellCard).getParentCard(); + if (spellCard instanceof AdventureSpellCard) { + Card card = ((AdventureSpellCard) spellCard).getParentCard(); if (adventureExileZone != null && adventureExileZone.contains(card.getId())) { return ActivationStatus.getFalse(); } diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index e6c7aa4b9ae..2515d21c1fd 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -530,8 +530,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } } - if (stackObject == null && (this instanceof AdventureCard)) { - stackObject = game.getStack().getSpell(((AdventureCard) this).getSpellCard().getId(), false); + if (stackObject == null && (this instanceof CardWithSpellOption)) { + stackObject = game.getStack().getSpell(((CardWithSpellOption) this).getSpellCard().getId(), false); } if (stackObject == null) { diff --git a/Mage/src/main/java/mage/cards/CardWithSpellOption.java b/Mage/src/main/java/mage/cards/CardWithSpellOption.java new file mode 100644 index 00000000000..b1470f7dedc --- /dev/null +++ b/Mage/src/main/java/mage/cards/CardWithSpellOption.java @@ -0,0 +1,131 @@ +package mage.cards; + +import mage.abilities.Abilities; +import mage.abilities.AbilitiesImpl; +import mage.abilities.Ability; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.ZoneChangeEvent; +import mage.util.CardUtil; + +import java.util.List; +import java.util.UUID; + +/** + * @author phulin, jmlundeen + */ +public abstract class CardWithSpellOption extends CardImpl { + + /* The adventure/omen spell card, i.e. Swift End. */ + protected SpellOptionCard spellCard; + + public CardWithSpellOption(UUID ownerId, CardSetInfo setInfo, CardType[] types, String costs) { + super(ownerId, setInfo, types, costs); + } + + public CardWithSpellOption(CardWithSpellOption card) { + super(card); + this.spellCard = card.getSpellCard().copy(); + this.spellCard.setParentCard(this); + } + + public SpellOptionCard getSpellCard() { + return spellCard; + } + + public void setParts(SpellOptionCard cardSpell) { + // for card copy only - set new parts + this.spellCard = cardSpell; + cardSpell.setParentCard(this); + } + + @Override + public void assignNewId() { + super.assignNewId(); + spellCard.assignNewId(); + } + + @Override + public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { + if (super.moveToZone(toZone, source, game, flag, appliedEffects)) { + Zone currentZone = game.getState().getZone(getId()); + game.getState().setZone(getSpellCard().getId(), currentZone); + return true; + } + return false; + } + + @Override + public void setZone(Zone zone, Game game) { + super.setZone(zone, game); + game.setZone(getSpellCard().getId(), zone); + } + + @Override + public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { + if (super.moveToExile(exileId, name, source, game, appliedEffects)) { + Zone currentZone = game.getState().getZone(getId()); + game.getState().setZone(getSpellCard().getId(), currentZone); + return true; + } + return false; + } + + @Override + public boolean removeFromZone(Game game, Zone fromZone, Ability source) { + // zone contains only one main card + return super.removeFromZone(game, fromZone, source); + } + + @Override + public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { + if (isCopy()) { // same as meld cards + super.updateZoneChangeCounter(game, event); + return; + } + super.updateZoneChangeCounter(game, event); + getSpellCard().updateZoneChangeCounter(game, event); + } + + @Override + public Abilities getAbilities() { + Abilities allAbilities = new AbilitiesImpl<>(); + allAbilities.addAll(spellCard.getAbilities()); + allAbilities.addAll(super.getAbilities()); + return allAbilities; + } + + @Override + public Abilities getInitAbilities() { + // must init only parent related abilities, spell card must be init separately + return super.getAbilities(); + } + + @Override + public Abilities getAbilities(Game game) { + Abilities allAbilities = new AbilitiesImpl<>(); + allAbilities.addAll(spellCard.getAbilities(game)); + allAbilities.addAll(super.getAbilities(game)); + return allAbilities; + } + + public Abilities getSharedAbilities(Game game) { + // abilities without spellCard + return super.getAbilities(game); + } + + public List getSharedRules(Game game) { + // rules without spellCard + Abilities sourceAbilities = this.getSharedAbilities(game); + return CardUtil.getCardRulesWithAdditionalInfo(game, this, sourceAbilities, sourceAbilities); + } + + @Override + public void setOwnerId(UUID ownerId) { + super.setOwnerId(ownerId); + abilities.setControllerId(ownerId); + spellCard.getAbilities().setControllerId(ownerId); + spellCard.setOwnerId(ownerId); + } +} diff --git a/Mage/src/main/java/mage/cards/OmenCard.java b/Mage/src/main/java/mage/cards/OmenCard.java new file mode 100644 index 00000000000..82401304f12 --- /dev/null +++ b/Mage/src/main/java/mage/cards/OmenCard.java @@ -0,0 +1,34 @@ +package mage.cards; + +import mage.abilities.SpellAbility; +import mage.constants.CardType; +import mage.constants.SpellAbilityType; +import mage.constants.Zone; +import mage.game.Game; + +import java.util.UUID; + +public abstract class OmenCard extends CardWithSpellOption { + + public OmenCard(UUID ownerId, CardSetInfo setInfo, CardType[] types, CardType[] typesSpell, String costs, String omenName, String costsSpell) { + super(ownerId, setInfo, types, costs); + this.spellCard = new OmenSpellCard(ownerId, setInfo, omenName, typesSpell, costsSpell, this); + } + + public OmenCard(OmenCard card) { + super(card); + } + + public void finalizeOmen() { + spellCard.finalizeSpell(); + } + + @Override + public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { + if (ability.getSpellAbilityType() == SpellAbilityType.OMEN_SPELL) { + return this.getSpellCard().cast(game, fromZone, ability, controllerId); + } + this.getSpellCard().getSpellAbility().setControllerId(controllerId); + return super.cast(game, fromZone, ability, controllerId); + } +} diff --git a/Mage/src/main/java/mage/cards/OmenSpellCard.java b/Mage/src/main/java/mage/cards/OmenSpellCard.java new file mode 100644 index 00000000000..040bba51217 --- /dev/null +++ b/Mage/src/main/java/mage/cards/OmenSpellCard.java @@ -0,0 +1,174 @@ +package mage.cards; + +import mage.abilities.Ability; +import mage.abilities.Modes; +import mage.abilities.SpellAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ShuffleIntoLibrarySourceEffect; +import mage.constants.CardType; +import mage.constants.SpellAbilityType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class OmenSpellCard extends CardImpl implements SpellOptionCard { + + private OmenCard omenCardParent; + + public OmenSpellCard(UUID ownerId, CardSetInfo setInfo, String omenName, CardType[] cardTypes, String costs, OmenCard omenCard) { + super(ownerId, setInfo, cardTypes, costs, SpellAbilityType.OMEN_SPELL); + this.subtype.add(SubType.OMEN); + + OmenCardSpellAbility newSpellAbility = new OmenCardSpellAbility(getSpellAbility(), omenName, cardTypes, costs); + this.replaceSpellAbility(newSpellAbility); + spellAbility = newSpellAbility; + + this.setName(omenName); + this.omenCardParent = omenCard; + } + + public void finalizeSpell() { + if (spellAbility instanceof OmenCardSpellAbility) { + ((OmenCardSpellAbility) spellAbility).finalizeOmen(); + } + } + + protected OmenSpellCard(final OmenSpellCard card) { + super(card); + this.omenCardParent = card.omenCardParent; + } + + @Override + public UUID getOwnerId() { + return omenCardParent.getOwnerId(); + } + + @Override + public String getExpansionSetCode() { + return omenCardParent.getExpansionSetCode(); + } + + @Override + public String getCardNumber() { + return omenCardParent.getCardNumber(); + } + + @Override + public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { + return omenCardParent.moveToZone(toZone, source, game, flag, appliedEffects); + } + + @Override + public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { + return omenCardParent.moveToExile(exileId, name, source, game, appliedEffects); + } + + @Override + public OmenCard getMainCard() { + return omenCardParent; + } + + @Override + public void setZone(Zone zone, Game game) { + game.setZone(omenCardParent.getId(), zone); + game.setZone(omenCardParent.getSpellCard().getId(), zone); + } + + @Override + public OmenSpellCard copy() { + return new OmenSpellCard(this); + } + + @Override + public void setParentCard(CardWithSpellOption card) { + this.omenCardParent = (OmenCard) card; + } + + @Override + public OmenCard getParentCard() { + return this.omenCardParent; + } + + @Override + public String getIdName() { + // id must send to main card (popup card hint in game logs) + return getName() + " [" + omenCardParent.getId().toString().substring(0, 3) + ']'; + } + + @Override + public String getSpellType() { + return "Omen"; + } +} + +class OmenCardSpellAbility extends SpellAbility { + + private String nameFull; + private boolean finalized = false; + + public OmenCardSpellAbility(final SpellAbility baseSpellAbility, String omenName, CardType[] cardTypes, String costs) { + super(baseSpellAbility); + this.setName(cardTypes, omenName, costs); + this.setCardName(omenName); + } + + public void finalizeOmen() { + if (finalized) { + throw new IllegalStateException("Wrong code usage. " + + "Omen (" + cardName + ") " + + "need to call finalizeOmen() exactly once."); + } + Effect effect = new ShuffleIntoLibrarySourceEffect(); + effect.setText(""); + this.addEffect(effect); + this.finalized = true; + } + + protected OmenCardSpellAbility(final OmenCardSpellAbility ability) { + super(ability); + this.nameFull = ability.nameFull; + if (!ability.finalized) { + throw new IllegalStateException("Wrong code usage. " + + "Omen (" + cardName + ") " + + "need to call finalizeOmen() at the very end of the card's constructor."); + } + this.finalized = true; + } + + public void setName(CardType[] cardTypes, String omenName, String costs) { + this.nameFull = "Omen " + Arrays.stream(cardTypes).map(CardType::toString).collect(Collectors.joining(" ")) + " — " + omenName; + this.name = this.nameFull + " " + costs; + } + + @Override + public String getRule(boolean all) { + return this.getRule(); + } + + @Override + public String getRule() { + StringBuilder sbRule = new StringBuilder(); + sbRule.append(this.nameFull); + sbRule.append(" "); + sbRule.append(getManaCosts().getText()); + sbRule.append(" — "); + Modes modes = this.getModes(); + if (modes.size() <= 1) { + sbRule.append(modes.getMode().getEffects().getTextStartingUpperCase(modes.getMode())); + } else { + sbRule.append(getModes().getText()); + } + sbRule.append(" (Then shuffle this card into its owner's library.)"); + return sbRule.toString(); + } + + @Override + public OmenCardSpellAbility copy() { + return new OmenCardSpellAbility(this); + } +} diff --git a/Mage/src/main/java/mage/cards/SpellOptionCard.java b/Mage/src/main/java/mage/cards/SpellOptionCard.java new file mode 100644 index 00000000000..0007d70fcbc --- /dev/null +++ b/Mage/src/main/java/mage/cards/SpellOptionCard.java @@ -0,0 +1,18 @@ +package mage.cards; + +public interface SpellOptionCard extends SubCard { + + @Override + SpellOptionCard copy(); + + /** + * Adds the final shared ability to the card. e.g. Adventure exile effect / Omen shuffle effect + */ + void finalizeSpell(); + + /** + * Used to get the card type text such as Adventure. Currently only used in {@link mage.game.stack.Spell#getSpellCastText Spell} for logging the spell + * being cast as part of the two part card. + */ + String getSpellType(); +} diff --git a/Mage/src/main/java/mage/cards/mock/MockCard.java b/Mage/src/main/java/mage/cards/mock/MockCard.java index ae406a39dca..6d768ef8b67 100644 --- a/Mage/src/main/java/mage/cards/mock/MockCard.java +++ b/Mage/src/main/java/mage/cards/mock/MockCard.java @@ -20,7 +20,7 @@ import java.util.List; */ public class MockCard extends CardImpl implements MockableCard { - public static String ADVENTURE_NAME_SEPARATOR = " // "; + public static String CARD_WITH_SPELL_OPTION_NAME_SEPARATOR = " // "; public static String MODAL_DOUBLE_FACES_NAME_SEPARATOR = " // "; // Needs to be here, as it is normally calculated from the @@ -34,7 +34,7 @@ public class MockCard extends CardImpl implements MockableCard { protected List manaCostLeftStr; protected List manaCostRightStr; protected List manaCostStr; - protected String adventureSpellName; + protected String spellOptionName; // adventure/omen spell name protected boolean isModalDoubleFacedCard; protected int manaValue; @@ -71,8 +71,8 @@ public class MockCard extends CardImpl implements MockableCard { this.secondSideCard = new MockCard(CardRepository.instance.findCardWithPreferredSetAndNumber(card.getSecondSideName(), card.getSetCode(), card.getCardNumber())); } - if (card.isAdventureCard()) { - this.adventureSpellName = card.getAdventureSpellName(); + if (card.isCardWithSpellOption()) { + this.spellOptionName = card.getSpellOptionCardName(); } if (card.isModalDoubleFacedCard()) { @@ -101,7 +101,7 @@ public class MockCard extends CardImpl implements MockableCard { this.manaCostLeftStr = new ArrayList<>(card.manaCostLeftStr); this.manaCostRightStr = new ArrayList<>(card.manaCostRightStr); this.manaCostStr = new ArrayList<>(card.manaCostStr); - this.adventureSpellName = card.adventureSpellName; + this.spellOptionName = card.spellOptionName; this.isModalDoubleFacedCard = card.isModalDoubleFacedCard; this.manaValue = card.manaValue; } @@ -155,8 +155,8 @@ public class MockCard extends CardImpl implements MockableCard { return getName(); } - if (adventureSpellName != null) { - return getName() + ADVENTURE_NAME_SEPARATOR + adventureSpellName; + if (spellOptionName != null) { + return getName() + CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + spellOptionName; } else if (isModalDoubleFacedCard) { return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + this.getSecondCardFace().getName(); } else { diff --git a/Mage/src/main/java/mage/cards/repository/CardInfo.java b/Mage/src/main/java/mage/cards/repository/CardInfo.java index 7477dc9bee3..d870673e76d 100644 --- a/Mage/src/main/java/mage/cards/repository/CardInfo.java +++ b/Mage/src/main/java/mage/cards/repository/CardInfo.java @@ -106,9 +106,9 @@ public class CardInfo { @DatabaseField protected String secondSideName; @DatabaseField - protected boolean adventureCard; + protected boolean cardWithSpellOption; @DatabaseField - protected String adventureSpellName; + protected String spellOptionCardName; @DatabaseField protected boolean modalDoubleFacedCard; @DatabaseField @@ -157,9 +157,9 @@ public class CardInfo { this.secondSideName = secondSide.getName(); } - if (card instanceof AdventureCard) { - this.adventureCard = true; - this.adventureSpellName = ((AdventureCard) card).getSpellCard().getName(); + if (card instanceof CardWithSpellOption) { + this.cardWithSpellOption = true; + this.spellOptionCardName = ((CardWithSpellOption) card).getSpellCard().getName(); } if (card instanceof ModalDoubleFacedCard) { @@ -189,8 +189,8 @@ public class CardInfo { List manaCostLeft = ((ModalDoubleFacedCard) card).getLeftHalfCard().getManaCostSymbols(); List manaCostRight = ((ModalDoubleFacedCard) card).getRightHalfCard().getManaCostSymbols(); this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight)); - } else if (card instanceof AdventureCard) { - List manaCostLeft = ((AdventureCard) card).getSpellCard().getManaCostSymbols(); + } else if (card instanceof CardWithSpellOption) { + List manaCostLeft = ((CardWithSpellOption) card).getSpellCard().getManaCostSymbols(); List manaCostRight = card.getManaCostSymbols(); this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight)); } else { @@ -469,12 +469,16 @@ public class CardInfo { return secondSideName; } - public boolean isAdventureCard() { - return adventureCard; + public boolean isCardWithSpellOption() { + return cardWithSpellOption; } - public String getAdventureSpellName() { - return adventureSpellName; + /** + * used for spell card portion of adventure/omen cards + * @return name of the spell + */ + public String getSpellOptionCardName() { + return spellOptionCardName; } public boolean isModalDoubleFacedCard() { diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 24484308724..88cb2fbadda 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -147,8 +147,8 @@ public enum CardRepository { if (card.getMeldsToCardName() != null && !card.getMeldsToCardName().isEmpty()) { namesList.add(card.getMeldsToCardName()); } - if (card.getAdventureSpellName() != null && !card.getAdventureSpellName().isEmpty()) { - namesList.add(card.getAdventureSpellName()); + if (card.getSpellOptionCardName() != null && !card.getSpellOptionCardName().isEmpty()) { + namesList.add(card.getSpellOptionCardName()); } } @@ -160,7 +160,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { addNewNames(card, names); @@ -176,7 +176,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().not().like("types", new SelectArg('%' + CardType.LAND.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -193,7 +193,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); Where where = qb.where(); where.and( where.not().like("supertypes", '%' + SuperType.BASIC.name() + '%'), @@ -214,7 +214,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().not().like("supertypes", new SelectArg('%' + SuperType.BASIC.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -231,7 +231,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().like("types", new SelectArg('%' + CardType.CREATURE.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -248,7 +248,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); qb.where().like("types", new SelectArg('%' + CardType.ARTIFACT.name() + '%')); List results = cardsDao.query(qb.prepare()); for (CardInfo card : results) { @@ -265,7 +265,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); Where where = qb.where(); where.and( where.not().like("types", '%' + CardType.CREATURE.name() + '%'), @@ -286,7 +286,7 @@ public enum CardRepository { Set names = new TreeSet<>(); try { QueryBuilder qb = cardsDao.queryBuilder(); - qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "adventureSpellName"); + qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName"); Where where = qb.where(); where.and( where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'), @@ -511,7 +511,7 @@ public enum CardRepository { queryBuilder.where() .eq("flipCardName", new SelectArg(name)).or() .eq("secondSideName", new SelectArg(name)).or() - .eq("adventureSpellName", new SelectArg(name)).or() + .eq("spellOptionCardName", new SelectArg(name)).or() .eq("modalDoubleFacedSecondSideName", new SelectArg(name)); results = cardsDao.query(queryBuilder.prepare()); } else { diff --git a/Mage/src/main/java/mage/constants/SpellAbilityType.java b/Mage/src/main/java/mage/constants/SpellAbilityType.java index cb95677e060..fac7a218aef 100644 --- a/Mage/src/main/java/mage/constants/SpellAbilityType.java +++ b/Mage/src/main/java/mage/constants/SpellAbilityType.java @@ -15,7 +15,8 @@ public enum SpellAbilityType { MODAL_LEFT("LeftModal SpellAbility"), MODAL_RIGHT("RightModal SpellAbility"), SPLICE("Spliced SpellAbility"), - ADVENTURE_SPELL("Adventure SpellAbility"); + ADVENTURE_SPELL("Adventure SpellAbility"), + OMEN_SPELL("Omen SpellAbility"); private final String text; diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index 0fcfafaa0c4..cbf9f006c19 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -13,6 +13,7 @@ public enum SubType { ADVENTURE("Adventure", SubTypeSet.SpellType), ARCANE("Arcane", SubTypeSet.SpellType), LESSON("Lesson", SubTypeSet.SpellType), + OMEN("Omen", SubTypeSet.SpellType), TRAP("Trap", SubTypeSet.SpellType), // Battle subtypes diff --git a/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java b/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java index 7f412b0e45f..dc15882ba86 100644 --- a/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/card/CardTextPredicate.java @@ -1,9 +1,6 @@ package mage.filter.predicate.card; -import mage.cards.AdventureCard; -import mage.cards.Card; -import mage.cards.ModalDoubleFacedCard; -import mage.cards.SplitCard; +import mage.cards.*; import mage.cards.mock.MockCard; import mage.constants.SubType; import mage.constants.SuperType; @@ -55,8 +52,8 @@ public class CardTextPredicate implements Predicate { fullName = ((MockCard) input).getFullName(true); } else if (input instanceof ModalDoubleFacedCard) { fullName = input.getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + ((ModalDoubleFacedCard) input).getRightHalfCard().getName(); - } else if (input instanceof AdventureCard) { - fullName = input.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + ((AdventureCard) input).getSpellCard().getName(); + } else if (input instanceof CardWithSpellOption) { + fullName = input.getName() + MockCard.CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + ((CardWithSpellOption) input).getSpellCard().getName(); } if (fullName.toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH))) { @@ -107,8 +104,8 @@ public class CardTextPredicate implements Predicate { } } - if (input instanceof AdventureCard) { - for (String rule : ((AdventureCard) input).getSpellCard().getRules(game)) { + if (input instanceof CardWithSpellOption) { + for (String rule : ((CardWithSpellOption) input).getSpellCard().getRules(game)) { if (rule.toLowerCase(Locale.ENGLISH).contains(token)) { found = true; break; diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 17238d3c262..7ff0e1610a7 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -334,8 +334,8 @@ public abstract class GameImpl implements Game { Card rightCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); rightCard.setOwnerId(ownerId); addCardToState(rightCard); - } else if (card instanceof AdventureCard) { - Card spellCard = ((AdventureCard) card).getSpellCard(); + } else if (card instanceof CardWithSpellOption) { + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); spellCard.setOwnerId(ownerId); addCardToState(spellCard); } else if (card.isTransformable() && card.getSecondCardFace() != null) { diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index e0c3d41d452..3f13573471d 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -1639,14 +1639,14 @@ public class GameState implements Serializable, Copyable { copiedParts.add(rightCopied); // sync parts ((ModalDoubleFacedCard) copiedCard).setParts(leftCopied, rightCopied); - } else if (copiedCard instanceof AdventureCard) { + } else if (copiedCard instanceof CardWithSpellOption) { // right - AdventureCardSpell rightOriginal = ((AdventureCard) copiedCard).getSpellCard(); - AdventureCardSpell rightCopied = rightOriginal.copy(); + SpellOptionCard rightOriginal = ((CardWithSpellOption) copiedCard).getSpellCard(); + SpellOptionCard rightCopied = rightOriginal.copy(); prepareCardForCopy(rightOriginal, rightCopied, newController); copiedParts.add(rightCopied); // sync parts - ((AdventureCard) copiedCard).setParts(rightCopied); + ((CardWithSpellOption) copiedCard).setParts(rightCopied); } // main part prepare (must be called after other parts cause it change ids for all) diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index c234e901643..149d8a6e92b 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -209,10 +209,11 @@ public class Spell extends StackObjectImpl implements Card { + " using " + this.getSpellAbility().getSpellAbilityCastMode(); } - if (card instanceof AdventureCardSpell) { - AdventureCard adventureCard = ((AdventureCardSpell) card).getParentCard(); - return GameLog.replaceNameByColoredName(card, getSpellAbility().toString(), adventureCard) - + " as Adventure spell of " + GameLog.getColoredObjectIdName(adventureCard); + if (card instanceof SpellOptionCard) { + CardWithSpellOption parentCard = ((SpellOptionCard) card).getParentCard(); + String type = ((SpellOptionCard) card).getSpellType(); + return GameLog.replaceNameByColoredName(card, getSpellAbility().toString(), parentCard) + + " as " + type + " spell of " + GameLog.getColoredObjectIdName(parentCard); } if (card instanceof ModalDoubleFacedCardHalf) { @@ -539,8 +540,8 @@ public class Spell extends StackObjectImpl implements Card { public String getIdName() { String idName; if (card != null) { - if (card instanceof AdventureCardSpell) { - idName = ((AdventureCardSpell) card).getParentCard().getId().toString().substring(0, 3); + if (card instanceof SpellOptionCard) { + idName = ((SpellOptionCard) card).getParentCard().getId().toString().substring(0, 3); } else { idName = card.getId().toString().substring(0, 3); } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index a6f3ac1dd88..0132cc7810c 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -4068,11 +4068,11 @@ public abstract class PlayerImpl implements Player, Serializable { getPlayableFromObjectSingle(game, fromZone, mainCard.getLeftHalfCard(), mainCard.getLeftHalfCard().getAbilities(game), availableMana, output); getPlayableFromObjectSingle(game, fromZone, mainCard.getRightHalfCard(), mainCard.getRightHalfCard().getAbilities(game), availableMana, output); getPlayableFromObjectSingle(game, fromZone, mainCard, mainCard.getSharedAbilities(game), availableMana, output); - } else if (object instanceof AdventureCard) { + } else if (object instanceof CardWithSpellOption) { // adventure must use different card characteristics for different spells (main or adventure) - AdventureCard adventureCard = (AdventureCard) object; - getPlayableFromObjectSingle(game, fromZone, adventureCard.getSpellCard(), adventureCard.getSpellCard().getAbilities(game), availableMana, output); - getPlayableFromObjectSingle(game, fromZone, adventureCard, adventureCard.getSharedAbilities(game), availableMana, output); + CardWithSpellOption cardWithSpellOption = (CardWithSpellOption) object; + getPlayableFromObjectSingle(game, fromZone, cardWithSpellOption.getSpellCard(), cardWithSpellOption.getSpellCard().getAbilities(game), availableMana, output); + getPlayableFromObjectSingle(game, fromZone, cardWithSpellOption, cardWithSpellOption.getSharedAbilities(game), availableMana, output); } else if (object instanceof Card) { getPlayableFromObjectSingle(game, fromZone, object, ((Card) object).getAbilities(game), availableMana, output); } else if (object instanceof StackObject) { diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index b55d2a33da6..ee24d673186 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1272,7 +1272,7 @@ public final class CardUtil { Card permCard; if (card instanceof SplitCard) { permCard = card; - } else if (card instanceof AdventureCard) { + } else if (card instanceof CardWithSpellOption) { permCard = card; } else if (card instanceof ModalDoubleFacedCard) { permCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); @@ -1460,9 +1460,9 @@ public final class CardUtil { if (cardToCast instanceof CardWithHalves) { cards.add(((CardWithHalves) cardToCast).getLeftHalfCard()); cards.add(((CardWithHalves) cardToCast).getRightHalfCard()); - } else if (cardToCast instanceof AdventureCard) { + } else if (cardToCast instanceof CardWithSpellOption) { cards.add(cardToCast); - cards.add(((AdventureCard) cardToCast).getSpellCard()); + cards.add(((CardWithSpellOption) cardToCast).getSpellCard()); } else { cards.add(cardToCast); } @@ -1651,9 +1651,9 @@ public final class CardUtil { } // handle adventure cards - if (card instanceof AdventureCard) { + if (card instanceof CardWithSpellOption) { Card creatureCard = card.getMainCard(); - Card spellCard = ((AdventureCard) card).getSpellCard(); + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); if (manaCost != null) { // get additional cost if any Costs additionalCostsCreature = creatureCard.getSpellAbility().getCosts(); @@ -1691,9 +1691,9 @@ public final class CardUtil { game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null); game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null); } - if (card instanceof AdventureCard) { + if (card instanceof CardWithSpellOption) { Card creatureCard = card.getMainCard(); - Card spellCard = ((AdventureCard) card).getSpellCard(); + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); game.getState().setValue("PlayFromNotOwnHandZone" + creatureCard.getId(), null); game.getState().setValue("PlayFromNotOwnHandZone" + spellCard.getId(), null); } @@ -2078,8 +2078,8 @@ public final class CardUtil { res.add(mainCard); res.add(mainCard.getLeftHalfCard()); res.add(mainCard.getRightHalfCard()); - } else if (object instanceof AdventureCard || object instanceof AdventureCardSpell) { - AdventureCard mainCard = (AdventureCard) ((Card) object).getMainCard(); + } else if (object instanceof CardWithSpellOption || object instanceof SpellOptionCard) { + CardWithSpellOption mainCard = (CardWithSpellOption) ((Card) object).getMainCard(); res.add(mainCard); res.add(mainCard.getSpellCard()); } else if (object instanceof Spell) { diff --git a/Mage/src/main/java/mage/util/ManaUtil.java b/Mage/src/main/java/mage/util/ManaUtil.java index 08b478d6e23..816353e13e9 100644 --- a/Mage/src/main/java/mage/util/ManaUtil.java +++ b/Mage/src/main/java/mage/util/ManaUtil.java @@ -13,10 +13,7 @@ import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.Effect; import mage.abilities.mana.*; -import mage.cards.AdventureCard; -import mage.cards.Card; -import mage.cards.ModalDoubleFacedCard; -import mage.cards.SplitCard; +import mage.cards.*; import mage.choices.Choice; import mage.constants.ColoredManaSymbol; import mage.constants.ManaType; @@ -644,8 +641,8 @@ public final class ManaUtil { Card secondSide; if (card instanceof SplitCard) { secondSide = ((SplitCard) card).getRightHalfCard(); - } else if (card instanceof AdventureCard) { - secondSide = ((AdventureCard) card).getSpellCard(); + } else if (card instanceof CardWithSpellOption) { + secondSide = ((CardWithSpellOption) card).getSpellCard(); } else if (card instanceof ModalDoubleFacedCard) { secondSide = ((ModalDoubleFacedCard) card).getRightHalfCard(); } else { From 235e5200d09fa605969f0a9d8fe22e24ae4d20b9 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Tue, 8 Apr 2025 08:44:31 -0400 Subject: [PATCH 58/59] [TDM] Implement Warden of the Grove --- .../src/mage/cards/w/WardenOfTheGrove.java | 91 +++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + .../effects/keyword/EndureSourceEffect.java | 15 ++- 3 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/w/WardenOfTheGrove.java diff --git a/Mage.Sets/src/mage/cards/w/WardenOfTheGrove.java b/Mage.Sets/src/mage/cards/w/WardenOfTheGrove.java new file mode 100644 index 00000000000..e612b21bb18 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WardenOfTheGrove.java @@ -0,0 +1,91 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.keyword.EndureSourceEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.counters.Counters; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WardenOfTheGrove extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("another nontoken creature you control"); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TokenPredicate.FALSE); + } + + public WardenOfTheGrove(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.HYDRA); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // At the beginning of your end step, put a +1/+1 counter on this creature. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()))); + + // Whenever another nontoken creature you control enters, it endures X, where X is the number of counters on this creature. + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + Zone.BATTLEFIELD, new WardenOfTheGroveEffect(), + filter, false, SetTargetPointer.PERMANENT + )); + } + + private WardenOfTheGrove(final WardenOfTheGrove card) { + super(card); + } + + @Override + public WardenOfTheGrove copy() { + return new WardenOfTheGrove(this); + } +} + +class WardenOfTheGroveEffect extends OneShotEffect { + + WardenOfTheGroveEffect() { + super(Outcome.Benefit); + staticText = "it endures X, where X is the number of counters on {this}"; + } + + private WardenOfTheGroveEffect(final WardenOfTheGroveEffect effect) { + super(effect); + } + + @Override + public WardenOfTheGroveEffect copy() { + return new WardenOfTheGroveEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return EndureSourceEffect.doEndure( + this.getTargetPointer().getFirstTargetPermanentOrLKI(game, source), + Optional.ofNullable(source.getSourcePermanentOrLKI(game)) + .map(p -> p.getCounters(game)) + .map(Counters::getTotalCount) + .orElse(0), + game, source + ); + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index 9f498363e61..a581a542f55 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -247,6 +247,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Voice of Victory", 33, Rarity.RARE, mage.cards.v.VoiceOfVictory.class)); cards.add(new SetCardInfo("War Effort", 131, Rarity.UNCOMMON, mage.cards.w.WarEffort.class)); cards.add(new SetCardInfo("Wail of War", 98, Rarity.UNCOMMON, mage.cards.w.WailOfWar.class)); + cards.add(new SetCardInfo("Warden of the Grove", 166, Rarity.RARE, mage.cards.w.WardenOfTheGrove.class)); cards.add(new SetCardInfo("Watcher of the Wayside", 249, Rarity.COMMON, mage.cards.w.WatcherOfTheWayside.class)); cards.add(new SetCardInfo("Wayspeaker Bodyguard", 34, Rarity.UNCOMMON, mage.cards.w.WayspeakerBodyguard.class)); cards.add(new SetCardInfo("Wild Ride", 132, Rarity.COMMON, mage.cards.w.WildRide.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/EndureSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/EndureSourceEffect.java index d6a8b4e9fea..9be3c36936d 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/EndureSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/EndureSourceEffect.java @@ -39,12 +39,19 @@ public class EndureSourceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { + return doEndure(source.getSourcePermanentOrLKI(game), 1, game, source); + } + + public static boolean doEndure(Permanent permanent, int amount, Game game, Ability source) { + if (permanent == null || amount < 1) { return false; } - Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent != null && player.chooseUse( + Player controller = game.getPlayer(permanent.getControllerId()); + if (controller == null) { + return false; + } + if (permanent.getZoneChangeCounter(game) == game.getState().getZoneChangeCounter(permanent.getId()) + && controller.chooseUse( Outcome.BoostCreature, "Put " + CardUtil.numberToText(amount, "a") + " +1/+1 counter" + (amount > 1 ? "s" : "") + " on " + permanent.getName() + " or create " + CardUtil.addArticle("" + amount) + ' ' + amount + '/' + amount + " Spirit token?", From 2e6e3cd4e7016397b93f7d5096e481b5148fd4ef Mon Sep 17 00:00:00 2001 From: theelk801 Date: Tue, 8 Apr 2025 09:16:50 -0400 Subject: [PATCH 59/59] [TDM] Implement Mardu Siegebreaker --- .../src/mage/cards/m/MarduSiegebreaker.java | 136 ++++++++++++++++++ .../src/mage/sets/TarkirDragonstorm.java | 1 + .../target/targetpointer/FixedTargets.java | 2 +- 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java diff --git a/Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java b/Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java new file mode 100644 index 00000000000..ead29290170 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MarduSiegebreaker.java @@ -0,0 +1,136 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.GameState; +import mage.game.permanent.token.Token; +import mage.target.TargetCard; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInExile; +import mage.target.targetpointer.FixedTargets; +import mage.util.CardUtil; +import mage.util.functions.CopyTokenFunction; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MarduSiegebreaker extends CardImpl { + + public MarduSiegebreaker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}{B}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When this creature enters, exile up to one other target creature you control until this creature leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetPermanent(0, 1, StaticFilters.FILTER_OTHER_CONTROLLED_CREATURE)); + this.addAbility(ability); + + // Whenever this creature attacks, for each opponent, create a tapped token that's a copy of the exiled card attacking that opponent. At the beginning of your end step, sacrifice those tokens. + this.addAbility(new AttacksTriggeredAbility(new MarduSiegebreakerEffect())); + } + + private MarduSiegebreaker(final MarduSiegebreaker card) { + super(card); + } + + @Override + public MarduSiegebreaker copy() { + return new MarduSiegebreaker(this); + } +} + +class MarduSiegebreakerEffect extends OneShotEffect { + + MarduSiegebreakerEffect() { + super(Outcome.Benefit); + staticText = "for each opponent, create a tapped token that's a copy of the exiled card " + + "attacking that opponent. At the beginning of your end step, sacrifice those tokens"; + } + + private MarduSiegebreakerEffect(final MarduSiegebreakerEffect effect) { + super(effect); + } + + @Override + public MarduSiegebreakerEffect copy() { + return new MarduSiegebreakerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Cards cards = Optional + .ofNullable(game) + .map(Game::getState) + .map(GameState::getExile) + .map(exile -> exile.getExileZone(CardUtil.getExileZoneId(game, source))) + .orElse(null); + if (cards == null) { + return false; + } + Card card; + switch (cards.size()) { + case 0: + return false; + case 1: + card = cards.getRandom(game); + break; + default: + card = Optional.ofNullable(game.getPlayer(source.getControllerId())).map(player -> { + TargetCard target = new TargetCardInExile(StaticFilters.FILTER_CARD); + target.withNotTarget(true); + target.withChooseHint("to copy"); + player.choose(Outcome.Neutral, cards, target, source, game); + return game.getCard(target.getFirstTarget()); + }).orElse(null); + } + if (card == null) { + return false; + } + Set addedTokens = new HashSet<>(); + Token token = CopyTokenFunction.createTokenCopy(card, game); + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + token.putOntoBattlefield(1, game, source, source.getControllerId(), true, true, opponentId); + token.getLastAddedTokenIds() + .stream() + .map(uuid -> new MageObjectReference(uuid, game)) + .forEach(addedTokens::add); + } + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new SacrificeTargetEffect().setTargetPointer(new FixedTargets(addedTokens)) + ), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java index a581a542f55..1354ac5ef04 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstorm.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstorm.java @@ -143,6 +143,7 @@ public final class TarkirDragonstorm extends ExpansionSet { cards.add(new SetCardInfo("Mammoth Bellow", 205, Rarity.UNCOMMON, mage.cards.m.MammothBellow.class)); cards.add(new SetCardInfo("Mardu Devotee", 16, Rarity.COMMON, mage.cards.m.MarduDevotee.class)); cards.add(new SetCardInfo("Mardu Monument", 245, Rarity.UNCOMMON, mage.cards.m.MarduMonument.class)); + cards.add(new SetCardInfo("Mardu Siegebreaker", 206, Rarity.RARE, mage.cards.m.MarduSiegebreaker.class)); cards.add(new SetCardInfo("Marshal of the Lost", 207, Rarity.UNCOMMON, mage.cards.m.MarshalOfTheLost.class)); cards.add(new SetCardInfo("Meticulous Artisan", 112, Rarity.COMMON, mage.cards.m.MeticulousArtisan.class)); cards.add(new SetCardInfo("Molten Exhale", 113, Rarity.COMMON, mage.cards.m.MoltenExhale.class)); diff --git a/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java b/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java index af3d80dbb31..1eb632fd1f6 100644 --- a/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java +++ b/Mage/src/main/java/mage/target/targetpointer/FixedTargets.java @@ -50,7 +50,7 @@ public class FixedTargets extends TargetPointerImpl { .collect(Collectors.toList()), game); } - public FixedTargets(List morList) { + public FixedTargets(Collection morList) { super(); targets.addAll(morList); this.setInitialized(); // no need dynamic init