Skip to content

Commit

Permalink
refactor: fixed dies events support in single cards (part 6);
Browse files Browse the repository at this point in the history
  • Loading branch information
JayDi85 committed Nov 30, 2024
1 parent d49ff89 commit b1024d2
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Mage.Sets/src/mage/cards/c/CallerOfTheClaw.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

package mage.cards.c;

import java.util.UUID;
Expand Down Expand Up @@ -39,6 +38,7 @@ public CallerOfTheClaw(UUID ownerId, CardSetInfo setInfo) {

// Flash
this.addAbility(FlashAbility.getInstance());

// When Caller of the Claw enters the battlefield, create a 2/2 green Bear creature token for each nontoken creature put into your graveyard from the battlefield this turn.
this.getSpellAbility().addWatcher(new CallerOfTheClawWatcher());
Effect effect = new CreateTokenEffect(new BearToken(), new CallerOfTheClawDynamicValue());
Expand Down
8 changes: 8 additions & 0 deletions Mage.Sets/src/mage/cards/d/DiabolicServitude.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mage.cards.d;

import java.util.UUID;

import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
Expand Down Expand Up @@ -91,6 +93,7 @@ class DiabolicServitudeCreatureDiesTriggeredAbility extends TriggeredAbilityImpl

public DiabolicServitudeCreatureDiesTriggeredAbility() {
super(Zone.BATTLEFIELD, new DiabolicServitudeExileCreatureEffect(), false);
setLeavesTheBattlefieldTrigger(true);
}

private DiabolicServitudeCreatureDiesTriggeredAbility(final DiabolicServitudeCreatureDiesTriggeredAbility ability) {
Expand Down Expand Up @@ -123,6 +126,11 @@ public boolean checkTrigger(GameEvent event, Game game) {
public String getRule() {
return "When the creature put onto the battlefield with {this} dies, exile it and return {this} to its owner's hand.";
}

@Override
public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game);
}
}

class DiabolicServitudeExileCreatureEffect extends OneShotEffect {
Expand Down
7 changes: 7 additions & 0 deletions Mage.Sets/src/mage/cards/e/EndlessEvil.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package mage.cards.e;

import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
Expand Down Expand Up @@ -97,6 +98,7 @@ class EndlessEvilBounceAbility extends TriggeredAbilityImpl {

public EndlessEvilBounceAbility() {
super(Zone.BATTLEFIELD, new ReturnToHandSourceEffect(false, true));
setLeavesTheBattlefieldTrigger(true);
}

private EndlessEvilBounceAbility(final EndlessEvilBounceAbility effect) {
Expand Down Expand Up @@ -126,4 +128,9 @@ public boolean checkTrigger(GameEvent event, Game game) {
public String getRule() {
return "When enchanted creature dies, if that creature was a Horror, return {this} to its owner's hand.";
}

@Override
public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game);
}
}
7 changes: 7 additions & 0 deletions Mage.Sets/src/mage/cards/e/EnigmaSphinx.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
Expand Down Expand Up @@ -65,6 +66,7 @@ public EnigmaSphinxTriggeredAbility(Effect effect) {
public EnigmaSphinxTriggeredAbility(Effect effect, boolean optional) {
super(Zone.ALL, effect, optional);
setTriggerPhrase("When {this} is put into your graveyard from the battlefield, ");
setLeavesTheBattlefieldTrigger(true);
}

private EnigmaSphinxTriggeredAbility(final EnigmaSphinxTriggeredAbility ability) {
Expand Down Expand Up @@ -94,6 +96,11 @@ public boolean checkTrigger(GameEvent event, Game game) {
}
return false;
}

@Override
public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game);
}
}

class EnigmaSphinxEffect extends OneShotEffect {
Expand Down
1 change: 1 addition & 0 deletions Mage.Sets/src/mage/cards/k/KayasGhostform.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class KayasGhostformTriggeredAbility extends TriggeredAbilityImpl {

KayasGhostformTriggeredAbility() {
super(Zone.ALL, new ReturnToBattlefieldUnderYourControlAttachedEffect(), false);
setLeavesTheBattlefieldTrigger(true);
}

private KayasGhostformTriggeredAbility(final KayasGhostformTriggeredAbility ability) {
Expand Down
7 changes: 7 additions & 0 deletions Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.PutIntoGraveFromBattlefieldAllTriggeredAbility;
import mage.abilities.effects.common.CreateTokenEffect;
Expand Down Expand Up @@ -59,6 +60,7 @@ class MagusOfTheBridgeTriggeredAbility extends TriggeredAbilityImpl {
public MagusOfTheBridgeTriggeredAbility() {
super(Zone.BATTLEFIELD, new ExileSourceEffect());
setTriggerPhrase("When a creature is put into an opponent's graveyard from the battlefield, ");
setLeavesTheBattlefieldTrigger(true);
}

private MagusOfTheBridgeTriggeredAbility(final MagusOfTheBridgeTriggeredAbility ability) {
Expand Down Expand Up @@ -86,4 +88,9 @@ public boolean checkTrigger(GameEvent event, Game game) {
}
return false;
}

@Override
public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game);
}
}
1 change: 1 addition & 0 deletions Mage.Sets/src/mage/cards/t/TaekoThePatientAvalanche.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class TaekoThePatientAvalancheTriggeredAbility extends TriggeredAbilityImpl {
super(Zone.BATTLEFIELD, new ScryEffect(1, false));
this.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()).concatBy("and"));
this.setTriggerPhrase("Whenever another creature you control leaves the battlefield, if it didn't die, ");
setLeavesTheBattlefieldTrigger(true);
}

private TaekoThePatientAvalancheTriggeredAbility(final TaekoThePatientAvalancheTriggeredAbility ability) {
Expand Down
73 changes: 46 additions & 27 deletions Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1985,33 +1985,52 @@ private void checkMissingAbilities(Card card, MtgJsonCard ref) {
fail(card, "abilities", "mutate cards aren't implemented and shouldn't be available");
}

// special check: wrong dies triggers
card.getAbilities().stream()
.filter(a -> a instanceof TriggeredAbility)
.map(a -> (TriggeredAbility) a)
.filter(a -> !a.isLeavesTheBattlefieldTrigger())
//.filter(a -> a.getRule().contains("whenever") || a.getRule().contains("Whenever")) // TODO: research failed cards
.filter(a -> a.getRule().contains("die ")
|| a.getRule().contains("dies ")
|| a.getRule().contains("die,")
|| a.getRule().contains("dies,")
|| (a.getRule().contains("put into")
&& a.getRule().contains("graveyard")
&& a.getRule().contains("from the battlefield"))
)
.filter(a -> !a.getRule().contains("roll")) // ignore roll die 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("and \"When")) // ignore token creating effects
.filter(a -> !a.getRule().contains("dies while {this} is in your graveyard")) // ignore Boneyard Scourge
.filter(a -> !a.getRule().contains("all creature cards that were put into your")) // ignore Fell Shepherd
.filter(a -> !card.getName().equals("Massacre Girl") // delayed trigger fixed, but verify check can't find it
&& !card.getName().equals("Infested Thrinax")
&& !card.getName().equals("Xira, the Golden Sting")
)
.forEach(a -> {
fail(card, "abilities", "dies trigger must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + a.getClass().getSimpleName());
});
// special check: wrong dies triggers (there are also a runtime check on wrong usage, see isInUseableZoneDiesTrigger)
Set<String> ignoredCards = new HashSet<>();
ignoredCards.add("Caller of the Claw");
ignoredCards.add("Boneyard Scourge");
ignoredCards.add("Fell Shepherd");
ignoredCards.add("Massacre Girl");
ignoredCards.add("Infested Thrinax");
ignoredCards.add("Xira, the Golden Sting");
ignoredCards.add("Mawloc");
List<String> ignoredAbilities = new ArrayList<>();
ignoredAbilities.add("roll"); // roll die effects
ignoredAbilities.add("with \"When"); // token creating effects
ignoredAbilities.add("gains \"When"); // token creating effects
ignoredAbilities.add("and \"When"); // token creating effects
ignoredAbilities.add("it has \"When"); // token creating effects
ignoredAbilities.add("beginning of your end step"); // step triggers
ignoredAbilities.add("beginning of each end step"); // step triggers
ignoredAbilities.add("beginning of combat"); // step triggers
if (!ignoredCards.contains(card.getName())) {
for (Ability ability : card.getAbilities()) {
TriggeredAbility triggeredAbility = ability instanceof TriggeredAbility ? (TriggeredAbility) ability : null;
if (triggeredAbility == null) {
continue;
}
// search and check dies related abilities
String rules = triggeredAbility.getRule();
if (ignoredAbilities.stream().anyMatch(rules::contains)) {
continue;
}
boolean isDiesAbility = rules.contains("die ")
|| rules.contains("dies ")
|| rules.contains("die,")
|| rules.contains("dies,");
boolean isPutToGraveAbility = rules.contains("put into")
&& rules.contains("graveyard")
&& rules.contains("from the battlefield");
if (triggeredAbility.isLeavesTheBattlefieldTrigger()) {
// TODO: add check for wrongly enabled settings too?
} else {
if (isDiesAbility || isPutToGraveAbility) {
fail(card, "abilities", "dies related trigger must use setLeavesTheBattlefieldTrigger(true) and possibly override isInUseableZone - "
+ triggeredAbility.getClass().getSimpleName());
}
}
}
}

// special check: duplicated words in ability text (wrong target/filter usage)
// example: You may exile __two two__ blue cards
Expand Down
2 changes: 1 addition & 1 deletion Mage/src/main/java/mage/abilities/AbilityImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1290,7 +1290,7 @@ protected static UUID getRealSourceObjectId(Ability sourceAbility, MageObject so
}

@Override
public boolean hasSourceObjectAbility(Game game, MageObject sourceObject, GameEvent event) {
public final boolean hasSourceObjectAbility(Game game, MageObject sourceObject, GameEvent event) {
MageObject object = sourceObject;
if (object == null) {
object = game.getPermanentEntering(getSourceId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public HauntAbility(Card card, Effect effect) {
setTriggerPhrase((creatureHaunt ? "When {this} enters or the creature it haunts dies, "
: "When the creature {this} haunts dies, ")
);
setLeavesTheBattlefieldTrigger(true);
}

private HauntAbility(final HauntAbility ability) {
Expand Down

0 comments on commit b1024d2

Please sign in to comment.