Fixed a bug that fizzling spell copies let also wrongly fizzle the original spell on the stack the copy was made from.

This commit is contained in:
LevelX2 2017-03-11 12:00:05 +01:00
parent 2d20045b61
commit c042d50ec7
5 changed files with 106 additions and 28 deletions

View file

@ -64,7 +64,7 @@ public class AtraxaPraetorsVoice extends CardImpl {
// Lifelink // Lifelink
this.addAbility(LifelinkAbility.getInstance()); this.addAbility(LifelinkAbility.getInstance());
// At the beginning of your end step, proliferate. // At the beginning of your end step, proliferate. (You choose any number of permanents and/or players with counters on them, then give each another counter of a kind already there.)
this.addAbility(new BeginningOfEndStepTriggeredAbility(new ProliferateEffect(), TargetController.YOU, false)); this.addAbility(new BeginningOfEndStepTriggeredAbility(new ProliferateEffect(), TargetController.YOU, false));
} }

View file

@ -90,8 +90,9 @@ public class CopySpellTest extends CardTestPlayerBase {
} }
/** /**
* Reported bug: "Silverfur Partisan and fellow wolves did not trigger off of copies of Strength of Arms made by Zada, Hedron Grinder. * Reported bug: "Silverfur Partisan and fellow wolves did not trigger off
* Not sure about other spells, but I imagine similar results." * of copies of Strength of Arms made by Zada, Hedron Grinder. Not sure
* about other spells, but I imagine similar results."
*/ */
@Test @Test
public void ZadaHedronSilverfurPartisan() { public void ZadaHedronSilverfurPartisan() {
@ -206,12 +207,13 @@ public class CopySpellTest extends CardTestPlayerBase {
} }
/** /**
* {4}{U} Enchantment (Enchant Player) * {4}{U} Enchantment (Enchant Player) Whenever enchanted player casts an
* Whenever enchanted player casts an instant or sorcery spell, each other player may copy that spell * instant or sorcery spell, each other player may copy that spell and may
* and may choose new targets for the copy he or she controls. * choose new targets for the copy he or she controls.
* *
* Reported bug: "A player with Curse of Echoes attached to them played Bribery and the player who controlled the curse had control * Reported bug: "A player with Curse of Echoes attached to them played
* of all 3 copies. This seems to be the case for all spells." * Bribery and the player who controlled the curse had control of all 3
* copies. This seems to be the case for all spells."
*/ */
@Test @Test
public void testCurseOfEchoes() { public void testCurseOfEchoes() {
@ -236,4 +238,56 @@ public class CopySpellTest extends CardTestPlayerBase {
assertLife(playerB, 17); // copy redirected assertLife(playerB, 17); // copy redirected
} }
/**
* What happened was my opponent had an Atraxa, Praetors' Voice and a
* Walking Ballista with 2 counters in play. On my turn, I cast Flame Slash
* targeting Atraxa and holding priority, then I cast Dualcaster Mage. I
* change the target of the Flame Slash copy to Walking Ballista. My
* opponent removes the counters from Ballista to kill a 2/2 creature of
* mine. Game log says both Flame Slashes fizzle, and Atraxa ends up still
* being in play at the end of it all. Only the Flame Slash targeting
* Walking Ballista should have fizzled.
*/
@Test
public void testOnlyCopyFizzles() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
// Flying, vigilance, deathtouch, lifelink
// At the beginning of your end step, proliferate.
addCard(Zone.BATTLEFIELD, playerA, "Atraxa, Praetors' Voice", 4);
// Walking Ballista enters the battlefield with X +1/+1 counters on it.
// {4}: Put a +1/+1 counter on Walking Ballista.
// Remove a +1/+1 counter from Walking Ballista: It deals 1 damage to target creature or player.
addCard(Zone.HAND, playerA, "Walking Ballista"); // {X}{X}
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
// Flame Slash deals 4 damage to target creature.
addCard(Zone.HAND, playerB, "Flame Slash"); // Sorcery {R}
// Flash
// When Dualcaster Mage enters the battlefield, copy target instant or sorcery spell. You may choose new targets for the copy.
addCard(Zone.HAND, playerB, "Dualcaster Mage"); // Creature {1}{R}{R}
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Walking Ballista");
setChoice(playerA, "X=1");
setChoice(playerA, "Walking Ballista"); // for proliferate
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Flame Slash", "Atraxa, Praetors' Voice");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Dualcaster Mage");
addTarget(playerB, "Flame Slash"); // original target
setChoice(playerB, "Yes");
addTarget(playerB, "Walking Ballista");
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Remove a", "Silvercoat Lion", "Flame Slash", StackClause.WHILE_COPY_ON_STACK);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Remove a", "Silvercoat Lion", "Flame Slash", StackClause.WHILE_COPY_ON_STACK);
setStopAt(2, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerB, "Silvercoat Lion", 1);
assertPermanentCount(playerB, "Dualcaster Mage", 1);
assertPermanentCount(playerA, "Atraxa, Praetors' Voice", 0);
assertPermanentCount(playerA, "Walking Ballista", 0);
assertGraveyardCount(playerB, "Flame Slash", 1);
}
} }

View file

@ -190,6 +190,16 @@ public class TestPlayer implements Player {
} }
} }
return false; return false;
} else if (groups[2].startsWith("spellCopyOnStack=")) {
String spellOnStack = groups[2].substring(17);
for (StackObject stackObject : game.getStack()) {
if (stackObject.getStackAbility().toString().contains(spellOnStack)) {
if (stackObject.isCopy()) {
return true;
}
}
}
return false;
} else if (groups[2].startsWith("!spellOnStack=")) { } else if (groups[2].startsWith("!spellOnStack=")) {
String spellNotOnStack = groups[2].substring(14); String spellNotOnStack = groups[2].substring(14);
for (StackObject stackObject : game.getStack()) { for (StackObject stackObject : game.getStack()) {
@ -223,7 +233,7 @@ public class TestPlayer implements Player {
boolean result = true; boolean result = true;
for (int i = 1; i < groupsForTargetHandling.length; i++) { for (int i = 1; i < groupsForTargetHandling.length; i++) {
String group = groupsForTargetHandling[i]; String group = groupsForTargetHandling[i];
if (group.startsWith("spellOnStack") || group.startsWith("spellOnTopOfStack") || group.startsWith("!spellOnStack") || group.startsWith("target=null") || group.startsWith("manaInPool=")) { if (group.startsWith("spell") || group.startsWith("!spell") || group.startsWith("target=null") || group.startsWith("manaInPool=")) {
break; break;
} }
if (ability instanceof SpellAbility && ((SpellAbility) ability).getSpellAbilityType().equals(SpellAbilityType.SPLIT_FUSED)) { if (ability instanceof SpellAbility && ((SpellAbility) ability).getSpellAbilityType().equals(SpellAbilityType.SPLIT_FUSED)) {

View file

@ -1022,6 +1022,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
public enum StackClause { public enum StackClause {
WHILE_ON_STACK, WHILE_ON_STACK,
WHILE_COPY_ON_STACK,
WHILE_NOT_ON_STACK WHILE_NOT_ON_STACK
} }
@ -1105,6 +1106,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
* @param targetName if not target has to be defined use the constant * @param targetName if not target has to be defined use the constant
* NO_TARGET * NO_TARGET
* @param spellOnStack * @param spellOnStack
* @param clause
*/ */
public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, String targetName, String spellOnStack, StackClause clause) { public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, String targetName, String spellOnStack, StackClause clause) {
StringBuilder sb = new StringBuilder("activate:").append(ability); StringBuilder sb = new StringBuilder("activate:").append(ability);
@ -1112,7 +1114,19 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
sb.append("$target=").append(targetName); sb.append("$target=").append(targetName);
} }
if (spellOnStack != null && !spellOnStack.isEmpty()) { if (spellOnStack != null && !spellOnStack.isEmpty()) {
sb.append('$').append(StackClause.WHILE_ON_STACK.equals(clause) ? "" : "!").append("spellOnStack=").append(spellOnStack); sb.append('$');
switch (clause) {
case WHILE_ON_STACK:
sb.append("spellOnStack=");
break;
case WHILE_NOT_ON_STACK:
sb.append("!spellOnStack=");
break;
case WHILE_COPY_ON_STACK:
sb.append("spellCopyOnStack=");
break;
}
sb.append(spellOnStack);
} }
player.addAction(turnNum, step, sb.toString()); player.addAction(turnNum, step, sb.toString());
} }

View file

@ -27,6 +27,10 @@
*/ */
package mage.game.stack; package mage.game.stack;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObject; import mage.MageObject;
import mage.Mana; import mage.Mana;
@ -63,11 +67,6 @@ import mage.game.permanent.PermanentCard;
import mage.players.Player; import mage.players.Player;
import mage.util.GameLog; import mage.util.GameLog;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
/** /**
* *
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
@ -394,7 +393,8 @@ public class Spell extends StackObjImpl implements Card {
} }
} }
} else { } else {
card.removeFromZone(game, Zone.STACK, sourceId); // Copied spell, only remove from stack
game.getStack().remove(this);
} }
} }