[CLB] Fixed connive fizzling on stack when permanent that connived leaves the battlefield. Closes #9252.

This commit is contained in:
Alex Vasile 2022-07-17 18:24:35 -04:00
parent 8878dc5cc7
commit 8c22db650a
2 changed files with 196 additions and 5 deletions

View file

@ -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 wont 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 owners 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);
}
}

View file

@ -5,6 +5,7 @@ import mage.abilities.Mode;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility; import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
@ -48,19 +49,26 @@ public class ConniveSourceEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Permanent permanent = source.getSourcePermanentIfItStillExists(game); Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent != null) { boolean connived = connive(permanent, 1, source, game);
connive(permanent, 1, source, game);
}
if (ability != null) { if (ability != null) {
game.fireReflexiveTriggeredAbility(ability, source); 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) { public static boolean connive(Permanent permanent, int amount, Ability source, Game game) {
if (amount < 1) { if (amount < 1) {
return false; 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()); Player player = game.getPlayer(permanent.getControllerId());
if (player == null) { if (player == null) {
return false; return false;
@ -69,7 +77,7 @@ public class ConniveSourceEffect extends OneShotEffect {
int counters = player int counters = player
.discard(amount, false, false, source, game) .discard(amount, false, false, source, game)
.count(StaticFilters.FILTER_CARDS_NON_LAND, game); .count(StaticFilters.FILTER_CARDS_NON_LAND, game);
if (counters > 0) { if (permanentStillOnBattlefield && counters > 0) {
permanent.addCounters(CounterType.P1P1.createInstance(counters), source, game); permanent.addCounters(CounterType.P1P1.createInstance(counters), source, game);
} }
return true; return true;