forked from External/mage
[TDM] fix Mistrise Village effect
* Add additional tests and fix effect to only work on next spell cast * closes #13891
This commit is contained in:
parent
ef270721a5
commit
05e304e621
2 changed files with 185 additions and 24 deletions
|
|
@ -1,10 +1,6 @@
|
||||||
package mage.cards.m;
|
package mage.cards.m;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import mage.MageObject;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.CantBeCounteredSourceAbility;
|
|
||||||
import mage.abilities.common.EntersBattlefieldTappedUnlessAbility;
|
import mage.abilities.common.EntersBattlefieldTappedUnlessAbility;
|
||||||
import mage.abilities.common.SimpleActivatedAbility;
|
import mage.abilities.common.SimpleActivatedAbility;
|
||||||
import mage.abilities.condition.common.YouControlPermanentCondition;
|
import mage.abilities.condition.common.YouControlPermanentCondition;
|
||||||
|
|
@ -13,7 +9,6 @@ import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
|
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
|
||||||
import mage.abilities.effects.Effect;
|
import mage.abilities.effects.Effect;
|
||||||
import mage.abilities.effects.common.AddContinuousEffectToGame;
|
import mage.abilities.effects.common.AddContinuousEffectToGame;
|
||||||
import mage.abilities.effects.common.continuous.NextSpellCastHasAbilityEffect;
|
|
||||||
import mage.abilities.mana.BlueManaAbility;
|
import mage.abilities.mana.BlueManaAbility;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
|
|
@ -27,6 +22,10 @@ import mage.game.Game;
|
||||||
import mage.game.events.GameEvent;
|
import mage.game.events.GameEvent;
|
||||||
import mage.game.stack.Spell;
|
import mage.game.stack.Spell;
|
||||||
import mage.game.stack.StackObject;
|
import mage.game.stack.StackObject;
|
||||||
|
import mage.watchers.common.SpellsCastWatcher;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -71,6 +70,8 @@ public final class MistriseVillage extends CardImpl {
|
||||||
|
|
||||||
class MistriseCantBeCounteredEffect extends ContinuousRuleModifyingEffectImpl {
|
class MistriseCantBeCounteredEffect extends ContinuousRuleModifyingEffectImpl {
|
||||||
|
|
||||||
|
private int spellsCastThisTurn;
|
||||||
|
|
||||||
public MistriseCantBeCounteredEffect() {
|
public MistriseCantBeCounteredEffect() {
|
||||||
super(Duration.OneUse, Outcome.Benefit, false, true);
|
super(Duration.OneUse, Outcome.Benefit, false, true);
|
||||||
staticText = "the next spell you cast this turn can't be countered";
|
staticText = "the next spell you cast this turn can't be countered";
|
||||||
|
|
@ -78,6 +79,7 @@ class MistriseCantBeCounteredEffect extends ContinuousRuleModifyingEffectImpl {
|
||||||
|
|
||||||
protected MistriseCantBeCounteredEffect(final MistriseCantBeCounteredEffect effect) {
|
protected MistriseCantBeCounteredEffect(final MistriseCantBeCounteredEffect effect) {
|
||||||
super(effect);
|
super(effect);
|
||||||
|
this.spellsCastThisTurn = effect.spellsCastThisTurn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -90,6 +92,15 @@ class MistriseCantBeCounteredEffect extends ContinuousRuleModifyingEffectImpl {
|
||||||
return event.getType() == GameEvent.EventType.COUNTER;
|
return event.getType() == GameEvent.EventType.COUNTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Ability source, Game game) {
|
||||||
|
super.init(source, game);
|
||||||
|
SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class);
|
||||||
|
if (watcher != null) {
|
||||||
|
spellsCastThisTurn = watcher.getSpellsCastThisTurn(source.getControllerId()).size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getInfoMessage(Ability source, GameEvent event, Game game) {
|
public String getInfoMessage(Ability source, GameEvent event, Game game) {
|
||||||
StackObject sourceObject = game.getStack().getStackObject(event.getSourceId());
|
StackObject sourceObject = game.getStack().getStackObject(event.getSourceId());
|
||||||
|
|
@ -103,10 +114,17 @@ class MistriseCantBeCounteredEffect extends ContinuousRuleModifyingEffectImpl {
|
||||||
@Override
|
@Override
|
||||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||||
Spell spell = game.getStack().getSpell(event.getTargetId());
|
Spell spell = game.getStack().getSpell(event.getTargetId());
|
||||||
boolean res = spell != null && spell.isControlledBy(source.getControllerId());
|
SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class);
|
||||||
if (res) {
|
if (spell == null || !spell.isControlledBy(source.getControllerId()) || watcher == null) {
|
||||||
discard();
|
return false;
|
||||||
}
|
}
|
||||||
return res;
|
List<Spell> spellsCast = watcher.getSpellsCastThisTurn(source.getControllerId());
|
||||||
|
for (int i = 0; i < spellsCast.size(); i++) {
|
||||||
|
if (i == spellsCastThisTurn && spellsCast.get(i).getId().equals(spell.getId())) {
|
||||||
|
discard();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,41 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
public class MistriseVillageTest extends CardTestPlayerBase {
|
public class MistriseVillageTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Mistrise Village
|
||||||
|
Land
|
||||||
|
This land enters tapped unless you control a Mountain or a Forest.
|
||||||
|
{T}: Add {U}.
|
||||||
|
{U}, {T}: The next spell you cast this turn can’t be countered.
|
||||||
|
*/
|
||||||
private static final String MISTRISE = "Mistrise Village";
|
private static final String MISTRISE = "Mistrise Village";
|
||||||
private static final String COUNTER = "Counterspell";
|
private static final String COUNTERSPELL = "Counterspell";
|
||||||
private static final String CUB = "Bear Cub";
|
private static final String BEAR_CUB = "Bear Cub";
|
||||||
private static final String BEARS = "Balduvian Bears";
|
private static final String BALDUVIAN_BEARS = "Balduvian Bears";
|
||||||
|
/*
|
||||||
|
Force of Negation
|
||||||
|
{1}{U}{U}
|
||||||
|
Instant
|
||||||
|
If it’s not your turn, you may exile a blue card from your hand rather than pay this spell’s mana cost.
|
||||||
|
Counter target noncreature spell. If that spell is countered this way, exile it instead of putting it into its owner’s graveyard.
|
||||||
|
*/
|
||||||
|
public static final String FORCE_OF_NEGATION = "Force of Negation";
|
||||||
|
/*
|
||||||
|
Narset, Parter of Veils
|
||||||
|
{1}{U}{U}
|
||||||
|
Legendary Planeswalker — Narset
|
||||||
|
Each opponent can’t draw more than one card each turn.
|
||||||
|
−2: Look at the top four cards of your library. You may reveal a noncreature, nonland card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
|
||||||
|
*/
|
||||||
|
public static final String NARSET_PARTER_OF_VEILS = "Narset, Parter of Veils";
|
||||||
|
/*
|
||||||
|
Aether Spellbomb
|
||||||
|
{1}
|
||||||
|
Artifact
|
||||||
|
{U}, Sacrifice this artifact: Return target creature to its owner’s hand.
|
||||||
|
{1}, Sacrifice this artifact: Draw a card.
|
||||||
|
*/
|
||||||
|
public static final String AETHER_SPELLBOMB = "Aether Spellbomb";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCounter() {
|
public void testCounter() {
|
||||||
|
|
@ -21,22 +52,134 @@ public class MistriseVillageTest extends CardTestPlayerBase {
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Island");
|
addCard(Zone.BATTLEFIELD, playerA, "Island");
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
|
||||||
addCard(Zone.BATTLEFIELD, playerB, "Island", 4);
|
addCard(Zone.BATTLEFIELD, playerB, "Island", 4);
|
||||||
addCard(Zone.HAND, playerB, COUNTER, 2);
|
addCard(Zone.HAND, playerB, COUNTERSPELL, 2);
|
||||||
addCard(Zone.HAND, playerA, CUB);
|
addCard(Zone.HAND, playerA, BEAR_CUB);
|
||||||
addCard(Zone.HAND, playerA, BEARS);
|
addCard(Zone.HAND, playerA, BALDUVIAN_BEARS);
|
||||||
|
|
||||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}");
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise
|
||||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, CUB, true);
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, BEAR_CUB, true);
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, COUNTER, CUB, CUB);
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, COUNTERSPELL, BEAR_CUB, BEAR_CUB);
|
||||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, BEARS, true);
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, BALDUVIAN_BEARS, true);
|
||||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, COUNTER, BEARS, BEARS);
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, COUNTERSPELL, BALDUVIAN_BEARS, BALDUVIAN_BEARS);
|
||||||
|
|
||||||
setStopAt(1, PhaseStep.END_TURN);
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
assertPermanentCount(playerA, CUB, 1);
|
assertPermanentCount(playerA, BEAR_CUB, 1);
|
||||||
assertGraveyardCount(playerA, BEARS, 1);
|
assertGraveyardCount(playerA, BALDUVIAN_BEARS, 1);
|
||||||
assertGraveyardCount(playerB, COUNTER, 2);
|
assertGraveyardCount(playerB, COUNTERSPELL, 2);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Test
|
||||||
|
public void testCastCounterAfterCastingSpell() {
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
skipInitShuffling();
|
||||||
|
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, MISTRISE);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 8);
|
||||||
|
addCard(Zone.HAND, playerA, NARSET_PARTER_OF_VEILS);
|
||||||
|
addCard(Zone.HAND, playerA, AETHER_SPELLBOMB);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Island", 3);
|
||||||
|
addCard(Zone.HAND, playerB, FORCE_OF_NEGATION);
|
||||||
|
addCard(Zone.LIBRARY, playerA, "Stock Up");
|
||||||
|
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, NARSET_PARTER_OF_VEILS);
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2: "); // Narset ability
|
||||||
|
setChoice(playerA, true);
|
||||||
|
addTarget(playerA, "Stock Up");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Stock Up");
|
||||||
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, FORCE_OF_NEGATION, "Stock Up");
|
||||||
|
|
||||||
|
waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN, playerA);
|
||||||
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, AETHER_SPELLBOMB);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, NARSET_PARTER_OF_VEILS, 1);
|
||||||
|
assertPermanentCount(playerA, AETHER_SPELLBOMB, 1);
|
||||||
|
assertGraveyardCount(playerB, FORCE_OF_NEGATION, 1);
|
||||||
|
assertExileCount(playerA, "Stock Up", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleMistrise() {
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
skipInitShuffling();
|
||||||
|
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, MISTRISE,2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 8);
|
||||||
|
addCard(Zone.HAND, playerA, NARSET_PARTER_OF_VEILS);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Island", 6);
|
||||||
|
addCard(Zone.HAND, playerB, FORCE_OF_NEGATION, 2);
|
||||||
|
addCard(Zone.LIBRARY, playerA, AETHER_SPELLBOMB);
|
||||||
|
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 2);
|
||||||
|
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, NARSET_PARTER_OF_VEILS);
|
||||||
|
checkPlayableAbility("cast force", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Force", true);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, FORCE_OF_NEGATION, NARSET_PARTER_OF_VEILS); // Fail to counter
|
||||||
|
setChoice(playerB, "Cast with no alternative cost");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2: "); // Narset ability
|
||||||
|
setChoice(playerA, true);
|
||||||
|
addTarget(playerA, AETHER_SPELLBOMB);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, AETHER_SPELLBOMB);
|
||||||
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, FORCE_OF_NEGATION, AETHER_SPELLBOMB); // Successful counter
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, NARSET_PARTER_OF_VEILS, 1);
|
||||||
|
assertGraveyardCount(playerB, FORCE_OF_NEGATION, 2);
|
||||||
|
assertExileCount(playerA, AETHER_SPELLBOMB, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleMistriseNotConsecutive() {
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
skipInitShuffling();
|
||||||
|
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, MISTRISE,2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 8);
|
||||||
|
addCard(Zone.HAND, playerA, NARSET_PARTER_OF_VEILS);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Island", 6);
|
||||||
|
addCard(Zone.HAND, playerB, FORCE_OF_NEGATION, 2);
|
||||||
|
addCard(Zone.LIBRARY, playerA, AETHER_SPELLBOMB);
|
||||||
|
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1);
|
||||||
|
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, NARSET_PARTER_OF_VEILS);
|
||||||
|
checkPlayableAbility("cast force", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Force", true);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, FORCE_OF_NEGATION, NARSET_PARTER_OF_VEILS); // Fail to counter
|
||||||
|
setChoice(playerB, "Cast with no alternative cost");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2: "); // Narset ability
|
||||||
|
setChoice(playerA, true);
|
||||||
|
addTarget(playerA, AETHER_SPELLBOMB);
|
||||||
|
|
||||||
|
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{U}, {T}"); // Activate mistrise
|
||||||
|
waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN, 1);
|
||||||
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, AETHER_SPELLBOMB);
|
||||||
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, FORCE_OF_NEGATION, AETHER_SPELLBOMB); // Successful counter
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, NARSET_PARTER_OF_VEILS, 1);
|
||||||
|
assertGraveyardCount(playerB, FORCE_OF_NEGATION, 2);
|
||||||
|
assertPermanentCount(playerA, AETHER_SPELLBOMB, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue