From 91300cebedf762e435a168c9be1d84287a1fa0d2 Mon Sep 17 00:00:00 2001 From: Daniel Bomar Date: Wed, 10 Nov 2021 11:59:22 -0600 Subject: [PATCH] [VOW] Implemented Chandra, Dressed to Kill --- .../mage/cards/c/ChandraDressedToKill.java | 175 ++++++++++++++++++ .../src/mage/sets/InnistradCrimsonVow.java | 1 + .../effects/common/DamageTargetEffect.java | 14 +- .../emblems/ChandraDressedToKillEmblem.java | 76 ++++++++ 4 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/c/ChandraDressedToKill.java create mode 100644 Mage/src/main/java/mage/game/command/emblems/ChandraDressedToKillEmblem.java diff --git a/Mage.Sets/src/mage/cards/c/ChandraDressedToKill.java b/Mage.Sets/src/mage/cards/c/ChandraDressedToKill.java new file mode 100644 index 0000000000..ead71a7256 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChandraDressedToKill.java @@ -0,0 +1,175 @@ +package mage.cards.c; + +import java.util.Set; +import java.util.UUID; + +import mage.MageObject; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.GetEmblemEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; +import mage.abilities.effects.mana.AddManaToManaPoolSourceControllerEffect; +import mage.cards.Card; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.common.FilterPlayerOrPlaneswalker; +import mage.game.Game; +import mage.game.command.emblems.ChandraDressedToKillEmblem; +import mage.players.Player; +import mage.target.common.TargetPlayerOrPlaneswalker; +import mage.target.targetpointer.FixedTarget; +import mage.target.targetpointer.FixedTargets; +import mage.util.CardUtil; + +/** + * + * @author weirddan455 + */ +public final class ChandraDressedToKill extends CardImpl { + + private static final FilterPlayerOrPlaneswalker filter = new FilterPlayerOrPlaneswalker(); + + public ChandraDressedToKill(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{R}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.CHANDRA); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(3)); + + // +1: Add {R}. Chandra, Dressed to Kill deals 1 damage to up to one target player or planeswalker. + Ability ability = new LoyaltyAbility(new AddManaToManaPoolSourceControllerEffect(new Mana(ManaType.RED, 1)), 1); + ability.addEffect(new DamageTargetEffect(1)); + ability.addTarget(new TargetPlayerOrPlaneswalker(0, 1, filter, false)); + this.addAbility(ability); + + // +1: Exile the top card of your library. If it's red, you may cast it this turn. + this.addAbility(new LoyaltyAbility(new ChandraDressedToKillExile1Effect(), 1)); + + // −7: Exile the top five cards of your library. You may cast red spells from among them this turn. + // You get an emblem with "Whenever you cast a red spell, this emblem deals X damage to any target, where X is the amount of mana spent to cast that spell." + ability = new LoyaltyAbility(new ChandraDressedToKillExile5Effect(), -7); + ability.addEffect(new GetEmblemEffect(new ChandraDressedToKillEmblem())); + this.addAbility(ability); + } + + private ChandraDressedToKill(final ChandraDressedToKill card) { + super(card); + } + + @Override + public ChandraDressedToKill copy() { + return new ChandraDressedToKill(this); + } +} + +// +1 and -7 need to be different abilities. +// The +1 ability checks if the card is red as it resolves. The -7 checks if it's red at the time you would cast it. +// Ex. Playing a Painter's Servant on red after the ability resolves. +// Ex. MDFCs like Mila, Crafty Companion. Back half cannot be played from the +1 but it can from the -7. +class ChandraDressedToKillExile1Effect extends OneShotEffect { + + public ChandraDressedToKillExile1Effect() { + super(Outcome.Benefit); + staticText = "Exile the top card of your library. If it's red, you may cast it this turn"; + } + + private ChandraDressedToKillExile1Effect(final ChandraDressedToKillExile1Effect effect) { + super(effect); + } + + @Override + public ChandraDressedToKillExile1Effect copy() { + return new ChandraDressedToKillExile1Effect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Card card = controller.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + MageObject sourceObject = source.getSourceObject(game); + String exileName = sourceObject == null ? null : sourceObject.getIdName(); + controller.moveCardsToExile(card, source, game, true, exileId, exileName); + if (game.getState().getZone(card.getId()) == Zone.EXILED && card.getColor(game).isRed()) { + game.addEffect(new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, TargetController.YOU, Duration.EndOfTurn, false, true) + .setTargetPointer(new FixedTarget(card, game)), source); + } + return true; + } +} + +class ChandraDressedToKillExile5Effect extends OneShotEffect { + + public ChandraDressedToKillExile5Effect() { + super(Outcome.Benefit); + staticText = "Exile the top five cards of your library. You may cast red spells from among them this turn"; + } + + private ChandraDressedToKillExile5Effect(final ChandraDressedToKillExile5Effect effect) { + super(effect); + } + + @Override + public ChandraDressedToKillExile5Effect copy() { + return new ChandraDressedToKillExile5Effect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Set cards = controller.getLibrary().getTopCards(game, 5); + if (cards.isEmpty()) { + return false; + } + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + MageObject sourceObject = source.getSourceObject(game); + String exileName = sourceObject == null ? null : sourceObject.getIdName(); + controller.moveCardsToExile(cards, source, game, true, exileId, exileName); + cards.removeIf(card -> !Zone.EXILED.equals(game.getState().getZone(card.getId()))); + if (!cards.isEmpty()) { + game.addEffect(new ChandraDressedToKillPlayEffect() + .setTargetPointer(new FixedTargets(cards, game)), source); + } + return true; + } +} + +// Only used for the -7 ability (see comment at top) +class ChandraDressedToKillPlayEffect extends PlayFromNotOwnHandZoneTargetEffect { + + public ChandraDressedToKillPlayEffect() { + super(Zone.EXILED, TargetController.YOU, Duration.EndOfTurn, false, true); + } + + private ChandraDressedToKillPlayEffect(final ChandraDressedToKillPlayEffect effect) { + super(effect); + } + + @Override + public ChandraDressedToKillPlayEffect copy() { + return new ChandraDressedToKillPlayEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + if (!super.applies(objectId, affectedAbility, source, game, playerId)) { + return false; + } + Card card = game.getCard(objectId); + return card != null && card.getColor(game).isRed(); + } +} diff --git a/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java b/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java index 515636f8d2..08bd9d7027 100644 --- a/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java +++ b/Mage.Sets/src/mage/sets/InnistradCrimsonVow.java @@ -78,6 +78,7 @@ public final class InnistradCrimsonVow extends ExpansionSet { cards.add(new SetCardInfo("Cemetery Protector", 6, Rarity.MYTHIC, mage.cards.c.CemeteryProtector.class)); cards.add(new SetCardInfo("Cemetery Prowler", 191, Rarity.MYTHIC, mage.cards.c.CemeteryProwler.class)); cards.add(new SetCardInfo("Ceremonial Knife", 254, Rarity.COMMON, mage.cards.c.CeremonialKnife.class)); + cards.add(new SetCardInfo("Chandra, Dressed to Kill", 149, Rarity.MYTHIC, mage.cards.c.ChandraDressedToKill.class)); cards.add(new SetCardInfo("Change of Fortune", 150, Rarity.RARE, mage.cards.c.ChangeOfFortune.class)); cards.add(new SetCardInfo("Child of the Pack", 234, Rarity.UNCOMMON, mage.cards.c.ChildOfThePack.class)); cards.add(new SetCardInfo("Chill of the Grave", 51, Rarity.COMMON, mage.cards.c.ChillOfTheGrave.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java index 55f24bf7cd..bb4608f7bf 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java @@ -11,6 +11,7 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; +import mage.util.CardUtil; import java.util.UUID; @@ -159,10 +160,21 @@ public class DamageTargetEffect extends OneShotEffect { sb.append(targetDescription); } else { if (!mode.getTargets().isEmpty()) { - String targetName = mode.getTargets().get(0).getTargetName(); + Target firstTarget = mode.getTargets().get(0); + String targetName = firstTarget.getTargetName(); if (targetName.contains("any")) { sb.append(targetName); } else { + if (firstTarget.getMinNumberOfTargets() == 0) { + int maxTargets = firstTarget.getMaxNumberOfTargets(); + if (maxTargets == Integer.MAX_VALUE) { + sb.append("any number of "); + } else { + sb.append("up to "); + sb.append(CardUtil.numberToText(maxTargets)); + sb.append(' '); + } + } sb.append("target ").append(targetName); } } else { diff --git a/Mage/src/main/java/mage/game/command/emblems/ChandraDressedToKillEmblem.java b/Mage/src/main/java/mage/game/command/emblems/ChandraDressedToKillEmblem.java new file mode 100644 index 0000000000..92e56f2bd3 --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/ChandraDressedToKillEmblem.java @@ -0,0 +1,76 @@ +package mage.game.command.emblems; + +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.game.Game; +import mage.game.command.Emblem; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.common.TargetAnyTarget; +import mage.watchers.common.ManaPaidSourceWatcher; + +/** + * + * @author weirddan455 + */ +public class ChandraDressedToKillEmblem extends Emblem { + + private static final FilterSpell filter = new FilterSpell("a red spell"); + + static { + filter.add(new ColorPredicate(ObjectColor.RED)); + } + + // Whenever you cast a red spell, this emblem deals X damage to any target, where X is the amount of mana spent to cast that spell. + public ChandraDressedToKillEmblem() { + this.setName("Emblem Chandra"); + Ability ability = new SpellCastControllerTriggeredAbility(Zone.COMMAND, new ChandraDressedToKillEmblemEffect(), filter, false, true); + ability.addTarget(new TargetAnyTarget()); + this.getAbilities().add(ability); + } +} + +class ChandraDressedToKillEmblemEffect extends OneShotEffect { + + public ChandraDressedToKillEmblemEffect() { + super(Outcome.Damage); + staticText = "this emblem deals X damage to any target, where X is the amount of mana spent to cast that spell"; + } + + private ChandraDressedToKillEmblemEffect(final ChandraDressedToKillEmblemEffect effect) { + super(effect); + } + + @Override + public ChandraDressedToKillEmblemEffect copy() { + return new ChandraDressedToKillEmblemEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Spell spell = (Spell) getValue("spellCast"); + if (controller == null || spell == null) { + return false; + } + int manaPaid = ManaPaidSourceWatcher.getTotalPaid(spell.getId(), game); + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent != null) { + permanent.damage(manaPaid, source, game); + return true; + } + Player player = game.getPlayer(source.getFirstTarget()); + if (player != null) { + player.damage(manaPaid, source, game); + return true; + } + return false; + } +}