From 03926f114f7362e0e369587445be75e3a337f956 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Thu, 11 Nov 2021 19:47:20 -0500 Subject: [PATCH] [VOW] Implemented Voltaic Visionary / Volt-Charged Berserker --- .../mage/cards/v/VoltChargedBerserker.java | 39 +++++ .../src/mage/cards/v/VoltaicVisionary.java | 145 ++++++++++++++++++ .../src/mage/sets/InnistradCrimsonVow.java | 2 + .../ExileTopXMayPlayUntilEndOfTurnEffect.java | 6 +- 4 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/v/VoltChargedBerserker.java create mode 100644 Mage.Sets/src/mage/cards/v/VoltaicVisionary.java diff --git a/Mage.Sets/src/mage/cards/v/VoltChargedBerserker.java b/Mage.Sets/src/mage/cards/v/VoltChargedBerserker.java new file mode 100644 index 00000000000..0663ca39e0a --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VoltChargedBerserker.java @@ -0,0 +1,39 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.abilities.common.CantBlockAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class VoltChargedBerserker extends CardImpl { + + public VoltChargedBerserker(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.BERSERKER); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + this.color.setRed(true); + this.nightCard = true; + + // Volt-Charged Berserker can't block. + this.addAbility(new CantBlockAbility()); + } + + private VoltChargedBerserker(final VoltChargedBerserker card) { + super(card); + } + + @Override + public VoltChargedBerserker copy() { + return new VoltChargedBerserker(this); + } +} diff --git a/Mage.Sets/src/mage/cards/v/VoltaicVisionary.java b/Mage.Sets/src/mage/cards/v/VoltaicVisionary.java new file mode 100644 index 00000000000..b1892b10664 --- /dev/null +++ b/Mage.Sets/src/mage/cards/v/VoltaicVisionary.java @@ -0,0 +1,145 @@ +package mage.cards.v; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.common.DamageControllerEffect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEndOfTurnEffect; +import mage.abilities.effects.common.TransformSourceEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.util.CardUtil; +import mage.watchers.Watcher; + +import java.util.*; + +/** + * @author TheElk801 + */ +public final class VoltaicVisionary extends CardImpl { + + public VoltaicVisionary(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + this.secondSideCardClazz = mage.cards.v.VoltChargedBerserker.class; + + // {T}: Voltaic Visionary deals 2 damage to you. Exile the top card of your library. You may play that card this turn. Activate only as a sorcery. + Ability ability = new ActivateAsSorceryActivatedAbility( + new DamageControllerEffect(2), new TapSourceCost() + ); + ability.addEffect(new ExileTopXMayPlayUntilEndOfTurnEffect(1)); + this.addAbility(ability); + + // When you play a card exiled with Voltaic Visionary, transform Voltaic Visionary. + this.addAbility(new TransformAbility()); + this.addAbility(new VoltaicVisionaryTriggeredAbility()); + } + + private VoltaicVisionary(final VoltaicVisionary card) { + super(card); + } + + @Override + public VoltaicVisionary copy() { + return new VoltaicVisionary(this); + } +} + +class VoltaicVisionaryTriggeredAbility extends TriggeredAbilityImpl { + + VoltaicVisionaryTriggeredAbility() { + super(Zone.BATTLEFIELD, new TransformSourceEffect()); + this.addWatcher(new VoltaicVisionaryWatcher()); + } + + private VoltaicVisionaryTriggeredAbility(final VoltaicVisionaryTriggeredAbility ability) { + super(ability); + } + + @Override + public VoltaicVisionaryTriggeredAbility copy() { + return new VoltaicVisionaryTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST + || event.getType() == GameEvent.EventType.LAND_PLAYED; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!this.isControlledBy(event.getPlayerId())) { + return false; + } + Card card = game.getCard(event.getTargetId()); + return VoltaicVisionaryWatcher.checkCard(card, this, game); + } + + @Override + public String getTriggerPhrase() { + return "When you play a card exiled with {this}, "; + } +} + +class VoltaicVisionaryWatcher extends Watcher { + + private final Map> map = new HashMap<>(); + private static final Set emptySet = Collections.unmodifiableSet(new HashSet<>()); + + VoltaicVisionaryWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.ZONE_CHANGE + || ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { + return; + } + Card card = game.getCard(event.getTargetId()); + UUID exileId = game + .getExile() + .getExileZones() + .stream().filter(exileZone -> exileZone.contains(card)) + .map(ExileZone::getId) + .findFirst() + .orElse(null); + if (exileId == null) { + return; + } + map.computeIfAbsent(exileId, x -> new HashSet<>()).add(new MageObjectReference(card, game)); + } + + @Override + public void reset() { + map.clear(); + super.reset(); + } + + static boolean checkCard(Card card, Ability source, Game game) { + return card != null + && game.getState() + .getWatcher(VoltaicVisionaryWatcher.class) + .map + .getOrDefault(CardUtil.getCardExileZoneId(game, source), emptySet) + .contains(new MageObjectReference(card, game, -1)); + } +} diff --git a/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java b/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java index 38ce3a57d29..107aeca8506 100644 --- a/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java +++ b/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java @@ -328,6 +328,8 @@ public final class InnistradCrimsonVow extends ExpansionSet { cards.add(new SetCardInfo("Voldaren Bloodcaster", 137, Rarity.RARE, mage.cards.v.VoldarenBloodcaster.class)); cards.add(new SetCardInfo("Voldaren Epicure", 182, Rarity.COMMON, mage.cards.v.VoldarenEpicure.class)); cards.add(new SetCardInfo("Voldaren Estate", 267, Rarity.RARE, mage.cards.v.VoldarenEstate.class)); + cards.add(new SetCardInfo("Volt-Charged Berserker", 183, Rarity.UNCOMMON, mage.cards.v.VoltChargedBerserker.class)); + cards.add(new SetCardInfo("Voltaic Visionary", 183, Rarity.UNCOMMON, mage.cards.v.VoltaicVisionary.class)); cards.add(new SetCardInfo("Wandering Mind", 251, Rarity.UNCOMMON, mage.cards.w.WanderingMind.class)); cards.add(new SetCardInfo("Wanderlight Spirit", 86, Rarity.COMMON, mage.cards.w.WanderlightSpirit.class)); cards.add(new SetCardInfo("Wash Away", 87, Rarity.UNCOMMON, mage.cards.w.WashAway.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEndOfTurnEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEndOfTurnEffect.java index 2f7bf62e9f9..4143d866f09 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEndOfTurnEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEndOfTurnEffect.java @@ -1,6 +1,5 @@ package mage.abilities.effects.common; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; @@ -52,15 +51,14 @@ public class ExileTopXMayPlayUntilEndOfTurnEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = game.getObject(source.getSourceId()); - if (controller == null || sourceObject == null) { + if (controller == null) { return false; } Set cards = controller.getLibrary().getTopCards(game, amount); if (cards.isEmpty()) { return true; } - controller.moveCardsToExile(cards, source, game, true, source.getSourceId(), sourceObject.getIdName()); + controller.moveCardsToExile(cards, source, game, true, CardUtil.getExileZoneId(game, source), CardUtil.getSourceLogName(game, source)); // remove cards that could not be moved to exile cards.removeIf(card -> !Zone.EXILED.equals(game.getState().getZone(card.getId()))); if (!cards.isEmpty()) {