From 84ff720d2524232c6194bef6594bc35f3f339a22 Mon Sep 17 00:00:00 2001 From: xenohedron <12538125+xenohedron@users.noreply.github.com> Date: Mon, 3 Mar 2025 23:52:53 -0500 Subject: [PATCH 1/9] fix text: Poultice Sliver --- Mage.Sets/src/mage/cards/p/PoulticeSliver.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/cards/p/PoulticeSliver.java b/Mage.Sets/src/mage/cards/p/PoulticeSliver.java index eca6543bcd4..402d738669e 100644 --- a/Mage.Sets/src/mage/cards/p/PoulticeSliver.java +++ b/Mage.Sets/src/mage/cards/p/PoulticeSliver.java @@ -1,7 +1,5 @@ - package mage.cards.p; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -13,18 +11,22 @@ import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; import mage.constants.Duration; -import mage.constants.Zone; +import mage.constants.SubType; +import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.target.TargetPermanent; +import java.util.UUID; + /** * * @author anonymous */ public final class PoulticeSliver extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent(SubType.SLIVER, "Sliver"); + public PoulticeSliver(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); this.subtype.add(SubType.SLIVER); @@ -35,7 +37,7 @@ public final class PoulticeSliver extends CardImpl { // All Slivers have "{2}, {tap}: Regenerate target Sliver." Ability ability = new SimpleActivatedAbility(new RegenerateTargetEffect(), new GenericManaCost(2)); ability.addCost(new TapSourceCost()); - ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_ALL_SLIVERS)); + ability.addTarget(new TargetPermanent(filter)); this.addAbility(new SimpleStaticAbility( new GainAbilityAllEffect(ability, -- 2.47.2 From 98897722bbc5813548e5546be88af6f38e1c7682 Mon Sep 17 00:00:00 2001 From: Jmlundeen <98545818+Jmlundeen@users.noreply.github.com> Date: Fri, 7 Mar 2025 00:41:51 -0600 Subject: [PATCH 2/9] [DFT] Implement Simple Cards (#13406) * [DFT] Implement Fang-Druid Summoner * [DFT] Implement March of the World Ooze * [DFT] Implement Riptide Gearhulk * [DFT] Implement Spectacular Pileup * [DFT] Implement Spire Mechcycle * fix Spire Mechcyle permanent filter * [DFT] Implement Thunderous Velocipede * [DFT] Implement Winter, Cursed Rider * [DFT] Implement Explosive Getaway * Missed PayLifeCost * Move "for each opponent" text --- .../src/mage/cards/e/ExplosiveGetaway.java | 51 ++++++++++++ .../src/mage/cards/f/FangDruidSummoner.java | 51 ++++++++++++ .../src/mage/cards/m/MarchOfTheWorldOoze.java | 58 +++++++++++++ .../src/mage/cards/r/RiptideGearhulk.java | 55 +++++++++++++ .../src/mage/cards/s/SpectacularPileup.java | 55 +++++++++++++ .../src/mage/cards/s/SpireMechcycle.java | 81 +++++++++++++++++++ .../mage/cards/t/ThunderousVelocipede.java | 71 ++++++++++++++++ .../src/mage/cards/w/WinterCursedRider.java | 76 +++++++++++++++++ Mage.Sets/src/mage/sets/Aetherdrift.java | 29 +++++++ 9 files changed, 527 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/ExplosiveGetaway.java create mode 100644 Mage.Sets/src/mage/cards/f/FangDruidSummoner.java create mode 100644 Mage.Sets/src/mage/cards/m/MarchOfTheWorldOoze.java create mode 100644 Mage.Sets/src/mage/cards/r/RiptideGearhulk.java create mode 100644 Mage.Sets/src/mage/cards/s/SpectacularPileup.java create mode 100644 Mage.Sets/src/mage/cards/s/SpireMechcycle.java create mode 100644 Mage.Sets/src/mage/cards/t/ThunderousVelocipede.java create mode 100644 Mage.Sets/src/mage/cards/w/WinterCursedRider.java diff --git a/Mage.Sets/src/mage/cards/e/ExplosiveGetaway.java b/Mage.Sets/src/mage/cards/e/ExplosiveGetaway.java new file mode 100644 index 00000000000..5410a4bcbc1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ExplosiveGetaway.java @@ -0,0 +1,51 @@ +package mage.cards.e; + +import java.util.UUID; +import java.util.function.Predicate; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.common.DamageAllEffect; +import mage.abilities.effects.common.ExileReturnBattlefieldNextEndStepTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +/** + * + * @author Jmlundeen + */ +public final class ExplosiveGetaway extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent("artifact or creature"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate() + )); + } + + public ExplosiveGetaway(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}{W}"); + + + // Exile up to one target artifact or creature. Return it to the battlefield under its owner's control at the beginning of the next end step. + this.getSpellAbility().addEffect(new ExileReturnBattlefieldNextEndStepTargetEffect().withTextThatCard(false)); + this.getSpellAbility().addTarget(new TargetPermanent(0, 1, filter)); + // Explosive Getaway deals 4 damage to each creature. + this.getSpellAbility().addEffect(new DamageAllEffect(4, StaticFilters.FILTER_PERMANENT_ALL_CREATURES).concatBy("
")); + } + + private ExplosiveGetaway(final ExplosiveGetaway card) { + super(card); + } + + @Override + public ExplosiveGetaway copy() { + return new ExplosiveGetaway(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FangDruidSummoner.java b/Mage.Sets/src/mage/cards/f/FangDruidSummoner.java new file mode 100644 index 00000000000..5aec37a1669 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FangDruidSummoner.java @@ -0,0 +1,51 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.search.SearchLibraryGraveyardPutInHandEffect; +import mage.constants.SubType; +import mage.abilities.keyword.ReachAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.NoAbilityPredicate; + +/** + * + * @author Jmlundeen + */ +public final class FangDruidSummoner extends CardImpl { + private static final FilterCreatureCard filter = new FilterCreatureCard("a creature card with no abilities"); + + static { + filter.add(NoAbilityPredicate.instance); + } + public FangDruidSummoner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.APE); + this.subtype.add(SubType.DRUID); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // When this creature enters, you may search your library and/or graveyard for a creature card with no abilities, reveal it, and put it into your hand. If you search your library this way, shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryGraveyardPutInHandEffect(filter, false, true) + )); + } + + private FangDruidSummoner(final FangDruidSummoner card) { + super(card); + } + + @Override + public FangDruidSummoner copy() { + return new FangDruidSummoner(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MarchOfTheWorldOoze.java b/Mage.Sets/src/mage/cards/m/MarchOfTheWorldOoze.java new file mode 100644 index 00000000000..7cb1ae1f446 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MarchOfTheWorldOoze.java @@ -0,0 +1,58 @@ +package mage.cards.m; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastOpponentNoManaSpentTriggeredAbility; +import mage.abilities.common.SpellCastOpponentTriggeredAbility; +import mage.abilities.condition.common.OnOpponentsTurnCondition; +import mage.abilities.effects.common.CastSourceTriggeredAbility; +import mage.abilities.effects.common.CreateTokenAllEffect; +import mage.abilities.effects.common.CreateTokenControllerTargetEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.continuous.AddCardSubtypeAllEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterSpell; +import mage.filter.StaticFilters; +import mage.game.permanent.token.ElephantToken; + +/** + * + * @author Jmlundeen + */ +public final class MarchOfTheWorldOoze extends CardImpl { + public static final FilterSpell filter = new FilterSpell("a spell, if it's not their turn"); + + static { + filter.add(TargetController.INACTIVE.getControllerPredicate()); + } + public MarchOfTheWorldOoze(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}{G}{G}"); + + + // Creatures you control have base power and toughness 6/6 and are Oozes in addition to their other types. + Ability ability = new SimpleStaticAbility(new SetBasePowerToughnessAllEffect( + 6, 6, Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_CREATURE)); + ability.addEffect(new AddCardSubtypeAllEffect(StaticFilters.FILTER_CONTROLLED_CREATURE, SubType.OOZE, null) + .concatBy("and")); + this.addAbility(ability); + // Whenever an opponent casts a spell, if it's not their turn, you create a 3/3 green Elephant creature token. + Ability ability2 = new SpellCastOpponentTriggeredAbility(new CreateTokenEffect(new ElephantToken()) + .setText("you create a 3/3 green Elephant creature token"), + filter, false); + this.addAbility(ability2); + } + + private MarchOfTheWorldOoze(final MarchOfTheWorldOoze card) { + super(card); + } + + @Override + public MarchOfTheWorldOoze copy() { + return new MarchOfTheWorldOoze(this); + } +} diff --git a/Mage.Sets/src/mage/cards/r/RiptideGearhulk.java b/Mage.Sets/src/mage/cards/r/RiptideGearhulk.java new file mode 100644 index 00000000000..cdcf4c2a575 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RiptideGearhulk.java @@ -0,0 +1,55 @@ +package mage.cards.r; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.PutIntoLibraryNFromTopTargetEffect; +import mage.constants.SubType; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.ProwessAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.common.TargetNonlandPermanent; +import mage.target.targetadjustment.ForEachOpponentTargetsAdjuster; +import mage.target.targetpointer.EachTargetPointer; + +/** + * + * @author Jmlundeen + */ +public final class RiptideGearhulk extends CardImpl { + public RiptideGearhulk(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{W}{W}{U}{U}"); + + this.subtype.add(SubType.CONSTRUCT); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // Prowess + this.addAbility(new ProwessAbility()); + + // When this creature enters, for each opponent, put up to one target nonland permanent that player controls into its owner's library third from the top. + Effect effect = new PutIntoLibraryNFromTopTargetEffect(3) + .setText("for each opponent, put up to one target nonland permanent that player controls into its owner's library third from the top") + .setTargetPointer(new EachTargetPointer()); + Ability ability = new EntersBattlefieldTriggeredAbility(effect); + ability.addTarget(new TargetNonlandPermanent(0, 1)); + ability.setTargetAdjuster(new ForEachOpponentTargetsAdjuster()); + this.addAbility(ability); + } + + private RiptideGearhulk(final RiptideGearhulk card) { + super(card); + } + + @Override + public RiptideGearhulk copy() { + return new RiptideGearhulk(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpectacularPileup.java b/Mage.Sets/src/mage/cards/s/SpectacularPileup.java new file mode 100644 index 00000000000..133b833404e --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpectacularPileup.java @@ -0,0 +1,55 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.continuous.LoseAbilityAllEffect; +import mage.abilities.keyword.CyclingAbility; +import mage.abilities.keyword.IndestructibleAbility; +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.predicate.Predicates; + +/** + * + * @author Jmlundeen + */ +public final class SpectacularPileup extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("creatures and Vehicles"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.VEHICLE.getPredicate() + )); + } + + public SpectacularPileup(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}{W}"); + + + // All creatures and Vehicles lose indestructible until end of turn, then destroy all creatures and Vehicles. + this.getSpellAbility().addEffect(new LoseAbilityAllEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn, filter) + .setText("All creatures and Vehicles lose indestructible until end of turn") + ); + this.getSpellAbility().addEffect(new DestroyAllEffect(filter).concatBy(", then")); + // Cycling {2} + this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}"))); + + } + + private SpectacularPileup(final SpectacularPileup card) { + super(card); + } + + @Override + public SpectacularPileup copy() { + return new SpectacularPileup(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpireMechcycle.java b/Mage.Sets/src/mage/cards/s/SpireMechcycle.java new file mode 100644 index 00000000000..f92e9a425c3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpireMechcycle.java @@ -0,0 +1,81 @@ +package mage.cards.s; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.costs.common.TapTargetCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.AddCardTypeSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.keyword.ExhaustAbility; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author Jmlundeen + */ +public final class SpireMechcycle extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("another untapped Mount or Vehicle you control"); + private static final FilterControlledPermanent mountAndVehicleFilter = new FilterControlledPermanent("Mount and/or Vehicle you control other than this Vehicle"); + private static final DynamicValue mountAndVehicleCount = new PermanentsOnBattlefieldCount(mountAndVehicleFilter); + + static { + filter.add(AnotherPredicate.instance); + filter.add(TappedPredicate.UNTAPPED); + filter.add(Predicates.or( + SubType.MOUNT.getPredicate(), + SubType.VEHICLE.getPredicate() + )); + mountAndVehicleFilter.add(AnotherPredicate.instance); + mountAndVehicleFilter.add(Predicates.or( + SubType.MOUNT.getPredicate(), + SubType.VEHICLE.getPredicate() + )); + } + + public SpireMechcycle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{R}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // Exhaust -- Tap another untapped Mount or Vehicle you control: This Vehicle becomes an artifact creature. Put a +1/+1 counter on it for each Mount and/or Vehicle you control other than this Vehicle. + Effect effect = new AddCardTypeSourceEffect(Duration.WhileOnBattlefield, CardType.ARTIFACT, CardType.CREATURE) + .setText("This Vehicle becomes an artifact creature"); + Ability ability = new ExhaustAbility(effect, new TapTargetCost(new TargetControlledPermanent(filter))); + ability.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance(), mountAndVehicleCount) + .setText("Put a +1/+1 counter on it for each Mount and/or Vehicle you control other than this Vehicle")); + this.addAbility(ability); + // Crew 2 + this.addAbility(new CrewAbility(2)); + + } + + private SpireMechcycle(final SpireMechcycle card) { + super(card); + } + + @Override + public SpireMechcycle copy() { + return new SpireMechcycle(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThunderousVelocipede.java b/Mage.Sets/src/mage/cards/t/ThunderousVelocipede.java new file mode 100644 index 00000000000..60bf8ff8b4d --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/ThunderousVelocipede.java @@ -0,0 +1,71 @@ +package mage.cards.t; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.EntersWithCountersControlledEffect; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; + +/** + * + * @author Jmlundeen + */ +public final class ThunderousVelocipede extends CardImpl { + + private static final FilterPermanent fourOrLessFilter = new FilterPermanent("other Vehicle and creature you control with mana value 4 or less"); + private static final FilterPermanent greaterThanFourFilter = new FilterPermanent("other Vehicle and creature you control with mana value greater than 4"); + + static { + fourOrLessFilter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 4)); + fourOrLessFilter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.VEHICLE.getPredicate() + )); + greaterThanFourFilter.add(new ManaValuePredicate(ComparisonType.MORE_THAN, 4)); + greaterThanFourFilter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.VEHICLE.getPredicate() + )); + } + + public ThunderousVelocipede(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{G}{G}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Each other Vehicle and creature you control enters with an additional +1/+1 counter on it if its mana value is 4 or less. Otherwise, it enters with three additional +1/+1 counters on it. + Ability ability = new SimpleStaticAbility(new EntersWithCountersControlledEffect(fourOrLessFilter, CounterType.P1P1.createInstance(1), true) + .setText("Each other Vehicle and creature you control enters with an additional +1/+1 counter on it if its mana value is 4 or less.")); + ability.addEffect(new EntersWithCountersControlledEffect(greaterThanFourFilter, CounterType.P1P1.createInstance(3), true) + .setText("otherwise, it enters with three additional +1/+1 counters on it.")); + this.addAbility(ability); + // Crew 3 + this.addAbility(new CrewAbility(3)); + + } + + private ThunderousVelocipede(final ThunderousVelocipede card) { + super(card); + } + + @Override + public ThunderousVelocipede copy() { + return new ThunderousVelocipede(this); + } +} diff --git a/Mage.Sets/src/mage/cards/w/WinterCursedRider.java b/Mage.Sets/src/mage/cards/w/WinterCursedRider.java new file mode 100644 index 00000000000..c2fea8bd0f0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WinterCursedRider.java @@ -0,0 +1,76 @@ +package mage.cards.w; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.ExileXFromYourGraveCost; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.dynamicvalue.common.SignInversionDynamicValue; +import mage.abilities.effects.common.continuous.BoostAllEffect; +import mage.abilities.effects.common.continuous.GainAbilityAllEffect; +import mage.abilities.keyword.ExhaustAbility; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; + +/** + * + * @author Jmlundeen + */ +public final class WinterCursedRider extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("Each other nonartifact creature"); + private static final DynamicValue xValue = new SignInversionDynamicValue(GetXValue.instance); + + static { + filter.add(Predicates.not(CardType.ARTIFACT.getPredicate())); + } + + public WinterCursedRider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Ward--Pay 2 life. + this.addAbility(new WardAbility(new PayLifeCost(2))); + + // Artifacts you control have "Ward--Pay 2 life." + WardAbility wardAbility = new WardAbility(new PayLifeCost(2)); + this.addAbility(new SimpleStaticAbility( + new GainAbilityAllEffect(wardAbility, Duration.WhileOnBattlefield, StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACTS) + .setText("Artifacts you control have " + "\"" + wardAbility.getRuleWithoutHint() + "\"") + )); + // Exhaust -- {2}{U}{B}, {T}, Exile X artifact cards from your graveyard: Each other nonartifact creature gets -X/-X until end of turn. + Ability ability = new ExhaustAbility(new BoostAllEffect(xValue, xValue, Duration.EndOfTurn, filter, true), + new ManaCostsImpl<>("{2}{U}{B}")); + ability.addCost(new TapSourceCost()); + ability.addCost(new ExileXFromYourGraveCost(StaticFilters.FILTER_CARD_ARTIFACTS) + .setText("Exile X artifact cards from your graveyard")); + this.addAbility(ability); + } + + private WinterCursedRider(final WinterCursedRider card) { + super(card); + } + + @Override + public WinterCursedRider copy() { + return new WinterCursedRider(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Aetherdrift.java b/Mage.Sets/src/mage/sets/Aetherdrift.java index fa0196632dd..cf4f88fbc67 100644 --- a/Mage.Sets/src/mage/sets/Aetherdrift.java +++ b/Mage.Sets/src/mage/sets/Aetherdrift.java @@ -111,7 +111,13 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Endrider Catalyzer", 124, Rarity.COMMON, mage.cards.e.EndriderCatalyzer.class)); cards.add(new SetCardInfo("Endrider Spikespitter", 125, Rarity.UNCOMMON, mage.cards.e.EndriderSpikespitter.class)); cards.add(new SetCardInfo("Engine Rat", 84, Rarity.COMMON, mage.cards.e.EngineRat.class)); + cards.add(new SetCardInfo("Explosive Getaway", 202, Rarity.RARE, mage.cards.e.ExplosiveGetaway.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Explosive Getaway", 390, Rarity.RARE, mage.cards.e.ExplosiveGetaway.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Explosive Getaway", 403, Rarity.MYTHIC, mage.cards.e.ExplosiveGetaway.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Explosive Getaway", 413, Rarity.MYTHIC, mage.cards.e.ExplosiveGetaway.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Explosive Getaway", 479, Rarity.RARE, mage.cards.e.ExplosiveGetaway.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Fang Guardian", 162, Rarity.UNCOMMON, mage.cards.f.FangGuardian.class)); + cards.add(new SetCardInfo("Fang-Druid Summoner", 163, Rarity.UNCOMMON, mage.cards.f.FangDruidSummoner.class)); cards.add(new SetCardInfo("Far Fortune, End Boss", 203, Rarity.RARE, mage.cards.f.FarFortuneEndBoss.class)); cards.add(new SetCardInfo("Fearless Swashbuckler", 204, Rarity.RARE, mage.cards.f.FearlessSwashbuckler.class)); cards.add(new SetCardInfo("Flood the Engine", 42, Rarity.COMMON, mage.cards.f.FloodTheEngine.class)); @@ -164,6 +170,11 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Lumbering Worldwagon", 168, Rarity.RARE, mage.cards.l.LumberingWorldwagon.class)); cards.add(new SetCardInfo("Magmakin Artillerist", 137, Rarity.COMMON, mage.cards.m.MagmakinArtillerist.class)); cards.add(new SetCardInfo("Marauding Mako", 138, Rarity.UNCOMMON, mage.cards.m.MaraudingMako.class)); + cards.add(new SetCardInfo("March of the World Ooze", 169, Rarity.MYTHIC, mage.cards.m.MarchOfTheWorldOoze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("March of the World Ooze", 388, Rarity.MYTHIC, mage.cards.m.MarchOfTheWorldOoze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("March of the World Ooze", 402, Rarity.MYTHIC, mage.cards.m.MarchOfTheWorldOoze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("March of the World Ooze", 412, Rarity.MYTHIC, mage.cards.m.MarchOfTheWorldOoze.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("March of the World Ooze", 468, Rarity.MYTHIC, mage.cards.m.MarchOfTheWorldOoze.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Marketback Walker", 235, Rarity.RARE, mage.cards.m.MarketbackWalker.class)); cards.add(new SetCardInfo("Marshals' Pathcruiser", 236, Rarity.UNCOMMON, mage.cards.m.MarshalsPathcruiser.class)); cards.add(new SetCardInfo("Maximum Overdrive", 96, Rarity.COMMON, mage.cards.m.MaximumOverdrive.class)); @@ -207,6 +218,10 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Repurposing Bay", 56, Rarity.RARE, mage.cards.r.RepurposingBay.class)); cards.add(new SetCardInfo("Ride's End", 25, Rarity.COMMON, mage.cards.r.RidesEnd.class)); cards.add(new SetCardInfo("Ripclaw Wrangler", 101, Rarity.COMMON, mage.cards.r.RipclawWrangler.class)); + cards.add(new SetCardInfo("Riptide Gearhulk", 219, Rarity.MYTHIC, mage.cards.r.RiptideGearhulk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Riptide Gearhulk", 353, Rarity.MYTHIC, mage.cards.r.RiptideGearhulk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Riptide Gearhulk", 490, Rarity.MYTHIC, mage.cards.r.RiptideGearhulk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Riptide Gearhulk", 552, Rarity.MYTHIC, mage.cards.r.RiptideGearhulk.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Rise from the Wreck", 178, Rarity.UNCOMMON, mage.cards.r.RiseFromTheWreck.class)); cards.add(new SetCardInfo("Risen Necroregent", 102, Rarity.UNCOMMON, mage.cards.r.RisenNecroregent.class)); cards.add(new SetCardInfo("Risky Shortcut", 103, Rarity.COMMON, mage.cards.r.RiskyShortcut.class)); @@ -232,10 +247,17 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Skycrash", 146, Rarity.UNCOMMON, mage.cards.s.Skycrash.class)); cards.add(new SetCardInfo("Skystreak Engineer", 61, Rarity.COMMON, mage.cards.s.SkystreakEngineer.class)); cards.add(new SetCardInfo("Slick Imitator", 62, Rarity.UNCOMMON, mage.cards.s.SlickImitator.class)); + cards.add(new SetCardInfo("Spectacular Pileup", 29, Rarity.RARE, mage.cards.s.SpectacularPileup.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Pileup", 378, Rarity.RARE, mage.cards.s.SpectacularPileup.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Pileup", 398, Rarity.MYTHIC, mage.cards.s.SpectacularPileup.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Pileup", 408, Rarity.MYTHIC, mage.cards.s.SpectacularPileup.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spectacular Pileup", 433, Rarity.RARE, mage.cards.s.SpectacularPileup.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spectral Interference", 63, Rarity.COMMON, mage.cards.s.SpectralInterference.class)); cards.add(new SetCardInfo("Spell Pierce", 64, Rarity.UNCOMMON, mage.cards.s.SpellPierce.class)); cards.add(new SetCardInfo("Spikeshell Harrier", 65, Rarity.UNCOMMON, mage.cards.s.SpikeshellHarrier.class)); cards.add(new SetCardInfo("Spin Out", 106, Rarity.COMMON, mage.cards.s.SpinOut.class)); + cards.add(new SetCardInfo("Spire Mechcycle", 147, Rarity.UNCOMMON, mage.cards.s.SpireMechcycle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spire Mechcycle", 314, Rarity.UNCOMMON, mage.cards.s.SpireMechcycle.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spotcycle Scouter", 30, Rarity.COMMON, mage.cards.s.SpotcycleScouter.class)); cards.add(new SetCardInfo("Stall Out", 66, Rarity.COMMON, mage.cards.s.StallOut.class)); cards.add(new SetCardInfo("Stampeding Scurryfoot", 181, Rarity.COMMON, mage.cards.s.StampedingScurryfoot.class)); @@ -256,6 +278,10 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Thornwood Falls", 266, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class)); cards.add(new SetCardInfo("Thunderhead Gunner", 148, Rarity.COMMON, mage.cards.t.ThunderheadGunner.class)); cards.add(new SetCardInfo("Thundering Broodwagon", 225, Rarity.UNCOMMON, mage.cards.t.ThunderingBroodwagon.class)); + cards.add(new SetCardInfo("Thunderous Velocipede", 183, Rarity.MYTHIC, mage.cards.t.ThunderousVelocipede.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thunderous Velocipede", 317, Rarity.MYTHIC, mage.cards.t.ThunderousVelocipede.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thunderous Velocipede", 471, Rarity.MYTHIC, mage.cards.t.ThunderousVelocipede.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Thunderous Velocipede", 529, Rarity.MYTHIC, mage.cards.t.ThunderousVelocipede.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ticket Tortoise", 245, Rarity.COMMON, mage.cards.t.TicketTortoise.class)); cards.add(new SetCardInfo("Trade the Helm", 69, Rarity.UNCOMMON, mage.cards.t.TradeTheHelm.class)); cards.add(new SetCardInfo("Tranquil Cove", 267, Rarity.COMMON, mage.cards.t.TranquilCove.class)); @@ -279,6 +305,9 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Wild Roads", 269, Rarity.UNCOMMON, mage.cards.w.WildRoads.class)); cards.add(new SetCardInfo("Willowrush Verge", 270, Rarity.RARE, mage.cards.w.WillowrushVerge.class)); cards.add(new SetCardInfo("Wind-Scarred Crag", 271, Rarity.COMMON, mage.cards.w.WindScarredCrag.class)); + cards.add(new SetCardInfo("Winter, Cursed Rider", 228, Rarity.RARE, mage.cards.w.WinterCursedRider.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Winter, Cursed Rider", 369, Rarity.RARE, mage.cards.w.WinterCursedRider.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Winter, Cursed Rider", 494, Rarity.RARE, mage.cards.w.WinterCursedRider.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Wreck Remover", 247, Rarity.COMMON, mage.cards.w.WreckRemover.class)); cards.add(new SetCardInfo("Wreckage Wickerfolk", 110, Rarity.COMMON, mage.cards.w.WreckageWickerfolk.class)); cards.add(new SetCardInfo("Wretched Doll", 111, Rarity.UNCOMMON, mage.cards.w.WretchedDoll.class)); -- 2.47.2 From a7428a6c9351a1980ea797e54d2770617183046b Mon Sep 17 00:00:00 2001 From: Jmlundeen <98545818+Jmlundeen@users.noreply.github.com> Date: Fri, 7 Mar 2025 00:44:01 -0600 Subject: [PATCH 3/9] [DFT] Add Mendicant Core, Guidelight (#13412) --- .../mage/cards/m/MendicantCoreGuidelight.java | 69 +++++++++++++++++++ Mage.Sets/src/mage/sets/Aetherdrift.java | 3 + 2 files changed, 72 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MendicantCoreGuidelight.java diff --git a/Mage.Sets/src/mage/cards/m/MendicantCoreGuidelight.java b/Mage.Sets/src/mage/cards/m/MendicantCoreGuidelight.java new file mode 100644 index 00000000000..8825fd6713b --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MendicantCoreGuidelight.java @@ -0,0 +1,69 @@ +package mage.cards.m; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.MaxSpeedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.common.delayed.CopyNextSpellDelayedTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CopyStackObjectEffect; +import mage.abilities.effects.common.CopyTargetStackObjectEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.constants.*; +import mage.abilities.keyword.StartYourEnginesAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.FilterPermanent; +import mage.filter.FilterSpell; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; + +/** + * + * @author Jmlundeen + */ +public final class MendicantCoreGuidelight extends CardImpl { + + private static final FilterSpell filter = new FilterSpell("an artifact spell"); + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACTS); + + static { + filter.add(CardType.ARTIFACT.getPredicate()); + } + + public MendicantCoreGuidelight(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.ROBOT); + this.power = new MageInt(0); + this.toughness = new MageInt(3); + + // Mendicant Core's power is equal to the number of artifacts you control. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SetBasePowerSourceEffect(xValue))); + // Start your engines! + this.addAbility(new StartYourEnginesAbility()); + + // Max speed -- Whenever you cast an artifact spell, you may pay {1}. If you do, copy it. + Effect copyEffect = new CopyTargetStackObjectEffect(true) + .setText("copy it. (The copy becomes a token.)"); + Effect doIfEffect = new DoIfCostPaid(copyEffect,new ManaCostsImpl<>("{1}")); + Ability ability = new SpellCastControllerTriggeredAbility(doIfEffect, filter, false, SetTargetPointer.SPELL); + this.addAbility(new MaxSpeedAbility(ability)); + } + + private MendicantCoreGuidelight(final MendicantCoreGuidelight card) { + super(card); + } + + @Override + public MendicantCoreGuidelight copy() { + return new MendicantCoreGuidelight(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Aetherdrift.java b/Mage.Sets/src/mage/sets/Aetherdrift.java index cf4f88fbc67..e311a06905e 100644 --- a/Mage.Sets/src/mage/sets/Aetherdrift.java +++ b/Mage.Sets/src/mage/sets/Aetherdrift.java @@ -179,6 +179,9 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Marshals' Pathcruiser", 236, Rarity.UNCOMMON, mage.cards.m.MarshalsPathcruiser.class)); cards.add(new SetCardInfo("Maximum Overdrive", 96, Rarity.COMMON, mage.cards.m.MaximumOverdrive.class)); cards.add(new SetCardInfo("Memory Guardian", 49, Rarity.UNCOMMON, mage.cards.m.MemoryGuardian.class)); + cards.add(new SetCardInfo("Mendicant Core, Guidelight", 213, Rarity.RARE, mage.cards.m.MendicantCoreGuidelight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mendicant Core, Guidelight", 365, Rarity.RARE, mage.cards.m.MendicantCoreGuidelight.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Mendicant Core, Guidelight", 485, Rarity.RARE, mage.cards.m.MendicantCoreGuidelight.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Midnight Mangler", 50, Rarity.COMMON, mage.cards.m.MidnightMangler.class)); cards.add(new SetCardInfo("Migrating Ketradon", 170, Rarity.COMMON, mage.cards.m.MigratingKetradon.class)); cards.add(new SetCardInfo("Mindspring Merfolk", 51, Rarity.RARE, mage.cards.m.MindspringMerfolk.class)); -- 2.47.2 From 18debb7ae3f68307e6b7ba6c9b1595d3e6b706a3 Mon Sep 17 00:00:00 2001 From: xenohedron <12538125+xenohedron@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:58:36 -0500 Subject: [PATCH 4/9] fix #13414 (Fecund Greenshell) --- Mage.Sets/src/mage/cards/f/FecundGreenshell.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/f/FecundGreenshell.java b/Mage.Sets/src/mage/cards/f/FecundGreenshell.java index 995d4fd4828..3a88f596fd6 100644 --- a/Mage.Sets/src/mage/cards/f/FecundGreenshell.java +++ b/Mage.Sets/src/mage/cards/f/FecundGreenshell.java @@ -59,7 +59,7 @@ public final class FecundGreenshell extends CardImpl { // Whenever Fecund Greenshell or another creature you control with toughness greater than its power enters, // look at the top card of your library. If it's a land card, you may put it onto the battlefield tapped. Otherwise, put it into your hand. this.addAbility(new EntersBattlefieldThisOrAnotherTriggeredAbility( - new FecundGreenshellEffect(), filter, false, false)); + new FecundGreenshellEffect(), filter, false, true)); } private FecundGreenshell(final FecundGreenshell card) { -- 2.47.2 From f9aa8c1527224622dc6c02ebb42fe8692656d073 Mon Sep 17 00:00:00 2001 From: Jmlundeen <98545818+Jmlundeen@users.noreply.github.com> Date: Sat, 8 Mar 2025 09:49:29 -0600 Subject: [PATCH 5/9] [DFT] Implement Wickerfolk Indomitable (#13404) --- .../mage/cards/w/WickerfolkIndomitable.java | 94 +++++++++++++++++++ Mage.Sets/src/mage/sets/Aetherdrift.java | 1 + Mage/src/main/java/mage/MageIdentifier.java | 3 +- 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/w/WickerfolkIndomitable.java diff --git a/Mage.Sets/src/mage/cards/w/WickerfolkIndomitable.java b/Mage.Sets/src/mage/cards/w/WickerfolkIndomitable.java new file mode 100644 index 00000000000..02199864ec4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WickerfolkIndomitable.java @@ -0,0 +1,94 @@ +package mage.cards.w; + +import java.util.UUID; + +import mage.MageIdentifier; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.MayCastFromGraveyardSourceAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author Jmlundeen + */ +public final class WickerfolkIndomitable extends CardImpl { + + public WickerfolkIndomitable(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{B}"); + + this.subtype.add(SubType.SCARECROW); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // You may cast this card from your graveyard by paying 2 life and sacrificing an artifact or creature in addition to paying its other costs. + Ability ability = new SimpleStaticAbility(Zone.GRAVEYARD, new WickerfolkIndomitableGraveyardEffect()) + .setIdentifier(MageIdentifier.WickerfolkIndomitableAlternateCast); + + this.addAbility(ability); + } + + private WickerfolkIndomitable(final WickerfolkIndomitable card) { + super(card); + } + + @Override + public WickerfolkIndomitable copy() { + return new WickerfolkIndomitable(this); + } +} + +class WickerfolkIndomitableGraveyardEffect extends AsThoughEffectImpl { + + WickerfolkIndomitableGraveyardEffect() { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfGame, Outcome.PutCreatureInPlay); + this.staticText = "You may cast this card from your graveyard by paying 2 life and sacrificing an artifact or creature in addition to paying its other costs."; + } + + private WickerfolkIndomitableGraveyardEffect(final WickerfolkIndomitableGraveyardEffect effect) { + super(effect); + } + + @Override + public WickerfolkIndomitableGraveyardEffect copy() { + return new WickerfolkIndomitableGraveyardEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (!objectId.equals(source.getSourceId()) || !source.isControlledBy(affectedControllerId) + || game.getState().getZone(source.getSourceId()) != Zone.GRAVEYARD) { + return false; + } + Player controller = game.getPlayer(affectedControllerId); + if (controller != null) { + Costs costs = new CostsImpl<>(); + costs.add(new PayLifeCost(2)); + costs.add(new SacrificeTargetCost(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT_OR_CREATURE)); + controller.setCastSourceIdWithAlternateMana(objectId, new ManaCostsImpl<>("{3}{B}"), costs, + MageIdentifier.WickerfolkIndomitableAlternateCast); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/Aetherdrift.java b/Mage.Sets/src/mage/sets/Aetherdrift.java index e311a06905e..f16262aba1f 100644 --- a/Mage.Sets/src/mage/sets/Aetherdrift.java +++ b/Mage.Sets/src/mage/sets/Aetherdrift.java @@ -305,6 +305,7 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Wastewood Verge", 268, Rarity.RARE, mage.cards.w.WastewoodVerge.class)); cards.add(new SetCardInfo("Waxen Shapethief", 74, Rarity.RARE, mage.cards.w.WaxenShapethief.class)); cards.add(new SetCardInfo("Webstrike Elite", 186, Rarity.RARE, mage.cards.w.WebstrikeElite.class)); + cards.add(new SetCardInfo("Wickerfolk Indomitable", 109, Rarity.UNCOMMON, mage.cards.w.WickerfolkIndomitable.class)); cards.add(new SetCardInfo("Wild Roads", 269, Rarity.UNCOMMON, mage.cards.w.WildRoads.class)); cards.add(new SetCardInfo("Willowrush Verge", 270, Rarity.RARE, mage.cards.w.WillowrushVerge.class)); cards.add(new SetCardInfo("Wind-Scarred Crag", 271, Rarity.COMMON, mage.cards.w.WindScarredCrag.class)); diff --git a/Mage/src/main/java/mage/MageIdentifier.java b/Mage/src/main/java/mage/MageIdentifier.java index afb1416331e..26059236f48 100644 --- a/Mage/src/main/java/mage/MageIdentifier.java +++ b/Mage/src/main/java/mage/MageIdentifier.java @@ -76,7 +76,8 @@ public enum MageIdentifier { TheRuinousPowersAlternateCast, FiresOfMountDoomAlternateCast, PrimalPrayersAlternateCast, - QuilledGreatwurmAlternateCast; + QuilledGreatwurmAlternateCast, + WickerfolkIndomitableAlternateCast; /** * Additional text if there is need to differentiate two very similar effects -- 2.47.2 From f22755d44d623540b7724ac13172145676bf83df Mon Sep 17 00:00:00 2001 From: Jmlundeen <98545818+Jmlundeen@users.noreply.github.com> Date: Sat, 8 Mar 2025 11:38:26 -0600 Subject: [PATCH 6/9] [DFT] Implement Riverchurn Monument (#13405) * fix MillCardsTargetEffect to work with multiple targets --- .../src/mage/cards/r/RiverchurnMonument.java | 88 +++++++++++++++++++ Mage.Sets/src/mage/sets/Aetherdrift.java | 3 + .../effects/common/MillCardsTargetEffect.java | 13 +-- 3 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/r/RiverchurnMonument.java diff --git a/Mage.Sets/src/mage/cards/r/RiverchurnMonument.java b/Mage.Sets/src/mage/cards/r/RiverchurnMonument.java new file mode 100644 index 00000000000..d9f211b9bda --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RiverchurnMonument.java @@ -0,0 +1,88 @@ +package mage.cards.r; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.keyword.ExhaustAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; +import mage.target.targetpointer.EachTargetPointer; + +/** + * + * @author Jmlundeen + */ +public final class RiverchurnMonument extends CardImpl { + + public RiverchurnMonument(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{U}"); + + + // {1}, {T}: Any number of target players each mill two cards. + Effect effect = new MillCardsTargetEffect(2); + effect.setTargetPointer(new EachTargetPointer()); + effect.setText("Any number of target players each mill two cards. " + + "(Each of them puts the top two cards of their library into their graveyard.)"); + Ability ability = new SimpleActivatedAbility(effect, new ManaCostsImpl<>("{1}")); + ability.addCost(new TapSourceCost()); + ability.addTarget(new TargetPlayer(0, Integer.MAX_VALUE, false)); + this.addAbility(ability); + + // Exhaust -- {2}{U}{U}, {T}: Any number of target players each mill cards equal to the number of cards in their graveyard. + Effect exhaustEffect = new RiverchurnMonumentEffect(); + exhaustEffect.setTargetPointer(new EachTargetPointer()); + exhaustEffect.setText("Any number of target players each mill cards equal to the number of cards in their graveyard."); + Ability exhaustAbility = new ExhaustAbility(exhaustEffect, new ManaCostsImpl<>("{2}{U}{U}")); + exhaustAbility.addCost(new TapSourceCost()); + exhaustAbility.addTarget(new TargetPlayer(0, Integer.MAX_VALUE, false)); + this.addAbility(exhaustAbility); + } + + private RiverchurnMonument(final RiverchurnMonument card) { + super(card); + } + + @Override + public RiverchurnMonument copy() { + return new RiverchurnMonument(this); + } +} + +class RiverchurnMonumentEffect extends OneShotEffect { + + public RiverchurnMonumentEffect() { + super(Outcome.Detriment); + this.staticText = "Any number of target players each mill cards equal to the number of cards in their graveyard."; + } + + public RiverchurnMonumentEffect(final RiverchurnMonumentEffect effect) { + super(effect); + } + + @Override + public RiverchurnMonumentEffect copy() { + return new RiverchurnMonumentEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + for (UUID playerId : getTargetPointer().getTargets(game, source)) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.millCards(player.getGraveyard().size(), source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/Aetherdrift.java b/Mage.Sets/src/mage/sets/Aetherdrift.java index f16262aba1f..76d348b8c14 100644 --- a/Mage.Sets/src/mage/sets/Aetherdrift.java +++ b/Mage.Sets/src/mage/sets/Aetherdrift.java @@ -228,6 +228,9 @@ public final class Aetherdrift extends ExpansionSet { cards.add(new SetCardInfo("Rise from the Wreck", 178, Rarity.UNCOMMON, mage.cards.r.RiseFromTheWreck.class)); cards.add(new SetCardInfo("Risen Necroregent", 102, Rarity.UNCOMMON, mage.cards.r.RisenNecroregent.class)); cards.add(new SetCardInfo("Risky Shortcut", 103, Rarity.COMMON, mage.cards.r.RiskyShortcut.class)); + cards.add(new SetCardInfo("Riverchurn Monument", 57, Rarity.RARE, mage.cards.r.RiverchurnMonument.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Riverchurn Monument", 381, Rarity.RARE, mage.cards.r.RiverchurnMonument.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Riverchurn Monument", 440, Rarity.RARE, mage.cards.r.RiverchurnMonument.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Riverpyre Verge", 260, Rarity.RARE, mage.cards.r.RiverpyreVerge.class)); cards.add(new SetCardInfo("Road Rage", 145, Rarity.UNCOMMON, mage.cards.r.RoadRage.class)); cards.add(new SetCardInfo("Roadside Assistance", 26, Rarity.UNCOMMON, mage.cards.r.RoadsideAssistance.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/MillCardsTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MillCardsTargetEffect.java index bd5fa91f6fa..25f32e64e2c 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/MillCardsTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/MillCardsTargetEffect.java @@ -10,6 +10,8 @@ import mage.game.Game; import mage.players.Player; import mage.util.CardUtil; +import java.util.UUID; + /** * @author LevelX2 */ @@ -38,12 +40,13 @@ public class MillCardsTargetEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (player != null) { - player.millCards(numberCards.calculate(game, source, this), source, game); - return true; + for (UUID playerId : getTargetPointer().getTargets(game, source)) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.millCards(numberCards.calculate(game, source, this), source, game); + } } - return false; + return true; } @Override -- 2.47.2 From 65129a9d0a31a0d2513d7e1179b6a5f08e62eb5b Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Sat, 8 Mar 2025 21:49:10 -0500 Subject: [PATCH 7/9] [DSC] Fix Kianne, Corrupted Memory --- Mage.Sets/src/mage/cards/k/KianneCorruptedMemory.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/k/KianneCorruptedMemory.java b/Mage.Sets/src/mage/cards/k/KianneCorruptedMemory.java index a51151624c2..4b62095cda2 100644 --- a/Mage.Sets/src/mage/cards/k/KianneCorruptedMemory.java +++ b/Mage.Sets/src/mage/cards/k/KianneCorruptedMemory.java @@ -46,15 +46,15 @@ public final class KianneCorruptedMemory extends CardImpl { this.addAbility(new SimpleStaticAbility( new ConditionalAsThoughEffect( new CastAsThoughItHadFlashAllEffect(Duration.Custom, filter), - KianneCorruptedMemoryOddCondition.instance - ))); + KianneCorruptedMemoryEvenCondition.instance + ).setText("As long as {this}'s power is even, you may cast noncreature spells as though they had flash."))); // As long as Kianne's power is odd, you may cast creature spells as though they had flash. this.addAbility(new SimpleStaticAbility( new ConditionalAsThoughEffect( new CastAsThoughItHadFlashAllEffect(Duration.Custom, filter2), - KianneCorruptedMemoryEvenCondition.instance - ))); + KianneCorruptedMemoryOddCondition.instance + ).setText("As long as {this}'s power is odd, you may cast creature spells as though they had flash."))); // Whenever you draw a card, put a +1/+1 counter on Kianne. this.addAbility(new DrawCardControllerTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false)); -- 2.47.2 From 5060f2504a0e6b10b5447eed12fe6c535ea46ebd Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Sat, 8 Mar 2025 23:21:40 -0500 Subject: [PATCH 8/9] [TDC] Implement Betor, Ancetor's Voice --- .../src/mage/cards/b/BetorAncestorsVoice.java | 99 +++++++++++++++++++ .../mage/sets/TarkirDragonstormCommander.java | 1 + Utils/mtg-cards-data.txt | 1 + 3 files changed, 101 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BetorAncestorsVoice.java diff --git a/Mage.Sets/src/mage/cards/b/BetorAncestorsVoice.java b/Mage.Sets/src/mage/cards/b/BetorAncestorsVoice.java new file mode 100644 index 00000000000..88e1cac8d2a --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BetorAncestorsVoice.java @@ -0,0 +1,99 @@ +package mage.cards.b; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.ControllerGainedLifeCount; +import mage.abilities.dynamicvalue.common.ControllerLostLifeCount; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.target.common.TargetCardInGraveyard; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; +import mage.watchers.common.PlayerGainedLifeWatcher; + +/** + * + * @author Grath + */ +public final class BetorAncestorsVoice extends CardImpl { + + final private static FilterCard filter + = new FilterCard("creature card with mana value less than or equal to the amount of life you lost this turn"); + + static { + filter.add(CardType.CREATURE.getPredicate()); + filter.add(BetorPredicate.instance); + } + + public BetorAncestorsVoice(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIRIT); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Lifelink + this.addAbility(LifelinkAbility.getInstance()); + + // At the beginning of your end step, put a number of +1/+1 counters on up to one other target creature you + // control equal to the amount of life you gained this turn. Return up to one target creature card with mana + // value less than or equal to the amount of life you lost this turn from your graveyard to the battlefield. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance(), ControllerGainedLifeCount.instance) + .setText("put a number of +1/+1 counters on up to one other target creature you " + + "control equal to the amount of life you gained this turn.") + ); + ability.addTarget(new TargetControlledCreaturePermanent(0, 1, StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL, false)); + ability.addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect().setTargetPointer(new SecondTargetPointer()) + .setText("Return up to one target creature card with mana value less than or equal to the amount of " + + "life you lost this turn from your graveyard to the battlefield.")); + ability.addTarget(new TargetCardInGraveyard(0, 1, filter)); + ability.addHint(ControllerGainedLifeCount.getHint()); + ability.addHint(ControllerLostLifeCount.getHint()); + this.addAbility(ability, new PlayerGainedLifeWatcher()); + } + + private BetorAncestorsVoice(final BetorAncestorsVoice card) { + super(card); + } + + @Override + public BetorAncestorsVoice copy() { + return new BetorAncestorsVoice(this); + } +} + +enum BetorPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return input.getObject().getManaValue() <= ControllerLostLifeCount.instance.calculate(game, input.getSource(), null); + } + + @Override + public String toString() { + return "mana value less than or equal to the amount of life you lost this turn"; + } +} \ 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 b968e03fbf5..9e5f74393d1 100644 --- a/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java +++ b/Mage.Sets/src/mage/sets/TarkirDragonstormCommander.java @@ -19,6 +19,7 @@ public final class TarkirDragonstormCommander extends ExpansionSet { super("Tarkir: Dragonstorm Commander", "TDC", ExpansionSet.buildDate(2025, 4, 11), SetType.SUPPLEMENTAL); this.hasBasicLands = false; + cards.add(new SetCardInfo("Betor, Ancestor's Voice", 1, Rarity.MYTHIC, mage.cards.b.BetorAncestorsVoice.class)); cards.add(new SetCardInfo("Teval, the Balanced Scale", 8, Rarity.MYTHIC, mage.cards.t.TevalTheBalancedScale.class)); } } diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 66151efc0c2..d585e63d623 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -57207,3 +57207,4 @@ Shiko, Paragon of the Way|Tarkir: Dragonstorm|223|M|{2}{U}{R}{W}|Legendary Creat Skirmish Rhino|Tarkir: Dragonstorm|224|U|{W}{B}{G}|Creature - Rhino|3|4|Trample$When this creature enters, each opponent loses 2 life and you gain 2 life.| Mox Jasper|Tarkir: Dragonstorm|246|M|{0}|Legendary Artifact|||{T}: Add one mana of any color. Activate only if you control a Dragon.| Teval, the Balanced Scale|Tarkir: Dragonstorm Commander|8|M|{1}{B}{G}{U}|Legendary Creature - Spirit Dragon|4|4|Flying$Whenever Teval attacks, mill three cards. Then you may return a land card from your graveyard to the battlefield tapped.$Whenever one or more cards leave your graveyard, create a 2/2 black Zombie Druid creature token.| +Betor, Ancestor's Voice|Tarkir: Dragonstorm Commander|1|M|{2}{W}{B}{G}|Legendary Creature - Spirit Dragon|3|5|Flying, lifelink$At the beginning of your end step, put a number of +1/+1 counters on up to one other target creature you control equal to the amount of life you gained this turn. Return up to one target creature card with mana value less than or equal to the amount of life you lost this turn from your graveyard to the battlefield. -- 2.47.2 From b614dcbcd6878f2754682010e404b5f0a5dc6dc4 Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Mon, 10 Mar 2025 10:46:35 -0400 Subject: [PATCH 9/9] [BLC] Implement Evercoat Ursine. --- .../src/mage/cards/e/EvercoatUrsine.java | 50 +++++++++++++++++++ .../src/mage/sets/BloomburrowCommander.java | 1 + 2 files changed, 51 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/EvercoatUrsine.java diff --git a/Mage.Sets/src/mage/cards/e/EvercoatUrsine.java b/Mage.Sets/src/mage/cards/e/EvercoatUrsine.java new file mode 100644 index 00000000000..683ccc27666 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EvercoatUrsine.java @@ -0,0 +1,50 @@ +package mage.cards.e; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.common.HideawayPlayEffect; +import mage.constants.SubType; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.HideawayAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; + +/** + * + * @author Grath + */ +public final class EvercoatUrsine extends CardImpl { + + public EvercoatUrsine(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}"); + + this.subtype.add(SubType.ELEMENTAL); + this.subtype.add(SubType.BEAR); + this.power = new MageInt(6); + this.toughness = new MageInt(5); + + // Trample + this.addAbility(TrampleAbility.getInstance()); + + // Hideaway 3 + this.addAbility(new HideawayAbility(this, 3)); + + // Hideaway 3 + this.addAbility(new HideawayAbility(this, 3)); + + // Whenever Evercoat Ursine deals combat damage to a player, if there are cards exiled with it, you may play + // one of them without paying its mana cost. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new HideawayPlayEffect(true))); + } + + private EvercoatUrsine(final EvercoatUrsine card) { + super(card); + } + + @Override + public EvercoatUrsine copy() { + return new EvercoatUrsine(this); + } +} diff --git a/Mage.Sets/src/mage/sets/BloomburrowCommander.java b/Mage.Sets/src/mage/sets/BloomburrowCommander.java index cd669f6f14a..19461bbc1e5 100644 --- a/Mage.Sets/src/mage/sets/BloomburrowCommander.java +++ b/Mage.Sets/src/mage/sets/BloomburrowCommander.java @@ -97,6 +97,7 @@ public final class BloomburrowCommander extends ExpansionSet { cards.add(new SetCardInfo("End-Raze Forerunners", 214, Rarity.RARE, mage.cards.e.EndRazeForerunners.class)); cards.add(new SetCardInfo("Esika's Chariot", 215, Rarity.RARE, mage.cards.e.EsikasChariot.class)); cards.add(new SetCardInfo("Etali, Primal Storm", 196, Rarity.RARE, mage.cards.e.EtaliPrimalStorm.class)); + cards.add(new SetCardInfo("Evercoat Ursine", 30, Rarity.RARE, mage.cards.e.EvercoatUrsine.class)); cards.add(new SetCardInfo("Evolving Wilds", 302, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); cards.add(new SetCardInfo("Exotic Orchard", 131, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); cards.add(new SetCardInfo("Explore", 216, Rarity.COMMON, mage.cards.e.Explore.class)); -- 2.47.2