From fab00d2f27787bae1934aedae3cb1417e05aabdc Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Thu, 31 Aug 2023 01:16:22 +0200 Subject: [PATCH] [WOE] Implement Curse of the Werefox (#11009) * [WOE] Implement Curse of the Werefox * apply review * Fix aura (and equipment?) tokens not checking for protection on target * fix targetting of reflexive trigger, by creating a custom fight effect. * fix reflexive ability target. --- .../src/mage/cards/c/CurseOfTheWerefox.java | 118 ++++++++++++++++++ Mage.Sets/src/mage/sets/WildsOfEldraine.java | 1 + .../single/woe/CurseOfTheWerefoxTest.java | 88 +++++++++++++ .../CreateRoleAttachedTargetEffect.java | 6 +- .../abilities/keyword/ProtectionAbility.java | 6 + 5 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/c/CurseOfTheWerefox.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/woe/CurseOfTheWerefoxTest.java diff --git a/Mage.Sets/src/mage/cards/c/CurseOfTheWerefox.java b/Mage.Sets/src/mage/cards/c/CurseOfTheWerefox.java new file mode 100644 index 00000000000..007341d08ef --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CurseOfTheWerefox.java @@ -0,0 +1,118 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateRoleAttachedTargetEffect; +import mage.abilities.effects.common.FightTargetsEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.RoleType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.GameLog; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class CurseOfTheWerefox extends CardImpl { + + public CurseOfTheWerefox(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); + + // Create a Monster Role token attached to target creature you control. When you do, that creature fights up to one target creature you don't control. + this.getSpellAbility().addEffect(new CurseOfTheWerefoxEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + } + + private CurseOfTheWerefox(final CurseOfTheWerefox card) { + super(card); + } + + @Override + public CurseOfTheWerefox copy() { + return new CurseOfTheWerefox(this); + } +} + +class CurseOfTheWerefoxEffect extends OneShotEffect { + + CurseOfTheWerefoxEffect() { + super(Outcome.Benefit); + staticText = "create a Monster Role token attached to target creature you control. " + + "When you do, that creature fights up to one target creature you don't control"; + } + + private CurseOfTheWerefoxEffect(final CurseOfTheWerefoxEffect effect) { + super(effect); + } + + @Override + public CurseOfTheWerefoxEffect copy() { + return new CurseOfTheWerefoxEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent target = game.getPermanent(source.getFirstTarget()); + if (target == null) { + return false; + } + + boolean didCreate = + new CreateRoleAttachedTargetEffect(RoleType.MONSTER) + .setTargetPointer(new FixedTarget(target, game)) + .apply(game, source); + if (!didCreate) { + return false; + } + + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( + new CurseOfTheWerefoxFightEffect(), false, + "that creature fights up to one target creature you don't control" + ); + ability.getEffects().setTargetPointer(new FixedTarget(target.getId(), game)); + ability.addTarget(new TargetCreaturePermanent(0, 1, StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL, false)); + game.fireReflexiveTriggeredAbility(ability, source); + return true; + } +} + + +class CurseOfTheWerefoxFightEffect extends OneShotEffect { + + CurseOfTheWerefoxFightEffect() { + super(Outcome.Damage); + } + + private CurseOfTheWerefoxFightEffect(final CurseOfTheWerefoxFightEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent triggeredCreature = game.getPermanent(this.targetPointer.getFirst(game, source)); + Permanent target = game.getPermanent(source.getFirstTarget()); + if (triggeredCreature != null + && target != null + && triggeredCreature.isCreature(game) + && target.isCreature(game)) { + return triggeredCreature.fight(target, source, game); + } + return false; + } + + @Override + public CurseOfTheWerefoxFightEffect copy() { + return new CurseOfTheWerefoxFightEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/WildsOfEldraine.java b/Mage.Sets/src/mage/sets/WildsOfEldraine.java index 6ed327e8e6b..992f111d406 100644 --- a/Mage.Sets/src/mage/sets/WildsOfEldraine.java +++ b/Mage.Sets/src/mage/sets/WildsOfEldraine.java @@ -65,6 +65,7 @@ public final class WildsOfEldraine extends ExpansionSet { cards.add(new SetCardInfo("Cooped Up", 8, Rarity.COMMON, mage.cards.c.CoopedUp.class)); cards.add(new SetCardInfo("Cruel Somnophage", 222, Rarity.RARE, mage.cards.c.CruelSomnophage.class)); cards.add(new SetCardInfo("Crystal Grotto", 254, Rarity.COMMON, mage.cards.c.CrystalGrotto.class)); + cards.add(new SetCardInfo("Curse of the Werefox", 167, Rarity.COMMON, mage.cards.c.CurseOfTheWerefox.class)); cards.add(new SetCardInfo("Cursed Courtier", 9, Rarity.UNCOMMON, mage.cards.c.CursedCourtier.class)); cards.add(new SetCardInfo("Cut In", 125, Rarity.COMMON, mage.cards.c.CutIn.class)); cards.add(new SetCardInfo("Decadent Dragon", 223, Rarity.RARE, mage.cards.d.DecadentDragon.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/woe/CurseOfTheWerefoxTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/woe/CurseOfTheWerefoxTest.java new file mode 100644 index 00000000000..6095dcc866a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/woe/CurseOfTheWerefoxTest.java @@ -0,0 +1,88 @@ +package org.mage.test.cards.single.woe; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class CurseOfTheWerefoxTest extends CardTestPlayerBase { + + /** + * Curse of the Werefox + * {2}{G} + * Sorcery + *
+ * Create a Monster Role token attached to target creature you control. When you do, that creature fights up to one target creature you don’t control. + */ + private static final String curseWerefox = "Curse of the Werefox"; + + /** + * Nexus Wardens + * Creature — Satyr Archer + *
+ * Reach + * Constellation — Whenever an enchantment enters the battlefield under your control, you gain 2 life. + *
+ * 1/4 + */ + private static final String wardens = "Nexus Wardens"; + + /** + * Azorius First-Wing + * {W}{U} + * Creature — Griffin + *
+ * Flying, protection from enchantments + *
+ * 2/2 + */ + private static final String azoriusFirstWing = "Azorius First-Wing"; + + // Checks that the "When you do" part of the ability does not trigger. + @Test + public void noTokenCreated() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, curseWerefox); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, wardens); + addCard(Zone.BATTLEFIELD, playerA, azoriusFirstWing); + addCard(Zone.BATTLEFIELD, playerB, wardens); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, curseWerefox, azoriusFirstWing); + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerA, 20); // no Constellation trigger. + assertDamageReceived(playerA, wardens, 0); + assertDamageReceived(playerA, azoriusFirstWing, 0); + assertDamageReceived(playerB, wardens, 0); + assertPermanentCount(playerA, "Monster", 0); + } + + @Test + public void usualBehavior() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, curseWerefox); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, wardens); + addCard(Zone.BATTLEFIELD, playerB, wardens); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, curseWerefox, wardens); + setChoice(playerA, "Constellation"); + addTarget(playerA, wardens); // Choosing second target for the fight. + setStopAt(1, PhaseStep.END_COMBAT); + execute(); + + assertLife(playerA, 20 + 2); // Constellation Trigger. + assertPermanentCount(playerA, wardens, 1); + assertPermanentCount(playerB, wardens, 1); + assertDamageReceived(playerA, wardens, 1); + assertDamageReceived(playerB, wardens, 2); + assertPermanentCount(playerA, "Monster", 1); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateRoleAttachedTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateRoleAttachedTargetEffect.java index ff5652ea894..cc964203c26 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/CreateRoleAttachedTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/CreateRoleAttachedTargetEffect.java @@ -7,6 +7,7 @@ import mage.constants.Outcome; import mage.constants.RoleType; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.permanent.token.Token; /** * @author TheElk801 @@ -36,8 +37,9 @@ public class CreateRoleAttachedTargetEffect extends OneShotEffect { if (permanent == null) { return false; } - roleType.createToken(permanent, game, source); - return true; + Token token = roleType.createToken(permanent, game, source); + // The token may not be created, for instance if the creature has protection from enchantments. + return token.getLastAddedTokenIds().size() > 0; } @Override diff --git a/Mage/src/main/java/mage/abilities/keyword/ProtectionAbility.java b/Mage/src/main/java/mage/abilities/keyword/ProtectionAbility.java index f47c2801a7d..a7bdf2f330b 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ProtectionAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ProtectionAbility.java @@ -10,6 +10,8 @@ import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import mage.game.permanent.token.Token; import mage.game.stack.Spell; import mage.players.Player; import mage.util.CardUtil; @@ -89,6 +91,10 @@ public class ProtectionAbility extends StaticAbility { return !((FilterCard) filter).match((Card) source, ((Permanent) source).getControllerId(), this, game); } else if (source instanceof Card) { return !((FilterCard) filter).match((Card) source, ((Card) source).getOwnerId(), this, game); + } else if (source instanceof Token) { + // Fake a permanent with the Token info. + PermanentToken token = new PermanentToken((Token) source, null, game); + return !((FilterCard) filter).match((Card) token, game); } return true; }