From 040a9779b56fe61f16ee631f2e98f0479387fa78 Mon Sep 17 00:00:00 2001 From: Vivian Greenslade Date: Fri, 11 Aug 2023 01:30:09 -0230 Subject: [PATCH] [LTR] Implement Forge Anew (#10777) * implements Forge Anew with unit tests * fixes equip effect and adjusts test * fixes effect to only apply on controller's turns * changed test location to set * fixes comparison, fixes wording of text to match oracle text --- Mage.Sets/src/mage/cards/f/ForgeAnew.java | 147 ++++++++++++++++++ .../TheLordOfTheRingsTalesOfMiddleEarth.java | 1 + .../test/cards/single/ltr/ForgeAnewTest.java | 44 ++++++ 3 files changed, 192 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/ForgeAnew.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/ForgeAnewTest.java diff --git a/Mage.Sets/src/mage/cards/f/ForgeAnew.java b/Mage.Sets/src/mage/cards/f/ForgeAnew.java new file mode 100644 index 00000000000..cd00008a13d --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/ForgeAnew.java @@ -0,0 +1,147 @@ +package mage.cards.f; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.effects.common.continuous.ActivateAbilitiesAnyTimeYouCouldCastInstantEffect; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.watchers.Watcher; + +/** + * + * @author xanderhall + */ +public class ForgeAnew extends CardImpl { + + private static final FilterCard filter = new FilterCard("Equipment card from your graveyard"); + + static { + filter.add(SubType.EQUIPMENT.getPredicate()); + } + + public ForgeAnew(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}"); + + // When Forge Anew enters the battlefield, return target Equipment card from your graveyard to the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); + ability.addTarget(new TargetCardInYourGraveyard(filter)); + this.addAbility(ability); + + //As long as it’s your turn, you may activate equip abilities any time you could cast an instant. + this.addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect( + new ActivateAbilitiesAnyTimeYouCouldCastInstantEffect(EquipAbility.class, "equip abilities"), MyTurnCondition.instance + ).setText("as long as it's your turn, you may activate equip abilities any time you could cast an instant.")) + ); + + //You may pay {0} rather than pay the equip cost of the first equip ability you activate during each of your turns. + this.addAbility(new SimpleStaticAbility(new ForgeAnewCostEffect()), new ForgeAnewWatcher()); + } + private ForgeAnew(final ForgeAnew card) { + super(card); + } + + @Override + public ForgeAnew copy() { + return new ForgeAnew(this); + } +} + +class ForgeAnewCostEffect extends CostModificationEffectImpl { + + ForgeAnewCostEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.SET_COST); + this.staticText = "you may pay {0} rather than pay the equip cost of the first equip ability you activate during each of your turns."; + } + + ForgeAnewCostEffect(final ForgeAnewCostEffect effect) { + super(effect); + } + + @Override + public boolean applies(Ability abilityToModify, Ability source, Game game) { + return abilityToModify instanceof EquipAbility + && source.isControlledBy(abilityToModify.getControllerId()) + && game.getActivePlayerId().equals(source.getControllerId()) + && !ForgeAnewWatcher.checkPlayer(abilityToModify.getControllerId(), game); + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + boolean applyReduce = false; + if (game.inCheckPlayableState()) { + // getPlayable use - apply all the time + applyReduce = true; + } else { + // real use - ask the player + Player controller = game.getPlayer(abilityToModify.getControllerId()); + if (controller != null + && controller.chooseUse(Outcome.PlayForFree, + String.format("Pay {0} to equip instead %s?", abilityToModify.getManaCostsToPay().getText()), source, game)) { + applyReduce = true; + } + } + + if (applyReduce) { + abilityToModify.getCosts().clear(); + abilityToModify.getManaCostsToPay().clear(); + return true; + } + + return false; + } + + @Override + public ForgeAnewCostEffect copy() { + return new ForgeAnewCostEffect(this); + } +} + +class ForgeAnewWatcher extends Watcher { + + private final Set equippedThisTurn = new HashSet<>(); + + ForgeAnewWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.ACTIVATED_ABILITY) { + return; + } + + StackObject object = game.getStack().getStackObject(event.getSourceId()); + + if (object != null && object.getStackAbility() instanceof EquipAbility) { + equippedThisTurn.add(event.getPlayerId()); + } + } + + @Override + public void reset() { + super.reset(); + equippedThisTurn.clear(); + } + + static boolean checkPlayer(UUID playerId, Game game) { + ForgeAnewWatcher watcher = game.getState().getWatcher(ForgeAnewWatcher.class); + return watcher != null && watcher.equippedThisTurn.contains(playerId); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java index d88729d13d6..fffe822c119 100644 --- a/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java +++ b/Mage.Sets/src/mage/sets/TheLordOfTheRingsTalesOfMiddleEarth.java @@ -99,6 +99,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet { cards.add(new SetCardInfo("Flowering of the White Tree", 15, Rarity.RARE, mage.cards.f.FloweringOfTheWhiteTree.class)); cards.add(new SetCardInfo("Fog on the Barrow-Downs", 16, Rarity.COMMON, mage.cards.f.FogOnTheBarrowDowns.class)); cards.add(new SetCardInfo("Foray of Orcs", 128, Rarity.UNCOMMON, mage.cards.f.ForayOfOrcs.class)); + cards.add(new SetCardInfo("Forge Anew", 17, Rarity.RARE, mage.cards.f.ForgeAnew.class)); cards.add(new SetCardInfo("Forest", 270, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Friendly Rivalry", 204, Rarity.UNCOMMON, mage.cards.f.FriendlyRivalry.class)); cards.add(new SetCardInfo("Frodo Baggins", 205, Rarity.UNCOMMON, mage.cards.f.FrodoBaggins.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/ForgeAnewTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/ForgeAnewTest.java new file mode 100644 index 00000000000..d19dd55424a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/ForgeAnewTest.java @@ -0,0 +1,44 @@ +package org.mage.test.cards.single.ltr; + +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import mage.constants.PhaseStep; +import mage.constants.Zone; + +/** + * @author Xanderhall + */ +public class ForgeAnewTest extends CardTestPlayerBase { + + static final String FORGE_ANEW = "Forge Anew"; + static final String EQUIPMENT = "Bronze Sword"; + static final String CREATURE = "Silvercoat Lion"; + + /** + * Test implementation of card + */ + + @Test + public void testForgeAnew() { + addCard(Zone.BATTLEFIELD, playerA, CREATURE, 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Zone.HAND, playerA, FORGE_ANEW, 1); + addCard(Zone.GRAVEYARD, playerA, EQUIPMENT, 1); + + // Test return from graveyard + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, FORGE_ANEW); + addTarget(playerA, EQUIPMENT, 1); + + // Test can equip at instant speed for 0 (all mana tapped) + activateAbility(1, PhaseStep.END_COMBAT, playerA, "Equip {3}", CREATURE); + setChoice(playerA, true); // Choose to equip for free + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + // Make sure it is attached + assertIsAttachedTo(playerA, EQUIPMENT, CREATURE); + } +}