From 244f8d24cbf362f9c10bfbffc00f5974a35ec30e Mon Sep 17 00:00:00 2001 From: Matthew Wilson Date: Wed, 21 Feb 2024 07:53:46 +0200 Subject: [PATCH] [MKM] Implement Kylox's Voltstrider; adjust Collect evidence cost (#11800) --- .../src/mage/cards/k/KyloxsVoltstrider.java | 163 ++++++++++++++++++ .../src/mage/sets/MurdersAtKarlovManor.java | 1 + .../costs/common/CollectEvidenceCost.java | 14 +- 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/k/KyloxsVoltstrider.java diff --git a/Mage.Sets/src/mage/cards/k/KyloxsVoltstrider.java b/Mage.Sets/src/mage/cards/k/KyloxsVoltstrider.java new file mode 100644 index 00000000000..81d48b65a17 --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KyloxsVoltstrider.java @@ -0,0 +1,163 @@ +package mage.cards.k; + +import java.util.UUID; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.CollectEvidenceCost; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.continuous.AddCardTypeSourceEffect; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.CrewAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInExile; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * + * @author DominionSpy + */ +public final class KyloxsVoltstrider extends CardImpl { + + public KyloxsVoltstrider(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{U}{R}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Collect evidence 6: Kylox's Voltstrider becomes an artifact creature until end of turn. + this.addAbility(new SimpleActivatedAbility( + new AddCardTypeSourceEffect(Duration.EndOfTurn, CardType.ARTIFACT, CardType.CREATURE) + .setText("{this} becomes an artifact creature until end of turn"), + new CollectEvidenceCost(6, true))); + + // Whenever Kylox's Voltstrider attacks, you may cast an instant or sorcery spell from among cards exiled with it. + // If that spell would be put into a graveyard, put it on the bottom of its owner's library instead. + this.addAbility(new AttacksTriggeredAbility(new KyloxsVoltstriderEffect(), true)); + + // Crew 2 + this.addAbility(new CrewAbility(2)); + + } + + private KyloxsVoltstrider(final KyloxsVoltstrider card) { + super(card); + } + + @Override + public KyloxsVoltstrider copy() { + return new KyloxsVoltstrider(this); + } +} + +class KyloxsVoltstriderEffect extends OneShotEffect { + + KyloxsVoltstriderEffect() { + super(Outcome.Benefit); + this.staticText = "you may cast an instant or sorcery spell from among cards exiled with it. " + + "If that spell would be put into a graveyard, put it on the bottom of its owner's library instead."; + } + + private KyloxsVoltstriderEffect(final KyloxsVoltstriderEffect effect) { + super(effect); + } + + @Override + public KyloxsVoltstriderEffect copy() { + return new KyloxsVoltstriderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), + game.getState().getZoneChangeCounter(source.getSourceId())); + ExileZone exileZone = game.getExile().getExileZone(exileId); + if (controller == null || exileZone == null) { + return false; + } + + TargetCard target = new TargetCardInExile(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, exileId); + target.withNotTarget(true); + if (!target.canChoose(source.getControllerId(), source, game)) { + return true; + } + controller.chooseTarget(outcome, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE); + controller.cast(controller.chooseAbilityForCast(card, game, false), + game, false, new ApprovingObject(source, game)); + game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null); + + // If that spell would be put into a graveyard, put it on the bottom of its owner's library instead. + ContinuousEffect effect = new KyloxsVoltstriderReplacementEffect(); + effect.setTargetPointer(new FixedTarget(card, game)); + game.addEffect(effect, source); + } + return true; + } +} + +class KyloxsVoltstriderReplacementEffect extends ReplacementEffectImpl { + + KyloxsVoltstriderReplacementEffect() { + super(Duration.Custom, Outcome.Exile); + staticText = "If that spell would be put into a graveyard, put it on the bottom of its owner's library instead."; + } + + private KyloxsVoltstriderReplacementEffect(final KyloxsVoltstriderReplacementEffect effect) { + super(effect); + } + + @Override + public KyloxsVoltstriderReplacementEffect copy() { + return new KyloxsVoltstriderReplacementEffect(this); + } + + @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(((FixedTarget) getTargetPointer()).getTarget()) && + ((FixedTarget) getTargetPointer()).getZoneChangeCounter() == + game.getState().getZoneChangeCounter(zEvent.getTargetId()); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null || controller == null) { + return false; + } + + controller.putCardsOnBottomOfLibrary(card, game, source, false); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java b/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java index b9673025ee0..6b430a88023 100644 --- a/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java +++ b/Mage.Sets/src/mage/sets/MurdersAtKarlovManor.java @@ -143,6 +143,7 @@ public final class MurdersAtKarlovManor extends ExpansionSet { cards.add(new SetCardInfo("Krenko's Buzzcrusher", 136, Rarity.RARE, mage.cards.k.KrenkosBuzzcrusher.class)); cards.add(new SetCardInfo("Krenko, Baron of Tin Street", 135, Rarity.RARE, mage.cards.k.KrenkoBaronOfTinStreet.class)); cards.add(new SetCardInfo("Krovod Haunch", 21, Rarity.UNCOMMON, mage.cards.k.KrovodHaunch.class)); + cards.add(new SetCardInfo("Kylox's Voltstrider", 215, Rarity.MYTHIC, mage.cards.k.KyloxsVoltstrider.class)); cards.add(new SetCardInfo("Kylox, Visionary Inventor", 214, Rarity.RARE, mage.cards.k.KyloxVisionaryInventor.class)); cards.add(new SetCardInfo("Lamplight Phoenix", 137, Rarity.RARE, mage.cards.l.LamplightPhoenix.class)); cards.add(new SetCardInfo("Lazav, Wearer of Faces", 216, Rarity.RARE, mage.cards.l.LazavWearerOfFaces.class)); diff --git a/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java b/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java index 83242ad81a5..363bf5c1605 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/CollectEvidenceCost.java @@ -14,6 +14,7 @@ import mage.game.events.GameEvent; import mage.players.Player; import mage.target.Target; import mage.target.common.TargetCardInYourGraveyard; +import mage.util.CardUtil; import java.awt.*; import java.util.Objects; @@ -25,16 +26,23 @@ import java.util.UUID; public class CollectEvidenceCost extends CostImpl { private final int amount; + private final boolean withSource; public CollectEvidenceCost(int amount) { + this(amount, false); + } + + public CollectEvidenceCost(int amount, boolean withSource) { super(); this.amount = amount; + this.withSource = withSource; this.text = "collect evidence " + amount; } private CollectEvidenceCost(final CollectEvidenceCost cost) { super(cost); this.amount = cost.amount; + this.withSource = cost.withSource; } @Override @@ -83,7 +91,11 @@ public class CollectEvidenceCost extends CostImpl { .mapToInt(MageObject::getManaValue) .sum() >= amount; if (paid) { - player.moveCards(cards, Zone.EXILED, source, game); + if (withSource) { + player.moveCardsToExile(cards.getCards(game), source, game, true, CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source)); + } else { + player.moveCards(cards, Zone.EXILED, source, game); + } game.fireEvent(GameEvent.getEvent( GameEvent.EventType.EVIDENCE_COLLECTED, source.getSourceId(), source, source.getControllerId(), amount