From d08b71642bd8f99fddd270899d7afdfb80755a9b Mon Sep 17 00:00:00 2001 From: Matthew Wilson Date: Thu, 28 Mar 2024 05:28:30 +0200 Subject: [PATCH] Update Laelia, the Blade Reforged to use ZONE_CHANGE_BATCH (#12010) * Fix #11747 * Fix #12008 --- .../src/mage/cards/c/CranialExtraction.java | 6 +- .../mage/cards/l/LaeliaTheBladeReforged.java | 55 +++----- .../c21/LaeliaTheBladeReforgedTest.java | 131 ++++++++++++++++++ 3 files changed, 153 insertions(+), 39 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/c21/LaeliaTheBladeReforgedTest.java diff --git a/Mage.Sets/src/mage/cards/c/CranialExtraction.java b/Mage.Sets/src/mage/cards/c/CranialExtraction.java index c3364ddd381..71b12f9da90 100644 --- a/Mage.Sets/src/mage/cards/c/CranialExtraction.java +++ b/Mage.Sets/src/mage/cards/c/CranialExtraction.java @@ -1,7 +1,6 @@ package mage.cards.c; import mage.abilities.Ability; -import mage.abilities.Mode; import mage.abilities.effects.common.ChooseACardNameEffect; import mage.abilities.effects.common.search.SearchTargetGraveyardHandLibraryForCardNameAndExileEffect; import mage.cards.CardImpl; @@ -53,11 +52,12 @@ class CranialExtractionEffect extends SearchTargetGraveyardHandLibraryForCardNam @Override public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); Player player = game.getPlayer(this.getTargetPointer().getFirst(game, source)); - if (player == null) { + if (controller == null || player == null) { return true; } - String cardName = ChooseACardNameEffect.TypeOfName.NON_LAND_NAME.getChoice(player, game, source, false); + String cardName = ChooseACardNameEffect.TypeOfName.NON_LAND_NAME.getChoice(controller, game, source, false); if (cardName == null) { return false; } diff --git a/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java b/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java index 64a44989707..5bbad75c84c 100644 --- a/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java +++ b/Mage.Sets/src/mage/cards/l/LaeliaTheBladeReforged.java @@ -13,12 +13,18 @@ import mage.constants.*; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeGroupEvent; +import mage.game.events.ZoneChangeBatchEvent; +import mage.game.events.ZoneChangeEvent; import java.util.Objects; import java.util.UUID; /** + * Rules update: 6/18/2021 + * Laelia, the Blade Reforged has received an update to its Oracle text. + * Specifically, its last triggered ability doesn't care which player is exiling cards from the library or graveyard. + * Cards put into exile from your library or graveyard for any reason, such as the delve ability, cause the ability to trigger. + * * @author jmharmon */ public final class LaeliaTheBladeReforged extends CardImpl { @@ -38,7 +44,7 @@ public final class LaeliaTheBladeReforged extends CardImpl { // Whenever Laelia, the Blade Reforged attacks, exile the top card of your library. You may play that card this turn. this.addAbility(new AttacksTriggeredAbility(new ExileTopXMayPlayUntilEffect(1, Duration.EndOfTurn), false)); - // Whenever a spell or ability you control exiles one or more cards from your library and/or your graveyard, put a +1/+1 counter on Laelia. + // Whenever one or more cards are put into exile from your library and/or your graveyard, put a +1/+1 counter on Laelia. this.addAbility(new LaeliaTheBladeReforgedAddCountersTriggeredAbility()); } @@ -69,44 +75,21 @@ class LaeliaTheBladeReforgedAddCountersTriggeredAbility extends TriggeredAbility @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE_GROUP; + return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; } @Override public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeGroupEvent zEvent = (ZoneChangeGroupEvent) event; - final int numberExiled = zEvent.getCards().size(); - if (zEvent.getToZone() != Zone.EXILED - || numberExiled == 0) { - return false; - } - switch (zEvent.getFromZone()) { - case LIBRARY: - if (zEvent - .getCards() - .stream() - .filter(Objects::nonNull) - .map(Card::getOwnerId) - .anyMatch(this::isControlledBy) - && numberExiled > 0) { - this.getEffects().clear(); - this.getEffects().add(new AddCountersSourceEffect(CounterType.P1P1.createInstance())); - return true; - } - case GRAVEYARD: - if (zEvent - .getCards() - .stream() - .filter(Objects::nonNull) - .map(Card::getOwnerId) - .anyMatch(this::isControlledBy) - && numberExiled > 0) { - this.getEffects().clear(); - this.getEffects().add(new AddCountersSourceEffect(CounterType.P1P1.createInstance())); - return true; - } - } - return false; + ZoneChangeBatchEvent zEvent = (ZoneChangeBatchEvent) event; + return zEvent.getEvents() + .stream() + .filter(e -> e.getFromZone() == Zone.LIBRARY || e.getFromZone() == Zone.GRAVEYARD) + .filter(e -> e.getToZone() == Zone.EXILED) + .map(ZoneChangeEvent::getTargetId) + .map(game::getCard) + .filter(Objects::nonNull) + .map(Card::getOwnerId) + .anyMatch(this::isControlledBy); } @Override diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/LaeliaTheBladeReforgedTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/LaeliaTheBladeReforgedTest.java new file mode 100644 index 00000000000..ec20ecc3e23 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/LaeliaTheBladeReforgedTest.java @@ -0,0 +1,131 @@ +package org.mage.test.cards.single.c21; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * {@link mage.cards.l.LaeliaTheBladeReforged Laelia, the Blade Reforged} + * {2}{R} + * Legendary Creature - Spirit Warrior + * Haste + * Whenever Laelia, the Blade Reforged attacks, exile the top card of your library. You may play that card this turn. + * Whenever one or more cards are put into exile from your library and/or your graveyard, put a +1/+1 counter on Laelia. + * + * @author DominionSpy + */ +public class LaeliaTheBladeReforgedTest extends CardTestPlayerBase { + + private static final String laelia = "Laelia, the Blade Reforged"; + private static final String cranialExtraction = "Cranial Extraction"; + private static final String llanowarElves = "Llanowar Elves"; + + @Test + public void controllerExilesOwnCards() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, laelia); + addCard(Zone.HAND, playerA, cranialExtraction); + addCard(Zone.HAND, playerA, llanowarElves); + addCard(Zone.GRAVEYARD, playerA, llanowarElves); + addCard(Zone.LIBRARY, playerA, llanowarElves); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cranialExtraction, playerA); + // Choose a nonland card name (Llanowar Elves) + setChoice(playerA, llanowarElves); + // Graveyard + setChoice(playerA, llanowarElves); + // Hand + setChoice(playerA, llanowarElves); + // Library + setChoice(playerA, llanowarElves); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, laelia, CounterType.P1P1, 1); + } + + @Test + public void opponentExilesControllersCards() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.HAND, playerA, cranialExtraction); + + addCard(Zone.BATTLEFIELD, playerB, laelia); + addCard(Zone.HAND, playerB, llanowarElves); + addCard(Zone.GRAVEYARD, playerB, llanowarElves); + addCard(Zone.LIBRARY, playerB, llanowarElves); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cranialExtraction, playerB); + // Choose a nonland card name (Llanowar Elves) + setChoice(playerA, llanowarElves); + // Graveyard + setChoice(playerA, llanowarElves); + // Hand + setChoice(playerA, llanowarElves); + // Library + setChoice(playerA, llanowarElves); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerB, laelia, CounterType.P1P1, 1); + } + + @Test + public void controllerExilesOpponentsCards() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerA, laelia); + addCard(Zone.HAND, playerA, cranialExtraction); + + addCard(Zone.HAND, playerB, llanowarElves); + addCard(Zone.GRAVEYARD, playerB, llanowarElves); + addCard(Zone.LIBRARY, playerB, llanowarElves); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cranialExtraction, playerB); + // Choose a nonland card name (Llanowar Elves) + setChoice(playerA, llanowarElves); + // Graveyard + setChoice(playerA, llanowarElves); + // Hand + setChoice(playerA, llanowarElves); + // Library + setChoice(playerA, llanowarElves); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerA, laelia, CounterType.P1P1, 0); + } + + @Test + public void opponentExilesOwnCards() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.HAND, playerA, cranialExtraction); + addCard(Zone.HAND, playerA, llanowarElves); + addCard(Zone.GRAVEYARD, playerA, llanowarElves); + addCard(Zone.LIBRARY, playerA, llanowarElves); + + addCard(Zone.BATTLEFIELD, playerB, laelia); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, cranialExtraction, playerA); + // Choose a nonland card name (Llanowar Elves) + setChoice(playerA, llanowarElves); + // Graveyard + setChoice(playerA, llanowarElves); + // Hand + setChoice(playerA, llanowarElves); + // Library + setChoice(playerA, llanowarElves); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertCounterCount(playerB, laelia, CounterType.P1P1, 0); + } +}