From 1eae1c533e3f597cfbe6ff21b7c6e00d4fdd8a16 Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Tue, 11 Apr 2023 18:03:18 -0400 Subject: [PATCH 1/3] [MOC] Implement Deluxe Dragster --- .../src/mage/cards/d/DeluxeDragster.java | 197 ++++++++++++++++++ .../mage/sets/MarchOfTheMachineCommander.java | 1 + 2 files changed, 198 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/d/DeluxeDragster.java diff --git a/Mage.Sets/src/mage/cards/d/DeluxeDragster.java b/Mage.Sets/src/mage/cards/d/DeluxeDragster.java new file mode 100644 index 0000000000..5158109a7a --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DeluxeDragster.java @@ -0,0 +1,197 @@ +package mage.cards.d; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleEvasionAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect; +import mage.abilities.keyword.CrewAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.card.OwnerIdPredicate; +import mage.game.Game; +import mage.game.events.DamagedPlayerEvent; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DeluxeDragster extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("except by Vehicles"); + + static { + filter.add(Predicates.not(SubType.VEHICLE.getPredicate())); + } + + public DeluxeDragster(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{U}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Deluxe Dragster can’t be blocked except by Vehicles. + this.addAbility(new SimpleEvasionAbility(new CantBeBlockedByCreaturesSourceEffect(filter, Duration.WhileOnBattlefield))); + + // Whenever Deluxe Dragster deals combat damage to a player, you may cast target instant or sorcery card from + // that player’s graveyard without paying its mana cost. If that spell would be put into a graveyard, exile it instead. + this.addAbility(new DeluxeDragsterTriggeredAbility()); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + } + + private DeluxeDragster(final DeluxeDragster card) { + super(card); + } + + @Override + public DeluxeDragster copy() { + return new DeluxeDragster(this); + } +} + +class DeluxeDragsterTriggeredAbility extends TriggeredAbilityImpl { + + public DeluxeDragsterTriggeredAbility() { + super(Zone.BATTLEFIELD, new DeluxeDragsterEffect(), true); + } + + public DeluxeDragsterTriggeredAbility(final DeluxeDragsterTriggeredAbility ability) { + super(ability); + } + + @Override + public DeluxeDragsterTriggeredAbility copy() { + return new DeluxeDragsterTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!event.getSourceId().equals(this.sourceId) || !((DamagedPlayerEvent) event).isCombatDamage()) { + return false; + } + Player damagedPlayer = game.getPlayer(event.getTargetId()); + if (damagedPlayer == null) { + return false; + } + FilterCard filter = new FilterCard("instant or sorcery in " + damagedPlayer.getName() + "'s graveyard"); + filter.add(Predicates.or(CardType.INSTANT.getPredicate(), CardType.SORCERY.getPredicate())); + filter.add(new OwnerIdPredicate(damagedPlayer.getId())); + TargetCardInGraveyard target = new TargetCardInGraveyard(filter); + this.getTargets().clear(); + this.addTarget(target); + return true; + } + + @Override + public String getRule() { + return "Whenever {this} deals combat damage to a player, " + + "you may cast target instant or sorcery card from " + + "that player's graveyard without paying its mana " + + "cost. If that spell would be put into a graveyard, " + + "exile it instead."; + } +} + +class DeluxeDragsterEffect extends OneShotEffect { + + public DeluxeDragsterEffect() { + super(Outcome.PlayForFree); + this.staticText = "you may cast target instant or sorcery card from " + + "that player's graveyard without paying its mana " + + "cost. If that spell would be put into a graveyard, " + + "exile it instead"; + } + + public DeluxeDragsterEffect(final DeluxeDragsterEffect effect) { + super(effect); + } + + @Override + public DeluxeDragsterEffect copy() { + return new DeluxeDragsterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Card targetCard = game.getCard(source.getFirstTarget()); + if (targetCard != null) { + game.getState().setValue("PlayFromNotOwnHandZone" + targetCard.getId(), Boolean.TRUE); + Boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(targetCard, game, true), + game, true, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + targetCard.getId(), null); + if (cardWasCast) { + ContinuousEffect effect = new DeluxeDragsterReplacementEffect(); + effect.setTargetPointer(new FixedTarget(targetCard.getId(), game.getState().getZoneChangeCounter(targetCard.getId()))); + game.addEffect(effect, source); + } + } + return true; + } + return false; + } +} + +class DeluxeDragsterReplacementEffect extends ReplacementEffectImpl { + + public DeluxeDragsterReplacementEffect() { + super(Duration.EndOfTurn, Outcome.Exile); + staticText = "If that spell would be put into a graveyard this turn, exile it instead"; + } + + public DeluxeDragsterReplacementEffect(final DeluxeDragsterReplacementEffect effect) { + super(effect); + } + + @Override + public DeluxeDragsterReplacementEffect copy() { + return new DeluxeDragsterReplacementEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + ((ZoneChangeEvent) event).setToZone(Zone.EXILED); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + return zEvent.getToZone() == Zone.GRAVEYARD + && event.getTargetId().equals(getTargetPointer().getFirst(game, source)); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/MarchOfTheMachineCommander.java b/Mage.Sets/src/mage/sets/MarchOfTheMachineCommander.java index 962e2cd229..155d3f0894 100644 --- a/Mage.Sets/src/mage/sets/MarchOfTheMachineCommander.java +++ b/Mage.Sets/src/mage/sets/MarchOfTheMachineCommander.java @@ -80,6 +80,7 @@ public final class MarchOfTheMachineCommander extends ExpansionSet { cards.add(new SetCardInfo("Cultivator's Caravan", 354, Rarity.RARE, mage.cards.c.CultivatorsCaravan.class)); cards.add(new SetCardInfo("Curse of Opulence", 274, Rarity.UNCOMMON, mage.cards.c.CurseOfOpulence.class)); cards.add(new SetCardInfo("Death-Greeter's Champion", 30, Rarity.RARE, mage.cards.d.DeathGreetersChampion.class)); + cards.add(new SetCardInfo("Deluxe Dragster", 21, Rarity.RARE, mage.cards.d.DeluxeDragster.class)); cards.add(new SetCardInfo("Despark", 322, Rarity.UNCOMMON, mage.cards.d.Despark.class)); cards.add(new SetCardInfo("Devouring Light", 180, Rarity.UNCOMMON, mage.cards.d.DevouringLight.class)); cards.add(new SetCardInfo("Distant Melody", 220, Rarity.COMMON, mage.cards.d.DistantMelody.class)); From b4f368eee843939423a3929d8dae36a87f7f73ae Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Wed, 12 Apr 2023 10:53:24 -0400 Subject: [PATCH 2/3] [MOC] Use utility functions for Rashmi and Ragavan. --- .../src/mage/cards/r/RashmiAndRagavan.java | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/Mage.Sets/src/mage/cards/r/RashmiAndRagavan.java b/Mage.Sets/src/mage/cards/r/RashmiAndRagavan.java index 6470177fba..27aa16f805 100644 --- a/Mage.Sets/src/mage/cards/r/RashmiAndRagavan.java +++ b/Mage.Sets/src/mage/cards/r/RashmiAndRagavan.java @@ -1,27 +1,26 @@ package mage.cards.r; -import mage.ApprovingObject; import mage.MageInt; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; -import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; import mage.constants.*; +import mage.filter.FilterCard; import mage.filter.StaticFilters; +import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.token.TreasureToken; import mage.game.stack.Spell; import mage.players.Player; import mage.target.common.TargetOpponent; -import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; import mage.watchers.common.SpellsCastWatcher; @@ -49,9 +48,7 @@ public final class RashmiAndRagavan extends CardImpl { // Then you may cast the exiled card without paying its mana cost if it’s a spell with mana value // less than the number of artifacts you control. // If you don’t cast it this way, you may cast it this turn. - Ability ability = new RashmiAndRagavanTriggeredAbility(); - ability.addTarget(new TargetOpponent()); - this.addAbility(ability, new SpellsCastWatcher()); + this.addAbility(new RashmiAndRagavanTriggeredAbility()); } private RashmiAndRagavan(final RashmiAndRagavan card) { @@ -68,6 +65,8 @@ class RashmiAndRagavanTriggeredAbility extends SpellCastControllerTriggeredAbili RashmiAndRagavanTriggeredAbility() { super(new CreateTokenEffect(new TreasureToken()), false); + this.addTarget(new TargetOpponent()); + this.addWatcher(new SpellsCastWatcher()); this.addEffect(new RashmiAndRagavanEffect()); } @@ -125,7 +124,6 @@ class RashmiAndRagavanEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Boolean cardWasCast = false; Player controller = game.getPlayer(source.getControllerId()); Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (controller == null || player == null) { @@ -149,17 +147,12 @@ class RashmiAndRagavanEffect extends OneShotEffect { int artifactCount = new PermanentsOnBattlefieldCount( StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT ).calculate(game, source, this); - if (!card.isLand() && card.getManaValue() < artifactCount && controller.chooseUse(Outcome.PlayForFree, "Cast " + card.getName() - + " without paying its mana cost?", source, game)) { - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); - cardWasCast = controller.cast(controller.chooseAbilityForCast(card, game, true), - game, true, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); - } + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + FilterCard filter = new FilterCard(); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, artifactCount)); + Boolean cardWasCast = CardUtil.castSpellWithAttributesForFree(controller, source, game, new CardsImpl(card), filter); if (!cardWasCast) { - ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, TargetController.YOU, Duration.EndOfTurn, false, true); - effect.setTargetPointer(new FixedTargets(cards, game)); - game.addEffect(effect, source); + CardUtil.makeCardPlayable(game, source, card, Duration.EndOfTurn, false, controller.getId(), null); } return true; } From a3f27507cb9bb8f7d9db5231fcbdab2af8e9fd1c Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Wed, 12 Apr 2023 11:07:50 -0400 Subject: [PATCH 3/3] Change Rashmi and Ragavan back to using PlayFromNotOwnHandZoneTargetEffect because that can be restricted to casting only. --- Mage.Sets/src/mage/cards/r/RashmiAndRagavan.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/r/RashmiAndRagavan.java b/Mage.Sets/src/mage/cards/r/RashmiAndRagavan.java index 8f7b368e62..48d31639ec 100644 --- a/Mage.Sets/src/mage/cards/r/RashmiAndRagavan.java +++ b/Mage.Sets/src/mage/cards/r/RashmiAndRagavan.java @@ -5,8 +5,10 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -21,6 +23,7 @@ import mage.game.permanent.token.TreasureToken; import mage.game.stack.Spell; import mage.players.Player; import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTargets; import mage.util.CardUtil; import mage.watchers.common.SpellsCastWatcher; @@ -147,12 +150,13 @@ class RashmiAndRagavanEffect extends OneShotEffect { int artifactCount = new PermanentsOnBattlefieldCount( StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT ).calculate(game, source, this); - game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); FilterCard filter = new FilterCard(); filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, artifactCount)); Boolean cardWasCast = CardUtil.castSpellWithAttributesForFree(controller, source, game, new CardsImpl(card), filter); if (!cardWasCast) { - CardUtil.makeCardPlayable(game, source, card, Duration.EndOfTurn, false, controller.getId(), null); + ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, TargetController.YOU, Duration.EndOfTurn, false, true); + effect.setTargetPointer(new FixedTargets(cards, game)); + game.addEffect(effect, source); } return true; }