From 8c22db650a56cf563ac2bee59c723edd4242de89 Mon Sep 17 00:00:00 2001 From: Alex Vasile <48962821+Alex-Vasile@users.noreply.github.com> Date: Sun, 17 Jul 2022 18:24:35 -0400 Subject: [PATCH] [CLB] Fixed connive fizzling on stack when permanent that connived leaves the battlefield. Closes #9252. --- .../cards/abilities/keywords/ConniveTest.java | 183 ++++++++++++++++++ .../effects/keyword/ConniveSourceEffect.java | 18 +- 2 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java new file mode 100644 index 0000000000..23cfcb9517 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java @@ -0,0 +1,183 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Alex-Vasile + * To have a creature connive, draw a card, then discard a card. If you discarded a nonland card, put a +1/+1 counter on that creature. + */ +public class ConniveTest extends CardTestPlayerBase { + + /** + * Connive and discard a land. + * Creature should not get a +1/+1 counter. + */ + @Test + public void conniveDiscardLand() { + // P/T : 1/2 + // {3}: Hypnotic Grifter connives + addCard(Zone.BATTLEFIELD, playerA, "Hypnotic Grifter"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerA, "Plains", 1); // To discard + + setStrictChooseMode(true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + setChoice(playerA, "Plains"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPowerToughness(playerA, "Hypnotic Grifter", 1, 2); // Discarded a land card + assertHandCount(playerA, 1); // 1 from drawing at start of turn + assertGraveyardCount(playerA, "Plains", 1); + } + + /** + * Connive and discard a creature. + * Creature should get a +1/+1 counter. + */ + @Test + public void conniveDiscardCreature() { + // P/T : 1/2 + // {3}: Hypnotic Grifter connives + addCard(Zone.BATTLEFIELD, playerA, "Hypnotic Grifter"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); + addCard(Zone.HAND, playerA, "Ledger Shredder", 1); // To discard + + setStrictChooseMode(true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + setChoice(playerA, "Ledger Shredder"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPowerToughness(playerA, "Hypnotic Grifter", 2, 3); // Discarded a non-land + assertHandCount(playerA, 1); // 1 from drawing at start of turn + assertGraveyardCount(playerA, "Ledger Shredder", 1); + } + + /** + * Connive + Madness. + * Creature should get a +1/+1 counter and the madness card gets played. + */ + @Test + public void conniveMadness() { + // P/T : 1/2 + // {3}: Hypnotic Grifter connives + addCard(Zone.BATTLEFIELD, playerA, "Hypnotic Grifter"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + // Target opponent loses 3 life and you gain 3 life. + // Madness {B} + addCard(Zone.HAND, playerA, "Alms of the Vein", 1); // To discard and use mad + + setStrictChooseMode(true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + setChoice(playerA, "Alms of the Vein"); + setChoice(playerA, "Yes"); // Cast for Madness + addTarget(playerA, playerB); // Target for Alms of the Vein + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertPowerToughness(playerA, "Hypnotic Grifter", 2, 3); // Discarded a non-land + assertHandCount(playerA, 1); // 1 from drawing at start of turn + assertGraveyardCount(playerA, "Alms of the Vein", 1); + + assertLife(playerA, 23); // 3 life gained from Alms of the Vein + assertLife(playerB, 17); // 3 life lost from Alms of the Vein + } + + /** + * Obscura Confluence allows you to connive a creature you don't control. + * It's the only card that causes you to coonnive a creature you don't control. + */ + @Test + public void conniveNonControlledCreature() { + // Choose three. You may choose the same mode more than once. + //• Until end of turn, target creature loses all abilities and has base power and toughness 1/1. + //• Target creature connives. (Draw a card, then discard a card. If you discarded a nonland card, put a +1/+1 counter on that creature.) + //• Target player returns a creature card from their graveyard to their hand. + addCard(Zone.HAND, playerA, "Obscura Confluence"); // {1}{W}{U}{B} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + + addCard(Zone.HAND, playerB, "Plains", 3); // 3 land cards for conniving + addCard(Zone.BATTLEFIELD, playerB, "Crazed Goblin"); // 1/1 creature that is made to connive + + setStrictChooseMode(true); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Obscura Confluence"); + setModeChoice(playerA, "2"); + setModeChoice(playerA, "2"); + setModeChoice(playerA, "2"); + addTarget(playerA, "Crazed Goblin"); + addTarget(playerA, "Crazed Goblin"); + addTarget(playerA, "Crazed Goblin"); + setChoice(playerB, "Plains"); + setChoice(playerB, "Plains"); + setChoice(playerB, "Plains"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + + execute(); + assertAllCommandsUsed(); + + assertPowerToughness(playerB, "Crazed Goblin", 1, 1); // Discarded only lands + assertGraveyardCount(playerB, "Plains", 3); + } + + /** + * Reported bug: https://github.com/magefree/mage/issues/9252 + * Connive fizzles if the creature that connived leaves the battlefield before connive resolves. + * + * Ruling: + * If a resolving spell or ability instructs a specific creature to connive but that creature has left the battlefield, + * the creature still connives. + * If you discard a nonland card this way, you won’t put a +1/+1 counter on anything. + * Abilities that trigger “when [that creature] connives” will trigger. + * (2022-04-29) + */ + @Test + public void conniveDoesNotFizzle() { + // {4}{U} + // 3/2 + // When Psychic Pickpocket enters the battlefield, it connives. + // When it connives this way, return up to one target nonland permanent to its owner’s hand. + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + addCard(Zone.HAND, playerA, "Psychic Pickpocket"); + addCard(Zone.HAND, playerA, "Swamp"); + + addCard(Zone.HAND, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + addCard(Zone.BATTLEFIELD, playerB, "Sol Ring"); // Target for Psychic Pickpocket + + setStrictChooseMode(true); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Psychic Pickpocket"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true); // Wait for ETB + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Psychic Pickpocket", "When"); + setChoice(playerA, "Swamp"); // Discard for connive + addTarget(playerA, "Sol Ring"); // Target for Psychic Pickpocket + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + + execute(); + assertAllCommandsUsed(); + + assertHandCount(playerA, 1); // Drew a card at start of turn + assertHandCount(playerB, "Sol Ring", 1); // Returned by Psychic Pickpocket's ability + assertGraveyardCount(playerA, "Swamp", 1); // Connived + assertGraveyardCount(playerA, "Psychic Pickpocket", 1); // Destroyed by Lightning Bolt + assertGraveyardCount(playerB, "Lightning Bolt", 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/ConniveSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/ConniveSourceEffect.java index a39dbb6d08..20842bd8b3 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/ConniveSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/ConniveSourceEffect.java @@ -5,6 +5,7 @@ import mage.abilities.Mode; import mage.abilities.common.delayed.ReflexiveTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; +import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.game.Game; @@ -48,19 +49,26 @@ public class ConniveSourceEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent permanent = source.getSourcePermanentIfItStillExists(game); - if (permanent != null) { - connive(permanent, 1, source, game); - } + boolean connived = connive(permanent, 1, source, game); if (ability != null) { game.fireReflexiveTriggeredAbility(ability, source); } - return permanent != null || ability != null; + return connived || ability != null; } public static boolean connive(Permanent permanent, int amount, Ability source, Game game) { if (amount < 1) { return false; } + boolean permanentStillOnBattlefield; + if (permanent == null) { + // If the permanent was killed, get last known information + permanent = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); + permanentStillOnBattlefield = false; + } else { + permanentStillOnBattlefield = true; + } + Player player = game.getPlayer(permanent.getControllerId()); if (player == null) { return false; @@ -69,7 +77,7 @@ public class ConniveSourceEffect extends OneShotEffect { int counters = player .discard(amount, false, false, source, game) .count(StaticFilters.FILTER_CARDS_NON_LAND, game); - if (counters > 0) { + if (permanentStillOnBattlefield && counters > 0) { permanent.addCounters(CounterType.P1P1.createInstance(counters), source, game); } return true;