From 8b6b90c8d87bc009159c81f51b2fb7a9793b222f Mon Sep 17 00:00:00 2001 From: jmlundeen Date: Thu, 4 Sep 2025 10:59:03 -0500 Subject: [PATCH] [SPM] implement Rhino's Rampage --- Mage.Sets/src/mage/cards/r/RhinosRampage.java | 95 +++++++++++++++++++ Mage.Sets/src/mage/sets/MarvelsSpiderMan.java | 1 + .../cards/single/spm/RhinosRampageTest.java | 92 ++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RhinosRampage.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/spm/RhinosRampageTest.java diff --git a/Mage.Sets/src/mage/cards/r/RhinosRampage.java b/Mage.Sets/src/mage/cards/r/RhinosRampage.java new file mode 100644 index 00000000000..e590d1fa386 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RhinosRampage.java @@ -0,0 +1,95 @@ +package mage.cards.r; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +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.ComparisonType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * + * @author Jmlundeen + */ +public final class RhinosRampage extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature an opponent controls"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public RhinosRampage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R/G}"); + + + // Target creature you control gets +1/+0 until end of turn. It fights target creature an opponent controls. When excess damage is dealt to the creature an opponent controls this way, destroy up to one target noncreature artifact with mana value 3 or less. + this.getSpellAbility().addEffect(new BoostTargetEffect(1, 0)); + this.getSpellAbility().addEffect(new FightTargetsEffect() + .setText("It fights target creature an opponent controls")); + this.getSpellAbility().addEffect(new RhinosRampageEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + + } + + private RhinosRampage(final RhinosRampage card) { + super(card); + } + + @Override + public RhinosRampage copy() { + return new RhinosRampage(this); + } +} + +class RhinosRampageEffect extends OneShotEffect { + + public RhinosRampageEffect() { + super(Outcome.BoostCreature); + staticText = "When excess damage is dealt to the creature an opponent controls this way, destroy up to one target noncreature " + + "artifact with mana value 3 or less"; + } + + protected RhinosRampageEffect(final RhinosRampageEffect effect) { + super(effect); + } + + @Override + public RhinosRampageEffect copy() { + return new RhinosRampageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanentOrLKIBattlefield(source.getTargets().get(1).getFirstTarget()); + if (permanent == null || permanent.getDamage() <= permanent.getToughness().getBaseValue()) { + return false; + } + FilterPermanent filter = new FilterArtifactPermanent("noncreature artifact with mana value 3 or less"); + filter.add(Predicates.not(CardType.CREATURE.getPredicate())); + filter.add(new ManaValuePredicate(ComparisonType.OR_LESS, 3)); + + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new DestroyTargetEffect(), false); + ability.addTarget(new TargetPermanent(0, 1, filter)); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java b/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java index 8552a631c14..5b8333368ed 100644 --- a/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java +++ b/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java @@ -159,6 +159,7 @@ public final class MarvelsSpiderMan extends ExpansionSet { cards.add(new SetCardInfo("Raging Goblinoids", 85, Rarity.UNCOMMON, mage.cards.r.RagingGoblinoids.class)); cards.add(new SetCardInfo("Rent Is Due", 11, Rarity.RARE, mage.cards.r.RentIsDue.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Rent Is Due", 247, Rarity.RARE, mage.cards.r.RentIsDue.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Rhino's Rampage", 141, Rarity.UNCOMMON, mage.cards.r.RhinosRampage.class)); cards.add(new SetCardInfo("Rhino, Barreling Brute", 140, Rarity.UNCOMMON, mage.cards.r.RhinoBarrelingBrute.class)); cards.add(new SetCardInfo("Risky Research", 62, Rarity.COMMON, mage.cards.r.RiskyResearch.class)); cards.add(new SetCardInfo("Robotics Mastery", 41, Rarity.UNCOMMON, mage.cards.r.RoboticsMastery.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/RhinosRampageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/RhinosRampageTest.java new file mode 100644 index 00000000000..c9fdc258521 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/RhinosRampageTest.java @@ -0,0 +1,92 @@ +package org.mage.test.cards.single.spm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Jmlundeen + */ +public class RhinosRampageTest extends CardTestPlayerBase { + + /* + Rhino's Rampage + {R/G} + Sorcery + Target creature you control gets +1/+0 until end of turn. It fights target creature an opponent controls. + When excess damage is dealt to the creature an opponent controls this way, destroy up to one target noncreature artifact with mana value 3 or less. + */ + private static final String rhinosRampage = "Rhino's Rampage"; + + /* + Bear Cub + {1}{G} + Creature - Bear + + 2/2 + */ + private static final String bearCub = "Bear Cub"; + + /* + Fugitive Wizard + {U} + Creature - Human Wizard + + 1/1 + */ + private static final String fugitiveWizard = "Fugitive Wizard"; + + /* + Tormod's Crypt + {0} + Artifact + {T}, Sacrifice Tormod's Crypt: Exile all cards from target player's graveyard. + */ + private static final String tormodsCrypt = "Tormod's Crypt"; + + @Test + public void testRhinosRampageExcess() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, rhinosRampage); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.BATTLEFIELD, playerA, bearCub); + addCard(Zone.BATTLEFIELD, playerB, fugitiveWizard); + addCard(Zone.BATTLEFIELD, playerB, tormodsCrypt); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rhinosRampage); + addTarget(playerA, bearCub); + addTarget(playerA, fugitiveWizard); + addTarget(playerA, tormodsCrypt); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, fugitiveWizard, 1); + assertGraveyardCount(playerB, tormodsCrypt, 1); + } + + @Test + public void testRhinosRampageNoExcess() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, rhinosRampage); + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.BATTLEFIELD, playerA, fugitiveWizard); + addCard(Zone.BATTLEFIELD, playerB, bearCub); + addCard(Zone.BATTLEFIELD, playerB, tormodsCrypt); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, rhinosRampage); + addTarget(playerA, fugitiveWizard); + addTarget(playerA, bearCub); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, fugitiveWizard, 1); + assertGraveyardCount(playerB, bearCub, 1); + assertPermanentCount(playerB, tormodsCrypt, 1); + } +} \ No newline at end of file