From f8a159839e92b0326a8eb1f05682062c0dc9cccd Mon Sep 17 00:00:00 2001 From: jimga150 Date: Mon, 20 May 2024 23:51:48 -0400 Subject: [PATCH] [WHO] Implement Weeping Angel (#12236) --- Mage.Sets/src/mage/cards/w/WeepingAngel.java | 151 ++++++++++++++++++ Mage.Sets/src/mage/sets/DoctorWho.java | 1 + .../cards/single/who/WeepingAngelTests.java | 61 +++++++ 3 files changed, 213 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/w/WeepingAngel.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/who/WeepingAngelTests.java diff --git a/Mage.Sets/src/mage/cards/w/WeepingAngel.java b/Mage.Sets/src/mage/cards/w/WeepingAngel.java new file mode 100644 index 00000000000..b190976e1bc --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WeepingAngel.java @@ -0,0 +1,151 @@ +package mage.cards.w; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastOpponentTriggeredAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.PreventionEffectImpl; +import mage.cards.Card; +import mage.constants.*; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.DamagePermanentEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author jimga150 + */ +public final class WeepingAngel extends CardImpl { + + public WeepingAngel(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}{B}"); + + this.subtype.add(SubType.ALIEN); + this.subtype.add(SubType.ANGEL); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever an opponent casts a creature spell, Weeping Angel isn't a creature until end of turn. + this.addAbility(new SpellCastOpponentTriggeredAbility( + new WeepingAngelMarbleizeEffect(), StaticFilters.FILTER_SPELL_A_CREATURE, false + )); + + // If Weeping Angel would deal combat damage to a creature, prevent that damage and that creature's owner shuffles it into their library. + this.addAbility(new SimpleStaticAbility(new WeepingAngelDamageEffect())); + } + + private WeepingAngel(final WeepingAngel card) { + super(card); + } + + @Override + public WeepingAngel copy() { + return new WeepingAngel(this); + } +} + +// Adapted from LoseCreatureTypeSourceEffect +class WeepingAngelMarbleizeEffect extends ContinuousEffectImpl { + + WeepingAngelMarbleizeEffect() { + super(Duration.EndOfTurn, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment); + staticText = "{this} isn't a creature until end of turn."; + } + + private WeepingAngelMarbleizeEffect(final WeepingAngelMarbleizeEffect effect) { + super(effect); + } + + @Override + public WeepingAngelMarbleizeEffect copy() { + return new WeepingAngelMarbleizeEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + if (duration.isOnlyValidIfNoZoneChange()) { + // If source permanent is no longer onto battlefield discard the effect + if (source.getSourcePermanentIfItStillExists(game) == null) { + discard(); + } + } + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent == null) { + return false; + } + permanent.removeCardType(game, CardType.CREATURE); + if (!permanent.isTribal(game)) { + permanent.removeAllCreatureTypes(game); + } + if (permanent.isAttacking() || permanent.getBlocking() > 0) { + permanent.removeFromCombat(game); + } + return true; + } +} + +// Based on PreventDamageAndRemoveCountersEffect +class WeepingAngelDamageEffect extends PreventionEffectImpl { + + WeepingAngelDamageEffect() { + super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, true, false); + staticText = "If {this} would deal combat damage to a creature, " + + "prevent that damage and that creature's owner shuffles it into their library."; + } + + private WeepingAngelDamageEffect(final WeepingAngelDamageEffect effect) { + super(effect); + } + + @Override + public WeepingAngelDamageEffect copy() { + return new WeepingAngelDamageEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + game.preventDamage(event, source, game, Integer.MAX_VALUE); + Card card = game.getPermanent(event.getTargetId()); + if (card == null) { + return false; + } + Player owner = game.getPlayer(card.getOwnerId()); + if (owner != null) { + owner.shuffleCardsToLibrary(card, game, source); + } + return false; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId()); + if (permanent == null || !permanent.isCreature(game)){ + return false; + } + return event.getSourceId().equals(source.getSourceId()) && ((DamagePermanentEvent) event).isCombatDamage(); + } +} diff --git a/Mage.Sets/src/mage/sets/DoctorWho.java b/Mage.Sets/src/mage/sets/DoctorWho.java index 0ea39dbda59..2d7c1edb421 100644 --- a/Mage.Sets/src/mage/sets/DoctorWho.java +++ b/Mage.Sets/src/mage/sets/DoctorWho.java @@ -284,6 +284,7 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Waterlogged Grove", 331, Rarity.RARE, mage.cards.w.WaterloggedGrove.class)); cards.add(new SetCardInfo("Wayfarer's Bauble", 256, Rarity.COMMON, mage.cards.w.WayfarersBauble.class)); cards.add(new SetCardInfo("Wedding Ring", 213, Rarity.MYTHIC, mage.cards.w.WeddingRing.class)); + cards.add(new SetCardInfo("Weeping Angel", 168, Rarity.RARE, mage.cards.w.WeepingAngel.class)); cards.add(new SetCardInfo("Wibbly-wobbly, Timey-wimey", 62, Rarity.COMMON, mage.cards.w.WibblyWobblyTimeyWimey.class)); cards.add(new SetCardInfo("Wound Reflection", 223, Rarity.RARE, mage.cards.w.WoundReflection.class)); cards.add(new SetCardInfo("Wreck and Rebuild", 169, Rarity.UNCOMMON, mage.cards.w.WreckAndRebuild.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/who/WeepingAngelTests.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/WeepingAngelTests.java new file mode 100644 index 00000000000..9b7b3a99ed9 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/who/WeepingAngelTests.java @@ -0,0 +1,61 @@ +package org.mage.test.cards.single.who; + +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author jimga150 + */ +public class WeepingAngelTests extends CardTestPlayerBase { + + @Test + public void testCreatureTypeLoss() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Weeping Angel"); + addCard(Zone.HAND, playerB, "Memnite"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Memnite", true); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertNotType("Weeping Angel", CardType.CREATURE); + } + + @Test + public void testCreatureTypeRegain() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Weeping Angel"); + addCard(Zone.HAND, playerB, "Memnite"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Memnite", true); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.UPKEEP); + execute(); + + assertType("Weeping Angel", CardType.CREATURE, true); + } + + @Test + public void testDamageEffect() { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Weeping Angel"); + addCard(Zone.BATTLEFIELD, playerB, "Impervious Greatwurm"); + + attack(1, playerA, "Weeping Angel", playerB); + block(1, playerB, "Impervious Greatwurm", "Weeping Angel"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLibraryCount(playerB, "Impervious Greatwurm", 1); + assertPermanentCount(playerB, "Impervious Greatwurm", 0); + assertPermanentCount(playerA, "Weeping Angel", 1); + } +}