refactor: fixed dies events support in single cards (part 8, related to #13089, continue from #13088);

This commit is contained in:
Oleg Agafonov 2024-12-14 15:48:32 +04:00
parent a970dc46c7
commit b855434a24
7 changed files with 67 additions and 5 deletions

View file

@ -64,6 +64,7 @@ class CuratorsWardTriggeredAbility extends TriggeredAbilityImpl {
public CuratorsWardTriggeredAbility() { public CuratorsWardTriggeredAbility() {
super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(2), false); super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(2), false);
setLeavesTheBattlefieldTrigger(true);
} }
private CuratorsWardTriggeredAbility(final CuratorsWardTriggeredAbility ability) { private CuratorsWardTriggeredAbility(final CuratorsWardTriggeredAbility ability) {

View file

@ -28,6 +28,7 @@ public final class GracefulAntelope extends CardImpl {
// Plainswalk // Plainswalk
this.addAbility(new PlainswalkAbility()); this.addAbility(new PlainswalkAbility());
// Whenever Graceful Antelope deals combat damage to a player, you may have target land become a Plains until Graceful Antelope leaves the battlefield. // Whenever Graceful Antelope deals combat damage to a player, you may have target land become a Plains until Graceful Antelope leaves the battlefield.
Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new BecomesBasicLandTargetEffect(Duration.UntilSourceLeavesBattlefield, SubType.PLAINS), true); Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new BecomesBasicLandTargetEffect(Duration.UntilSourceLeavesBattlefield, SubType.PLAINS), true);
ability.addTarget(new TargetLandPermanent()); ability.addTarget(new TargetLandPermanent());

View file

@ -33,8 +33,7 @@ public final class Portcullis extends CardImpl {
public Portcullis(UUID ownerId, CardSetInfo setInfo) { public Portcullis(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}"); super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}");
// Whenever a creature enters the battlefield, if there are two or more other creatures on the battlefield, exile that creature. // Whenever a creature enters the battlefield, if there are two or more other creatures on the battlefield, exile that creature. Return that card to the battlefield under its owner's control when Portcullis leaves the battlefield.
// Return that card to the battlefield under its owner's control when Portcullis leaves the battlefield.
String rule = "Whenever a creature enters the battlefield, if there are two or more other creatures on the battlefield, exile that creature."; String rule = "Whenever a creature enters the battlefield, if there are two or more other creatures on the battlefield, exile that creature.";
String rule2 = " Return that card to the battlefield under its owner's control when {this} leaves the battlefield."; String rule2 = " Return that card to the battlefield under its owner's control when {this} leaves the battlefield.";
TriggeredAbility ability = new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new PortcullisExileEffect(), TriggeredAbility ability = new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new PortcullisExileEffect(),

View file

@ -61,6 +61,7 @@ class SludgeStriderTriggeredAbility extends TriggeredAbilityImpl {
public SludgeStriderTriggeredAbility() { public SludgeStriderTriggeredAbility() {
// setting optional = false because DoIfCostPaid already asks the player // setting optional = false because DoIfCostPaid already asks the player
super(Zone.BATTLEFIELD, new DoIfCostPaid(new SludgeStriderEffect(), new GenericManaCost(1)), false); super(Zone.BATTLEFIELD, new DoIfCostPaid(new SludgeStriderEffect(), new GenericManaCost(1)), false);
setLeavesTheBattlefieldTrigger(true);
} }
private SludgeStriderTriggeredAbility(final SludgeStriderTriggeredAbility ability) { private SludgeStriderTriggeredAbility(final SludgeStriderTriggeredAbility ability) {
@ -69,7 +70,8 @@ class SludgeStriderTriggeredAbility extends TriggeredAbilityImpl {
@Override @Override
public boolean checkEventType(GameEvent event, Game game) { public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD || event.getType() == GameEvent.EventType.ZONE_CHANGE; return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD
|| event.getType() == GameEvent.EventType.ZONE_CHANGE;
} }
@Override @Override

View file

@ -0,0 +1,39 @@
package org.mage.test.cards.single.who;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author JayDi85
*/
public class CrackInTimeTest extends CardTestPlayerBase {
@Test
public void test_ExileUntilLeaves() {
addCustomEffect_TargetDestroy(playerA);
// When Crack in Time enters the battlefield and at the beginning of your precombat main phase,
// exile target creature an opponent controls until Crack in Time leaves the battlefield.
addCard(Zone.HAND, playerA, "Crack in Time"); // {3}{W}
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1);
// exile
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Crack in Time");
addTarget(playerA, "Balduvian Bears"); // exile on etb
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkExileCount("on exile", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Balduvian Bears", 1);
// destroy and return
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "target destroy", "Crack in Time");
waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN);
checkExileCount("on return", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 0);
checkPermanentCount("on return", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Balduvian Bears", 1);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
}
}

View file

@ -13,6 +13,7 @@ import mage.abilities.condition.Condition;
import mage.abilities.costs.Cost; import mage.abilities.costs.Cost;
import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.common.ExileUntilSourceLeavesEffect;
import mage.abilities.effects.common.FightTargetsEffect; import mage.abilities.effects.common.FightTargetsEffect;
import mage.abilities.effects.common.counter.ProliferateEffect; import mage.abilities.effects.common.counter.ProliferateEffect;
import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.effects.keyword.ScryEffect;
@ -74,6 +75,7 @@ public class VerifyCardDataTest {
private static final String FULL_ABILITIES_CHECK_SET_CODES = "MH3;M3C"; // check ability text due mtgjson, can use multiple sets like MAT;CMD or * for all private static final String FULL_ABILITIES_CHECK_SET_CODES = "MH3;M3C"; // check ability text due mtgjson, can use multiple sets like MAT;CMD or * for all
private static final boolean CHECK_ONLY_ABILITIES_TEXT = false; // use when checking text locally, suppresses unnecessary checks and output messages private static final boolean CHECK_ONLY_ABILITIES_TEXT = false; // use when checking text locally, suppresses unnecessary checks and output messages
private static final boolean CHECK_COPYABLE_FIELDS = true; // disable for better verify test performance
private static final boolean AUTO_FIX_SAMPLE_DECKS = false; // debug only: auto-fix sample decks by test_checkSampleDecks test run private static final boolean AUTO_FIX_SAMPLE_DECKS = false; // debug only: auto-fix sample decks by test_checkSampleDecks test run
@ -1632,7 +1634,9 @@ public class VerifyCardDataTest {
checkRarityAndBasicLands(card, ref); checkRarityAndBasicLands(card, ref);
checkMissingAbilities(card, ref); checkMissingAbilities(card, ref);
checkWrongSymbolsInRules(card); checkWrongSymbolsInRules(card);
checkCardCanBeCopied(card); if (CHECK_COPYABLE_FIELDS) {
checkCardCanBeCopied(card);
}
} }
checkWrongAbilitiesText(card, ref, cardIndex); checkWrongAbilitiesText(card, ref, cardIndex);
} }
@ -1994,6 +1998,10 @@ public class VerifyCardDataTest {
ignoredCards.add("Infested Thrinax"); ignoredCards.add("Infested Thrinax");
ignoredCards.add("Xira, the Golden Sting"); ignoredCards.add("Xira, the Golden Sting");
ignoredCards.add("Mawloc"); ignoredCards.add("Mawloc");
ignoredCards.add("Crack in Time");
ignoredCards.add("Mysterious Limousine");
ignoredCards.add("Graceful Antelope");
ignoredCards.add("Portcullis");
List<String> ignoredAbilities = new ArrayList<>(); List<String> ignoredAbilities = new ArrayList<>();
ignoredAbilities.add("roll"); // roll die effects ignoredAbilities.add("roll"); // roll die effects
ignoredAbilities.add("with \"When"); // token creating effects ignoredAbilities.add("with \"When"); // token creating effects
@ -2009,6 +2017,18 @@ public class VerifyCardDataTest {
if (triggeredAbility == null) { if (triggeredAbility == null) {
continue; continue;
} }
// ignore exile effects
// example 1: exile up to one other target nonland permanent until Constable of the Realm leaves the battlefield.
if (ability.getAllEffects().stream().anyMatch(e -> e instanceof ExileUntilSourceLeavesEffect)) {
continue;
}
// example 2: When Hostage Taker enters the battlefield, exile another target artifact or creature until Hostage Taker leaves the battlefield
if (ability instanceof EntersBattlefieldTriggeredAbility) {
continue;
}
// search and check dies related abilities // search and check dies related abilities
String rules = triggeredAbility.getRule(); String rules = triggeredAbility.getRule();
if (ignoredAbilities.stream().anyMatch(rules::contains)) { if (ignoredAbilities.stream().anyMatch(rules::contains)) {
@ -2022,7 +2042,6 @@ public class VerifyCardDataTest {
&& rules.contains("graveyard") && rules.contains("graveyard")
&& rules.contains("from the battlefield"); && rules.contains("from the battlefield");
boolean isLeavesBattlefield = rules.contains("leaves the battlefield"); boolean isLeavesBattlefield = rules.contains("leaves the battlefield");
isLeavesBattlefield = false; // TODO: remove and fix all bad cards
if (triggeredAbility.isLeavesTheBattlefieldTrigger()) { if (triggeredAbility.isLeavesTheBattlefieldTrigger()) {
// TODO: add check for wrongly enabled settings too? // TODO: add check for wrongly enabled settings too?
} else { } else {

View file

@ -40,6 +40,7 @@ public class OnLeaveReturnExiledAbility extends DelayedTriggeredAbility {
super(new ReturnExiledPermanentsEffect(zone), Duration.OneUse); super(new ReturnExiledPermanentsEffect(zone), Duration.OneUse);
this.usesStack = false; this.usesStack = false;
this.setRuleVisible(false); this.setRuleVisible(false);
setLeavesTheBattlefieldTrigger(true);
} }
protected OnLeaveReturnExiledAbility(final OnLeaveReturnExiledAbility ability) { protected OnLeaveReturnExiledAbility(final OnLeaveReturnExiledAbility ability) {