From ad66b916421a92a35690ce8e905a95a977794437 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sat, 28 Oct 2023 15:17:21 -0400 Subject: [PATCH] fix #11353 (InvestigateTargetEffect) add some test cases --- .../src/mage/cards/d/DeclarationInStone.java | 64 ++++++++--------- .../src/mage/cards/e/ErdwalIlluminator.java | 6 +- .../src/mage/cards/f/FatefulAbsence.java | 5 +- .../mage/cards/w/WernogRidersChaplain.java | 13 ++-- .../abilities/keywords/InvestigateTest.java | 47 ++++++++++++- .../single/soi/DeclarationInStoneTest.java | 9 ++- .../effects/keyword/InvestigateEffect.java | 12 ++-- .../keyword/InvestigateTargetEffect.java | 68 +++++++++++++++++++ .../main/java/mage/game/events/GameEvent.java | 2 +- 9 files changed, 167 insertions(+), 59 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/effects/keyword/InvestigateTargetEffect.java diff --git a/Mage.Sets/src/mage/cards/d/DeclarationInStone.java b/Mage.Sets/src/mage/cards/d/DeclarationInStone.java index a20f98c13e8..f588698908e 100644 --- a/Mage.Sets/src/mage/cards/d/DeclarationInStone.java +++ b/Mage.Sets/src/mage/cards/d/DeclarationInStone.java @@ -3,6 +3,7 @@ package mage.cards.d; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.keyword.InvestigateTargetEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -13,9 +14,9 @@ import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; -import mage.game.permanent.token.ClueArtifactToken; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import java.util.HashSet; @@ -61,42 +62,41 @@ class DeclarationInStoneEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source); - if (sourceObject != null && controller != null) { - Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (targetPermanent != null) { - Set cardsToExile = new HashSet<>(); - int nonTokenCount = 0; - if (CardUtil.haveEmptyName(targetPermanent)) { // face down creature - cardsToExile.add(targetPermanent); - if (!(targetPermanent instanceof PermanentToken)) { + Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (controller == null || sourceObject == null || targetPermanent == null) { + return false; + } + Set cardsToExile = new HashSet<>(); + int nonTokenCount = 0; + if (CardUtil.haveEmptyName(targetPermanent)) { // face down creature + cardsToExile.add(targetPermanent); + if (!(targetPermanent instanceof PermanentToken)) { + nonTokenCount++; + } + } else { + if (cardsToExile.add(targetPermanent) + && !(targetPermanent instanceof PermanentToken)) { + nonTokenCount++; + } + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURES, targetPermanent.getControllerId(), game)) { + if (!permanent.getId().equals(targetPermanent.getId()) + && CardUtil.haveSameNames(permanent, targetPermanent)) { + cardsToExile.add(permanent); + // exiled count only matters for non-tokens + if (!(permanent instanceof PermanentToken)) { nonTokenCount++; } - } else { - if (cardsToExile.add(targetPermanent) - && !(targetPermanent instanceof PermanentToken)) { - nonTokenCount++; - } - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURES, targetPermanent.getControllerId(), game)) { - if (!permanent.getId().equals(targetPermanent.getId()) - && CardUtil.haveSameNames(permanent, targetPermanent)) { - cardsToExile.add(permanent); - // exiled count only matters for non-tokens - if (!(permanent instanceof PermanentToken)) { - nonTokenCount++; - } - } - } } - controller.moveCards(cardsToExile, Zone.EXILED, source, game); - game.getState().processAction(game); - if (nonTokenCount > 0) { - new ClueArtifactToken().putOntoBattlefield(nonTokenCount, game, source, targetPermanent.getControllerId(), false, false); - } - return true; } } - - return false; + controller.moveCards(cardsToExile, Zone.EXILED, source, game); + game.getState().processAction(game); + if (nonTokenCount > 0) { + new InvestigateTargetEffect(nonTokenCount) + .setTargetPointer(new FixedTarget(targetPermanent.getControllerId())) + .apply(game, source); + } + return true; } @Override diff --git a/Mage.Sets/src/mage/cards/e/ErdwalIlluminator.java b/Mage.Sets/src/mage/cards/e/ErdwalIlluminator.java index f3525e82c69..4e2e00c20a8 100644 --- a/Mage.Sets/src/mage/cards/e/ErdwalIlluminator.java +++ b/Mage.Sets/src/mage/cards/e/ErdwalIlluminator.java @@ -1,4 +1,3 @@ - package mage.cards.e; import mage.MageInt; @@ -13,7 +12,6 @@ import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.watchers.Watcher; import java.util.HashMap; @@ -66,6 +64,9 @@ class ErdwalIlluminatorTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { + if (!isControlledBy(event.getPlayerId())) { // only when controller investigates + return false; + } InvestigatedWatcher watcher = game.getState().getWatcher(InvestigatedWatcher.class); return watcher != null && watcher.getTimesInvestigated(getControllerId()) == 1; } @@ -93,7 +94,6 @@ class InvestigatedWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.INVESTIGATED) { timesInvestigated.put(event.getPlayerId(), getTimesInvestigated(event.getPlayerId()) + 1); - } } diff --git a/Mage.Sets/src/mage/cards/f/FatefulAbsence.java b/Mage.Sets/src/mage/cards/f/FatefulAbsence.java index 5a291dfcc45..8479872efc1 100644 --- a/Mage.Sets/src/mage/cards/f/FatefulAbsence.java +++ b/Mage.Sets/src/mage/cards/f/FatefulAbsence.java @@ -4,14 +4,15 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.keyword.InvestigateTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.token.ClueArtifactToken; import mage.target.common.TargetCreatureOrPlaneswalker; +import mage.target.targetpointer.FixedTarget; /** * @@ -61,7 +62,7 @@ class FatefulAbsenceEffect extends OneShotEffect { } UUID controllerId = permanent.getControllerId(); permanent.destroy(source, game, false); - new ClueArtifactToken().putOntoBattlefield(1, game, source, controllerId); + new InvestigateTargetEffect().setTargetPointer(new FixedTarget(controllerId)).apply(game, source); return true; } } diff --git a/Mage.Sets/src/mage/cards/w/WernogRidersChaplain.java b/Mage.Sets/src/mage/cards/w/WernogRidersChaplain.java index 6ae24c5c247..0ba99ca4dde 100644 --- a/Mage.Sets/src/mage/cards/w/WernogRidersChaplain.java +++ b/Mage.Sets/src/mage/cards/w/WernogRidersChaplain.java @@ -4,6 +4,8 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldOrLeavesSourceTriggeredAbility; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.keyword.InvestigateEffect; +import mage.abilities.effects.keyword.InvestigateTargetEffect; import mage.abilities.keyword.FriendsForeverAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -12,10 +14,8 @@ import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.token.ClueArtifactToken; -import mage.game.permanent.token.Token; import mage.players.Player; +import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -69,7 +69,6 @@ class WernogRidersChaplainEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int count = 1; - Token token = new ClueArtifactToken(); for (UUID playerId : game.getOpponents(source.getControllerId())) { Player opponent = game.getPlayer(playerId); if (opponent == null) { @@ -77,14 +76,12 @@ class WernogRidersChaplainEffect extends OneShotEffect { } if (opponent.chooseUse(outcome, "Investigate?", source, game)) { count++; - token.putOntoBattlefield(1, game, source, playerId); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.INVESTIGATED, source.getSourceId(), source, playerId)); + new InvestigateTargetEffect().setTargetPointer(new FixedTarget(playerId)).apply(game, source); } else { opponent.loseLife(1, game, source, false); } } - token.putOntoBattlefield(count, game, source, source.getControllerId()); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.INVESTIGATED, source.getSourceId(), source, source.getControllerId())); + new InvestigateEffect(count).apply(game, source); return true; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/InvestigateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/InvestigateTest.java index 981ab6fc6e2..9cadea96257 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/InvestigateTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/InvestigateTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; @@ -12,10 +11,52 @@ import org.mage.test.serverside.base.CardTestPlayerBase; */ public class InvestigateTest extends CardTestPlayerBase { + @Test + public void testInvestigatingBothPlayers() { + String wernog = "Wernog, Rider's Chaplain"; // {W}{B} 1/2 + // When Wernog, Rider’s Chaplain enters or leaves the battlefield, each opponent may investigate. + // Each opponent who doesn’t loses 1 life. + // You investigate X times, where X is one plus the number of opponents who investigated this way. + String absence = "Fateful Absence"; // {1}{W} Instant + // Destroy target creature or planeswalker. Its controller investigates. + String clue = "Clue Token"; + String erdwal = "Erdwal Illuminator"; // Whenever you investigate for the first time each turn, investigate an additional time. + + addCard(Zone.BATTLEFIELD, playerA, "Scrubland", 4); + addCard(Zone.HAND, playerA, wernog); + addCard(Zone.HAND, playerA, absence); + addCard(Zone.BATTLEFIELD, playerA, erdwal, 2); + addCard(Zone.BATTLEFIELD, playerB, erdwal); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, wernog); + setChoice(playerB, false); // opponent chooses not to investigate + setChoice(playerA, "Whenever you investigate for the "); // order triggers + + checkLife("lost 1 life", 1, PhaseStep.BEGIN_COMBAT, playerB, 19); + checkPermanentCount("3 clues", 1, PhaseStep.BEGIN_COMBAT, playerA, clue, 3); // one, plus two erdwal triggers + checkPlayableAbility("clue ability", 1, PhaseStep.BEGIN_COMBAT, playerA, "{2}, Sacrifice ", true); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, absence, wernog); // + 1 clue + setChoice(playerB, true); // opponent chooses to investigate + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 19); + assertGraveyardCount(playerA, absence, 1); + assertGraveyardCount(playerA, wernog, 1); + assertPermanentCount(playerA, clue, 3 + 1 + 2); + assertPermanentCount(playerB, clue, 1 + 1); + + } + @Test public void testBriarbridgePatrol() { - // Whenever Briarbridge Patrol deals damage to one or more creatures, investigate (Create a colorless Clue artifact token onto the battlefield with "{2}, Sacrifice this artifact: Draw a card."). - // At the beginning of each end step, if you sacrificed three or more Clues this turn, you may put a creature card from your hand onto the battlefield. + // Whenever Briarbridge Patrol deals damage to one or more creatures, investigate. + // At the beginning of each end step, if you sacrificed three or more Clues this turn, + // you may put a creature card from your hand onto the battlefield. addCard(Zone.BATTLEFIELD, playerA, "Briarbridge Patrol", 1); // 3/3 addCard(Zone.BATTLEFIELD, playerA, "Island", 2); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/DeclarationInStoneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/DeclarationInStoneTest.java index 5d80cf06c20..6a1d3ce5d56 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/DeclarationInStoneTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/DeclarationInStoneTest.java @@ -21,22 +21,27 @@ public class DeclarationInStoneTest extends CardTestPlayerBase { String dStone = "Declaration in Stone"; String memnite = "Memnite"; // {0} 1/1 String hGiant = "Hill Giant"; // {3}{R} 3/3 + String erdwal = "Erdwal Illuminator"; // Whenever you investigate for the first time each turn, investigate an additional time. addCard(Zone.BATTLEFIELD, playerB, memnite, 3); addCard(Zone.BATTLEFIELD, playerB, hGiant); + addCard(Zone.BATTLEFIELD, playerB, erdwal); + addCard(Zone.BATTLEFIELD, playerA, erdwal); addCard(Zone.HAND, playerA, dStone); addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, dStone, memnite); - + setStopAt(1, PhaseStep.BEGIN_COMBAT); + setStrictChooseMode(true); execute(); assertGraveyardCount(playerA, dStone, 1); assertPermanentCount(playerB, hGiant, 1); assertPermanentCount(playerB, memnite, 0); assertExileCount(playerB, memnite, 3); - assertPermanentCount(playerB, "Clue Token", 3); // 3 creatures exiled = 3 clues for them + assertPermanentCount(playerB, "Clue Token", 3 + 1); // 3 creatures exiled = 3 clues for them, plus 1 extra from Erdwal effect + assertPermanentCount(playerA, "Clue Token", 0); // player A doesn't investigate here } @Test diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java index 4dc2e79118a..c1025776cba 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateEffect.java @@ -1,7 +1,6 @@ package mage.abilities.effects.keyword; import mage.abilities.Ability; -import mage.abilities.Mode; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; @@ -29,6 +28,7 @@ public class InvestigateEffect extends OneShotEffect { public InvestigateEffect(DynamicValue amount) { super(Outcome.Benefit); this.amount = amount; + this.staticText = makeText(); } protected InvestigateEffect(final InvestigateEffect effect) { @@ -54,11 +54,7 @@ public class InvestigateEffect extends OneShotEffect { return new InvestigateEffect(this); } - @Override - public String getText(Mode mode) { - if (staticText != null && !staticText.isEmpty()) { - return staticText; - } + private String makeText() { String message; if (amount instanceof StaticValue) { int value = ((StaticValue) amount).getValue(); @@ -75,7 +71,7 @@ public class InvestigateEffect extends OneShotEffect { } else { message = " X times, where X is the " + amount.getMessage() + ". (To investigate, c"; } - return "investigate" + message + "reate a colorless Clue artifact token " + - "with \"{2}, Sacrifice this artifact: Draw a card.\")"; + return "investigate" + message + "reate a Clue token. " + + "It's an artifact with \"{2}, Sacrifice this artifact: Draw a card.\")"; } } diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateTargetEffect.java new file mode 100644 index 00000000000..f9ac666d3f2 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/keyword/InvestigateTargetEffect.java @@ -0,0 +1,68 @@ +package mage.abilities.effects.keyword; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.token.ClueArtifactToken; +import mage.players.Player; + +/** + * @author LevelX2 + */ +public class InvestigateTargetEffect extends OneShotEffect { + + private final DynamicValue amount; + + public InvestigateTargetEffect() { + this(1); + } + + public InvestigateTargetEffect(int amount) { + this(StaticValue.get(amount)); + } + + public InvestigateTargetEffect(DynamicValue amount) { + super(Outcome.Benefit); + this.amount = amount; + } + + protected InvestigateTargetEffect(final InvestigateTargetEffect effect) { + super(effect); + this.amount = effect.amount; + } + + @Override + public boolean apply(Game game, Ability source) { + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); + if (targetPlayer == null) { + return false; + } + int value = this.amount.calculate(game, source, this); + if (value < 1) { + return false; + } + new ClueArtifactToken().putOntoBattlefield(value, game, source, targetPlayer.getId()); + for (int i = 0; i < value; i++) { + game.fireEvent(GameEvent.getEvent(GameEvent.EventType.INVESTIGATED, source.getSourceId(), source, targetPlayer.getId())); + } + return true; + } + + @Override + public InvestigateTargetEffect copy() { + return new InvestigateTargetEffect(this); + } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return getTargetPointer().describeTargets(mode.getTargets(), "that player") + " investigates"; + } +} diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 61a54eddbae..b0c8bc7348f 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -84,7 +84,7 @@ public class GameEvent implements Serializable { playerId controller of the card */ MADNESS_CARD_EXILED, - INVESTIGATED, + INVESTIGATED, // playerId is the player who investigated KICKED, /* CONVOKED targetId id of the creature that was taped to convoke the sourceId