[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:
jmlundeen 2025-08-13 16:25:53 -05:00
parent ef270721a5
commit 05e304e621
2 changed files with 185 additions and 24 deletions

View file

@ -1,10 +1,6 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.CantBeCounteredSourceAbility;
import mage.abilities.common.EntersBattlefieldTappedUnlessAbility;
import mage.abilities.common.SimpleActivatedAbility;
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.Effect;
import mage.abilities.effects.common.AddContinuousEffectToGame;
import mage.abilities.effects.common.continuous.NextSpellCastHasAbilityEffect;
import mage.abilities.mana.BlueManaAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -27,6 +22,10 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
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 {
private int spellsCastThisTurn;
public MistriseCantBeCounteredEffect() {
super(Duration.OneUse, Outcome.Benefit, false, true);
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) {
super(effect);
this.spellsCastThisTurn = effect.spellsCastThisTurn;
}
@Override
@ -90,6 +92,15 @@ class MistriseCantBeCounteredEffect extends ContinuousRuleModifyingEffectImpl {
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
public String getInfoMessage(Ability source, GameEvent event, Game game) {
StackObject sourceObject = game.getStack().getStackObject(event.getSourceId());
@ -103,10 +114,17 @@ class MistriseCantBeCounteredEffect extends ContinuousRuleModifyingEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Spell spell = game.getStack().getSpell(event.getTargetId());
boolean res = spell != null && spell.isControlledBy(source.getControllerId());
if (res) {
discard();
SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class);
if (spell == null || !spell.isControlledBy(source.getControllerId()) || watcher == null) {
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;
}
}

View file

@ -8,10 +8,41 @@ import org.mage.test.serverside.base.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 cant be countered.
*/
private static final String MISTRISE = "Mistrise Village";
private static final String COUNTER = "Counterspell";
private static final String CUB = "Bear Cub";
private static final String BEARS = "Balduvian Bears";
private static final String COUNTERSPELL = "Counterspell";
private static final String BEAR_CUB = "Bear Cub";
private static final String BALDUVIAN_BEARS = "Balduvian Bears";
/*
Force of Negation
{1}{U}{U}
Instant
If its not your turn, you may exile a blue card from your hand rather than pay this spells mana cost.
Counter target noncreature spell. If that spell is countered this way, exile it instead of putting it into its owners graveyard.
*/
public static final String FORCE_OF_NEGATION = "Force of Negation";
/*
Narset, Parter of Veils
{1}{U}{U}
Legendary Planeswalker Narset
Each opponent cant 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 owners hand.
{1}, Sacrifice this artifact: Draw a card.
*/
public static final String AETHER_SPELLBOMB = "Aether Spellbomb";
@Test
public void testCounter() {
@ -21,22 +52,134 @@ public class MistriseVillageTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Island");
addCard(Zone.BATTLEFIELD, playerA, "Forest", 4);
addCard(Zone.BATTLEFIELD, playerB, "Island", 4);
addCard(Zone.HAND, playerB, COUNTER, 2);
addCard(Zone.HAND, playerA, CUB);
addCard(Zone.HAND, playerA, BEARS);
addCard(Zone.HAND, playerB, COUNTERSPELL, 2);
addCard(Zone.HAND, playerA, BEAR_CUB);
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);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, CUB, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, COUNTER, CUB, CUB);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, BEARS, true);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, COUNTER, BEARS, BEARS);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, BEAR_CUB, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, COUNTERSPELL, BEAR_CUB, BEAR_CUB);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, BALDUVIAN_BEARS, true);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, COUNTERSPELL, BALDUVIAN_BEARS, BALDUVIAN_BEARS);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, CUB, 1);
assertGraveyardCount(playerA, BEARS, 1);
assertGraveyardCount(playerB, COUNTER, 2);
assertPermanentCount(playerA, BEAR_CUB, 1);
assertGraveyardCount(playerA, BALDUVIAN_BEARS, 1);
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);
}
}