From bd38a1d8513f3fedcb33345e9a4466fd91f3c387 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Wed, 11 May 2022 18:36:08 -0400 Subject: [PATCH] [CLB] Implemented Zevlor, Elturel Exile --- .../src/mage/cards/z/ZevlorElturelExile.java | 200 ++++++++++++++++++ .../CommanderLegendsBattleForBaldursGate.java | 1 + .../MageObjectReferencePredicate.java | 6 + Utils/mtg-cards-data.txt | 1 + 4 files changed, 208 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/z/ZevlorElturelExile.java diff --git a/Mage.Sets/src/mage/cards/z/ZevlorElturelExile.java b/Mage.Sets/src/mage/cards/z/ZevlorElturelExile.java new file mode 100644 index 00000000000..a72e2b21fb7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/z/ZevlorElturelExile.java @@ -0,0 +1,200 @@ +package mage.cards.z; + +import mage.MageInt; +import mage.MageItem; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CopySpellForEachItCouldTargetEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; +import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetPermanent; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class ZevlorElturelExile extends CardImpl { + + public ZevlorElturelExile(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{B}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.TIEFLING); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(4); + this.toughness = new MageInt(2); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // {2}, {T}: When you next cast an instant or sorcery spell that targets only a single opponent or a single permanent an opponent controls this turn, for each other opponent, choose that player or a permanent they control, copy that spell, and the copy targets the chosen permanent. + Ability ability = new SimpleActivatedAbility( + new CreateDelayedTriggeredAbilityEffect(new ZevlorElturelExileTriggeredAbility()), new GenericManaCost(2) + ); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + private ZevlorElturelExile(final ZevlorElturelExile card) { + super(card); + } + + @Override + public ZevlorElturelExile copy() { + return new ZevlorElturelExile(this); + } +} + +class ZevlorElturelExileTriggeredAbility extends DelayedTriggeredAbility { + + ZevlorElturelExileTriggeredAbility() { + super(new ZevlorElturelExileEffect(), Duration.EndOfTurn, true, false); + } + + private ZevlorElturelExileTriggeredAbility(final ZevlorElturelExileTriggeredAbility ability) { + super(ability); + } + + @Override + public ZevlorElturelExileTriggeredAbility copy() { + return new ZevlorElturelExileTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!this.isControlledBy(event.getPlayerId())) { + return false; + } + Spell spell = game.getSpell(event.getTargetId()); + if (spell == null || !spell.isInstantOrSorcery(game)) { + return false; + } + Set targets = spell + .getSpellAbility() + .getTargets() + .stream() + .map(Target::getTargets) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + targets.removeIf(uuid -> game.getPermanent(uuid) == null && game.getPlayer(uuid) == null); + if (targets.size() != 1) { + return false; + } + UUID targetId = targets.iterator().next(); + Set opponents = game.getOpponents(this.getControllerId()); + if (opponents.contains(targetId)) { + this.getEffects().setValue("targetId", targetId); + } else if (opponents.contains(game.getControllerId(targetId))) { + this.getEffects().setValue("targetId", game.getControllerId(targetId)); + } else { + return false; + } + this.getEffects().setValue("savedSpell", spell); + return true; + } + + @Override + public String getRule() { + return "When you next cast an instant or sorcery spell that targets only a single opponent or a single permanent an opponent controls this turn, for each other opponent, choose that player or a permanent they control, copy that spell, and the copy targets the chosen permanent."; + } +} + +class ZevlorElturelExileEffect extends CopySpellForEachItCouldTargetEffect { + + ZevlorElturelExileEffect() { + super(); + } + + @Override + protected StackObject getStackObject(Game game, Ability source) { + return (Spell) getValue("savedSpell"); + } + + @Override + protected Player getPlayer(Game game, Ability source) { + return game.getPlayer(source.getControllerId()); + } + + @Override + protected List prepareCopiesWithTargets(StackObject stackObject, Player player, Ability source, Game game) { + UUID targetId = (UUID) getValue("targetId"); + List predicates = new ArrayList<>(); + for (UUID opponentId : game.getOpponents(player.getId())) { + if (opponentId.equals(targetId)) { + continue; + } + Player opponent = game.getPlayer(opponentId); + if (opponent == null) { + continue; + } + boolean canTargetPlayer = stackObject.canTarget(game, opponentId); + Set targetAb = game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_PERMANENT, + opponentId, source, game + ) + .stream() + .map(MageItem::getId) + .filter(uuid -> stackObject.canTarget(game, uuid)).collect(Collectors.toSet()); + if (!canTargetPlayer && targetAb.isEmpty()) { + continue; + } + if (canTargetPlayer && (targetAb.isEmpty() || player.chooseUse( + outcome, "Have the copy target " + opponent.getName() + " or a permanent they control?", + null, opponent.getName(), "A permanent they control", source, game + ))) { + predicates.add(new MageObjectReferencePredicate(new MageObjectReference(opponentId))); + } else if (!targetAb.isEmpty()) { + if (targetAb.size() == 1) { + predicates.add(new MageObjectReferencePredicate(targetAb.iterator().next(), game)); + continue; + } + FilterPermanent filter = new FilterPermanent("Permanent to target"); + filter.add(Predicates.or(targetAb.stream().map(PermanentIdPredicate::new).collect(Collectors.toSet()))); + TargetPermanent target = new TargetPermanent(filter); + target.setNotTarget(true); + player.choose(outcome, target, source, game); + predicates.add(new MageObjectReferencePredicate(target.getFirstTarget(), game)); + } + } + return predicates; + } + + private ZevlorElturelExileEffect(final ZevlorElturelExileEffect effect) { + super(effect); + } + + @Override + public ZevlorElturelExileEffect copy() { + return new ZevlorElturelExileEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java index 5a32fe64f60..4fea850afc5 100644 --- a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java +++ b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java @@ -33,5 +33,6 @@ public final class CommanderLegendsBattleForBaldursGate extends ExpansionSet { cards.add(new SetCardInfo("Sea of Clouds", 360, Rarity.RARE, mage.cards.s.SeaOfClouds.class)); cards.add(new SetCardInfo("Spire Garden", 361, Rarity.RARE, mage.cards.s.SpireGarden.class)); cards.add(new SetCardInfo("Wand of Wonder", 204, Rarity.RARE, mage.cards.w.WandOfWonder.class)); + cards.add(new SetCardInfo("Zevlor, Elturel Exile", 296, Rarity.RARE, mage.cards.z.ZevlorElturelExile.class)); } } diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/MageObjectReferencePredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/MageObjectReferencePredicate.java index f438b24e950..411360b5f6b 100644 --- a/Mage/src/main/java/mage/filter/predicate/mageobject/MageObjectReferencePredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/MageObjectReferencePredicate.java @@ -8,6 +8,8 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.UUID; + /** * @author TheElk801 */ @@ -15,6 +17,10 @@ public class MageObjectReferencePredicate implements Predicate { private final MageObjectReference mor; + public MageObjectReferencePredicate(UUID sourceId, Game game) { + this(new MageObjectReference(sourceId, game)); + } + public MageObjectReferencePredicate(MageObject mageObject, Game game) { this(new MageObjectReference(mageObject, game)); } diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index c60c74456cc..9d9bf712013 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -44189,6 +44189,7 @@ Fireball|Commander Legends: Battle for Baldur's Gate|175|U|{X}{R}|Sorcery|||This Lightning Bolt|Commander Legends: Battle for Baldur's Gate|187|C|{R}|Instant|||Lightning Bolt deals 3 damage to any target.| Wand of Wonder|Commander Legends: Battle for Baldur's Gate|204|R|{3}{R}|Artifact|||{4}, {T}: Roll a d20. Each opponent exiles cards from the top of their library until they exile an instant or sorcery card, then shuffles the rest into their library. You may cast up to X instant and/or sorcery spells from among cards exiled this way without paying their mana costs.$1-9 & X is one$10-19 & X is two.$20 & X is three.| Minsc & Boo, Timeless Heroes|Commander Legends: Battle for Baldur's Gate|285|M|{2}{R}{G}|Legendary Planeswalker - Minsc|3|When Minsc & Boo, Timeless Heroes enters the battlefield and at the beginning of your upkeep, you may create Boo, a legendary 1/1 red Hamster creature token with trample and haste.$+1: Put three +1/+1 counters on up to one target creature with trample or haste.$−2: Sacrifice a creature. When you do, Minsc & Boo, Timeless Heroes deals X damage to any target, where X is that creature's power. If the sacrificed creature was a Hamster, draw X cards.$Minsc & Boo, Timeless Heroes can be your commander.| +Zevlor, Elturel Exile|Commander Legends: Battle for Baldur's Gate|296|R|{1}{U}{B}{R}|Legendary Creature - Tiefling Warrior|4|2|Haste${2}, {T}: When you next cast an instant or sorcery spell that targets a single opponent or a single permanent an opponent controls this turn, for each other opponent, choose that player or a permanent they control, copy that spell, and the copy targets the chosen permanent.| Bountiful Promenade|Commander Legends: Battle for Baldur's Gate|348|R||Land|||Bountiful Promenade enters the battlefield tapped unless you have two or more opponents.${T}: Add {G} or {W}.| Luxury Suite|Commander Legends: Battle for Baldur's Gate|355|R||Land|||Luxury Suite enters the battlefield tapped unless you have two or more opponents.${T}: Add {B} or {R}.| Morphic Pool|Commander Legends: Battle for Baldur's Gate|357|R||Land|||Morphic Pool enters the battlefield tapped unless you have two or more opponents.${T}: Add {U} or {B}.|