refactor: fixed dies events support in single cards (part 3);

This commit is contained in:
Oleg Agafonov 2024-11-09 17:55:07 +04:00
parent c3343110f3
commit dc9f349828
14 changed files with 84 additions and 2 deletions

View file

@ -54,6 +54,7 @@ class DaxosBlessedByTheSunAbility extends TriggeredAbilityImpl {
DaxosBlessedByTheSunAbility() { DaxosBlessedByTheSunAbility() {
super(Zone.BATTLEFIELD, new GainLifeEffect(1)); super(Zone.BATTLEFIELD, new GainLifeEffect(1));
setLeavesTheBattlefieldTrigger(true);
} }
private DaxosBlessedByTheSunAbility(DaxosBlessedByTheSunAbility ability) { private DaxosBlessedByTheSunAbility(DaxosBlessedByTheSunAbility ability) {

View file

@ -62,6 +62,7 @@ class DeathTyrantTriggeredAbility extends TriggeredAbilityImpl {
DeathTyrantTriggeredAbility() { DeathTyrantTriggeredAbility() {
super(Zone.BATTLEFIELD, new CreateTokenEffect(new ZombieToken())); super(Zone.BATTLEFIELD, new CreateTokenEffect(new ZombieToken()));
setTriggerPhrase("Whenever an attacking creature you control or a blocking creature an opponent controls dies, "); setTriggerPhrase("Whenever an attacking creature you control or a blocking creature an opponent controls dies, ");
setLeavesTheBattlefieldTrigger(true);
} }
private DeathTyrantTriggeredAbility(final DeathTyrantTriggeredAbility ability) { private DeathTyrantTriggeredAbility(final DeathTyrantTriggeredAbility ability) {

View file

@ -53,6 +53,7 @@ class DreadhoundTriggeredAbility extends TriggeredAbilityImpl {
public DreadhoundTriggeredAbility() { public DreadhoundTriggeredAbility() {
super(Zone.BATTLEFIELD, new LoseLifeOpponentsEffect(1)); super(Zone.BATTLEFIELD, new LoseLifeOpponentsEffect(1));
setTriggerPhrase("Whenever a creature dies or a creature card is put into a graveyard from a library, "); setTriggerPhrase("Whenever a creature dies or a creature card is put into a graveyard from a library, ");
setLeavesTheBattlefieldTrigger(true);
} }
private DreadhoundTriggeredAbility(final DreadhoundTriggeredAbility ability) { private DreadhoundTriggeredAbility(final DreadhoundTriggeredAbility ability) {

View file

@ -48,6 +48,7 @@ class GutterGrimeTriggeredAbility extends TriggeredAbilityImpl {
public GutterGrimeTriggeredAbility() { public GutterGrimeTriggeredAbility() {
super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.SLIME.createInstance()), false); super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.SLIME.createInstance()), false);
this.addEffect(new GutterGrimeEffect()); this.addEffect(new GutterGrimeEffect());
setLeavesTheBattlefieldTrigger(true);
} }
private GutterGrimeTriggeredAbility(final GutterGrimeTriggeredAbility ability) { private GutterGrimeTriggeredAbility(final GutterGrimeTriggeredAbility ability) {
@ -83,6 +84,11 @@ class GutterGrimeTriggeredAbility extends TriggeredAbilityImpl {
public String getRule() { public String getRule() {
return "Whenever a nontoken creature you control dies, put a slime counter on {this}, then create a green Ooze creature token with \"This creature's power and toughness are each equal to the number of slime counters on {this}.\""; return "Whenever a nontoken creature you control dies, put a slime counter on {this}, then create a green Ooze creature token with \"This creature's power and toughness are each equal to the number of slime counters on {this}.\"";
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game);
}
} }
class GutterGrimeEffect extends OneShotEffect { class GutterGrimeEffect extends OneShotEffect {

View file

@ -1,6 +1,7 @@
package mage.cards.h; package mage.cards.h;
import mage.MageInt; import mage.MageInt;
import mage.MageObject;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.keyword.LifelinkAbility; import mage.abilities.keyword.LifelinkAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -51,6 +52,7 @@ class HatefulEidolonTriggeredAbility extends TriggeredAbilityImpl {
HatefulEidolonTriggeredAbility() { HatefulEidolonTriggeredAbility() {
super(Zone.BATTLEFIELD, null, false); super(Zone.BATTLEFIELD, null, false);
setLeavesTheBattlefieldTrigger(true);
} }
private HatefulEidolonTriggeredAbility(final HatefulEidolonTriggeredAbility ability) { private HatefulEidolonTriggeredAbility(final HatefulEidolonTriggeredAbility ability) {
@ -105,4 +107,9 @@ class HatefulEidolonTriggeredAbility extends TriggeredAbilityImpl {
return "Whenever an enchanted creature dies, draw a card for each " return "Whenever an enchanted creature dies, draw a card for each "
+ "Aura you controlled that was attached to it."; + "Aura you controlled that was attached to it.";
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game);
}
} }

View file

@ -1,7 +1,9 @@
package mage.cards.i; package mage.cards.i;
import mage.MageInt; import mage.MageInt;
import mage.MageObject;
import mage.abilities.DelayedTriggeredAbility; import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.dynamicvalue.common.SavedDamageValue;
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
@ -55,6 +57,7 @@ class InfestedThrinaxTriggeredAbility extends DelayedTriggeredAbility {
InfestedThrinaxTriggeredAbility() { InfestedThrinaxTriggeredAbility() {
super(new CreateTokenEffect(new SaprolingToken(), SavedDamageValue.MUCH), Duration.EndOfTurn, false, false); super(new CreateTokenEffect(new SaprolingToken(), SavedDamageValue.MUCH), Duration.EndOfTurn, false, false);
setLeavesTheBattlefieldTrigger(true);
} }
private InfestedThrinaxTriggeredAbility(final InfestedThrinaxTriggeredAbility ability) { private InfestedThrinaxTriggeredAbility(final InfestedThrinaxTriggeredAbility ability) {
@ -89,4 +92,9 @@ class InfestedThrinaxTriggeredAbility extends DelayedTriggeredAbility {
return "Whenever a nontoken creature you control dies, " + return "Whenever a nontoken creature you control dies, " +
"create a number of 1/1 green Saproling creature tokens equal to that creature's power."; "create a number of 1/1 green Saproling creature tokens equal to that creature's power.";
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game);
}
} }

View file

@ -94,6 +94,7 @@ class JerrenCorruptedBishopTriggeredAbility extends TriggeredAbilityImpl {
JerrenCorruptedBishopTriggeredAbility() { JerrenCorruptedBishopTriggeredAbility() {
super(Zone.BATTLEFIELD, new LoseLifeSourceControllerEffect(1)); super(Zone.BATTLEFIELD, new LoseLifeSourceControllerEffect(1));
this.addEffect(new CreateTokenEffect(new HumanToken())); this.addEffect(new CreateTokenEffect(new HumanToken()));
setLeavesTheBattlefieldTrigger(true);
} }
private JerrenCorruptedBishopTriggeredAbility(final JerrenCorruptedBishopTriggeredAbility ability) { private JerrenCorruptedBishopTriggeredAbility(final JerrenCorruptedBishopTriggeredAbility ability) {

View file

@ -1,8 +1,10 @@
package mage.cards.m; package mage.cards.m;
import mage.MageInt; import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility; import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.abilities.effects.common.continuous.BoostAllEffect;
@ -77,6 +79,7 @@ class MassacreGirlDelayedTriggeredAbility extends DelayedTriggeredAbility {
MassacreGirlDelayedTriggeredAbility() { MassacreGirlDelayedTriggeredAbility() {
super(new BoostAllEffect(-1, -1, Duration.EndOfTurn, true), Duration.EndOfTurn, false); super(new BoostAllEffect(-1, -1, Duration.EndOfTurn, true), Duration.EndOfTurn, false);
setLeavesTheBattlefieldTrigger(true);
} }
private MassacreGirlDelayedTriggeredAbility(final MassacreGirlDelayedTriggeredAbility ability) { private MassacreGirlDelayedTriggeredAbility(final MassacreGirlDelayedTriggeredAbility ability) {
@ -103,4 +106,9 @@ class MassacreGirlDelayedTriggeredAbility extends DelayedTriggeredAbility {
public String getRule() { public String getRule() {
return "Whenever a creature dies this turn, each creature other than {this} gets -1/-1 until end of turn"; return "Whenever a creature dies this turn, each creature other than {this} gets -1/-1 until end of turn";
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game);
}
} }

View file

@ -1994,6 +1994,9 @@ public class VerifyCardDataTest {
.filter(a -> !a.getRule().contains("with \"When")) // ignore token creating effects .filter(a -> !a.getRule().contains("with \"When")) // ignore token creating effects
.filter(a -> !a.getRule().contains("gains \"When")) // ignore token creating effects .filter(a -> !a.getRule().contains("gains \"When")) // ignore token creating effects
.filter(a -> !a.getRule().contains("and \"When")) // ignore token creating effects .filter(a -> !a.getRule().contains("and \"When")) // ignore token creating effects
.filter(a -> !card.getName().equals("Massacre Girl") // delayed trigger fixed, but verify check can't find it
&& !card.getName().equals("Infested Thrinax")
)
.filter(a -> !a.isLeavesTheBattlefieldTrigger()) .filter(a -> !a.isLeavesTheBattlefieldTrigger())
.forEach(a -> { .forEach(a -> {
fail(card, "abilities", "dies trigger must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + a.getClass().getSimpleName()); fail(card, "abilities", "dies trigger must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + a.getClass().getSimpleName());
@ -2319,6 +2322,9 @@ public class VerifyCardDataTest {
} }
private void checkWrongAbilitiesTextStart() { private void checkWrongAbilitiesTextStart() {
if (FULL_ABILITIES_CHECK_SET_CODES.isEmpty()) {
return;
}
System.out.println("Ability text checks started for " + FULL_ABILITIES_CHECK_SET_CODES); System.out.println("Ability text checks started for " + FULL_ABILITIES_CHECK_SET_CODES);
wrongAbilityStatsTotal = 0; wrongAbilityStatsTotal = 0;
wrongAbilityStatsGood = 0; wrongAbilityStatsGood = 0;
@ -2326,6 +2332,10 @@ public class VerifyCardDataTest {
} }
private void checkWrongAbilitiesTextEnd() { private void checkWrongAbilitiesTextEnd() {
if (FULL_ABILITIES_CHECK_SET_CODES.isEmpty()) {
return;
}
// TODO: implement tests result/stats by github actions to show in check message compared to prev version // TODO: implement tests result/stats by github actions to show in check message compared to prev version
System.out.println(); System.out.println();
System.out.printf("Stats for %d cards checked for abilities text:%n", wrongAbilityStatsTotal); System.out.printf("Stats for %d cards checked for abilities text:%n", wrongAbilityStatsTotal);

View file

@ -1255,13 +1255,13 @@ public abstract class AbilityImpl implements Ability {
} }
return allEvents.stream().anyMatch(e -> { return allEvents.stream().anyMatch(e -> {
// TODO: add more events with zone change logic (or make it even't param)? // TODO: add more events with zone change logic (or make it event's param)?
switch (e.getType()) { switch (e.getType()) {
case DESTROYED_PERMANENT: case DESTROYED_PERMANENT:
case EXPLOITED_CREATURE: case EXPLOITED_CREATURE:
return true; return true;
case ZONE_CHANGE: case ZONE_CHANGE:
return ((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD; return ((ZoneChangeEvent) e).getFromZone() == Zone.BATTLEFIELD;
default: default:
return false; return false;
} }

View file

@ -1,5 +1,6 @@
package mage.abilities.common; package mage.abilities.common;
import mage.MageObject;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
@ -32,6 +33,7 @@ public class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility
setTriggerPhrase(getWhen() + CardUtil.addArticle(filter.getMessage()) + " dealt damage by " setTriggerPhrase(getWhen() + CardUtil.addArticle(filter.getMessage()) + " dealt damage by "
+ CardUtil.getTextWithFirstCharLowerCase(attachmentType.verb()) + + CardUtil.getTextWithFirstCharLowerCase(attachmentType.verb()) +
" creature this turn dies, "); " creature this turn dies, ");
setLeavesTheBattlefieldTrigger(true);
} }
protected DealtDamageAttachedAndDiedTriggeredAbility(final DealtDamageAttachedAndDiedTriggeredAbility ability) { protected DealtDamageAttachedAndDiedTriggeredAbility(final DealtDamageAttachedAndDiedTriggeredAbility ability) {
@ -75,4 +77,9 @@ public class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility
} }
return true; return true;
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game);
}
} }

View file

@ -1,5 +1,6 @@
package mage.abilities.common; package mage.abilities.common;
import mage.MageObject;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.cards.Card; import mage.cards.Card;
@ -48,6 +49,7 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl {
this.setTargetPointer = setTargetPointer; this.setTargetPointer = setTargetPointer;
this.rememberSource = rememberSource; this.rememberSource = rememberSource;
setTriggerPhrase(generateTriggerPhrase()); setTriggerPhrase(generateTriggerPhrase());
setLeavesTheBattlefieldTrigger(true);
} }
protected DiesAttachedTriggeredAbility(final DiesAttachedTriggeredAbility ability) { protected DiesAttachedTriggeredAbility(final DiesAttachedTriggeredAbility ability) {
@ -157,4 +159,9 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl {
} }
return sb.toString(); return sb.toString();
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game);
}
} }

View file

@ -1,9 +1,11 @@
package mage.abilities.common.delayed; package mage.abilities.common.delayed;
import mage.MageObject;
import mage.abilities.DelayedTriggeredAbility; import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.Modes; import mage.abilities.Modes;
import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects; import mage.abilities.effects.Effects;
import mage.constants.Duration; import mage.constants.Duration;
@ -103,4 +105,14 @@ public class UntilYourNextTurnDelayedTriggeredAbility extends DelayedTriggeredAb
public int getSourceObjectZoneChangeCounter() { public int getSourceObjectZoneChangeCounter() {
return ability.getSourceObjectZoneChangeCounter(); return ability.getSourceObjectZoneChangeCounter();
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
if (isLeavesTheBattlefieldTrigger()) {
// TODO: leaves battlefield and die are not same! Is it possible make a diff logic?
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game);
} else {
return super.isInUseableZone(game, source, event);
}
}
} }

View file

@ -1,5 +1,6 @@
package mage.abilities.decorator; package mage.abilities.decorator;
import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Modes; import mage.abilities.Modes;
import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbility;
@ -41,6 +42,9 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl {
this.ability = ability; this.ability = ability;
this.condition = condition; this.condition = condition;
this.abilityText = text; this.abilityText = text;
if (ability.isLeavesTheBattlefieldTrigger()) {
this.setLeavesTheBattlefieldTrigger(true);
}
} }
protected ConditionalTriggeredAbility(final ConditionalTriggeredAbility triggered) { protected ConditionalTriggeredAbility(final ConditionalTriggeredAbility triggered) {
@ -118,4 +122,13 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl {
return this; return this;
} }
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
if (isLeavesTheBattlefieldTrigger()) {
// TODO: leaves battlefield and die are not same! Is it possible make a diff logic?
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game);
} else {
return super.isInUseableZone(game, source, event);
}
}
} }