fix #13451 (Nether Traitor), add test

This commit is contained in:
xenohedron 2025-03-16 00:45:48 -04:00
parent 198fc02e0c
commit fa88a3217d
3 changed files with 137 additions and 61 deletions

View file

@ -1,11 +1,8 @@
package mage.cards.n;
import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.mana.ColoredManaCost;
import mage.abilities.common.DiesCreatureTriggeredAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect;
import mage.abilities.keyword.HasteAbility;
@ -14,11 +11,12 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.ColoredManaSymbol;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import java.util.UUID;
/**
*
@ -26,6 +24,12 @@ import mage.game.events.ZoneChangeEvent;
*/
public final class NetherTraitor extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent();
static {
filter.add(AnotherPredicate.instance);
filter.add(TargetController.YOU.getOwnerPredicate());
}
public NetherTraitor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{B}{B}");
this.subtype.add(SubType.SPIRIT);
@ -39,7 +43,11 @@ public final class NetherTraitor extends CardImpl {
this.addAbility(ShadowAbility.getInstance());
// Whenever another creature is put into your graveyard from the battlefield, you may pay {B}. If you do, return Nether Traitor from your graveyard to the battlefield.
this.addAbility(new NetherTraitorTriggeredAbility());
this.addAbility(new DiesCreatureTriggeredAbility(Zone.GRAVEYARD, new DoIfCostPaid(
new ReturnSourceFromGraveyardToBattlefieldEffect(),
new ManaCostsImpl<>("{B}")
), false, filter, false
).setTriggerPhrase("Whenever another creature is put into your graveyard from the battlefield, "));
}
private NetherTraitor(final NetherTraitor card) {
@ -52,53 +60,3 @@ public final class NetherTraitor extends CardImpl {
}
}
class NetherTraitorTriggeredAbility extends TriggeredAbilityImpl {
NetherTraitorTriggeredAbility(){
super(Zone.GRAVEYARD, new DoIfCostPaid(new ReturnSourceFromGraveyardToBattlefieldEffect(), new ColoredManaCost(ColoredManaSymbol.B)));
setLeavesTheBattlefieldTrigger(true);
}
private NetherTraitorTriggeredAbility(final NetherTraitorTriggeredAbility ability) {
super(ability);
}
@Override
public NetherTraitorTriggeredAbility copy(){
return new NetherTraitorTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
for (Zone z : Zone.values()) {
if (game.checkShortLivingLKI(sourceId, z) && z != Zone.GRAVEYARD) {
return false;
}
}
if (zEvent.isDiesEvent()) {
if (zEvent.getTarget() != null &&
zEvent.getTarget().isOwnedBy(this.getControllerId()) &&
zEvent.getTarget().isCreature(game)&&
!zEvent.getTarget().getId().equals(this.getSourceId())) {
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever another creature is put into your graveyard from the battlefield, you may pay {B}. If you do, return {this} from your graveyard to the battlefield.";
}
@Override
public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) {
return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game);
}
}

View file

@ -0,0 +1,118 @@
package org.mage.test.cards.single.tsp;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author xenohedron
*/
public class NetherTraitorTest extends CardTestPlayerBase {
private static final String nt = "Nether Traitor";
/* Haste; shadow
Whenever another creature is put into your graveyard from the battlefield, you may pay {B}.
If you do, return this card from your graveyard to the battlefield.
*/
private static final String gb = "Goblin Bombardment"; // Sacrifice a creature: 1 damage to any target
private static final String wg = "Warpath Ghoul"; // 3/2
@Test
public void testNetherTraitorBattlefieldGhoulDies() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.BATTLEFIELD, playerA, nt);
addCard(Zone.BATTLEFIELD, playerA, gb);
addCard(Zone.BATTLEFIELD, playerA, wg);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice");
setChoice(playerA, wg); // to sac
addTarget(playerA, playerB); // 1 damage
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
assertLife(playerB, 19);
assertGraveyardCount(playerA, wg, 1);
assertPermanentCount(playerA, nt, 1);
assertTapped("Swamp", false);
}
@Test
public void testNetherTraitorBattlefieldAndDies() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.BATTLEFIELD, playerA, nt);
addCard(Zone.BATTLEFIELD, playerA, gb);
addCard(Zone.BATTLEFIELD, playerA, wg);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice");
setChoice(playerA, nt); // to sac
addTarget(playerA, playerB); // 1 damage
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
assertLife(playerB, 19);
assertGraveyardCount(playerA, nt, 1);
assertPermanentCount(playerA, wg, 1);
assertTapped("Swamp", false);
}
@Test
public void testNetherTraitorGraveyardGhoulDies() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.GRAVEYARD, playerA, nt);
addCard(Zone.BATTLEFIELD, playerA, gb);
addCard(Zone.BATTLEFIELD, playerA, wg);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice");
setChoice(playerA, wg); // to sac
addTarget(playerA, playerB); // 1 damage
setChoice(playerA, true); // yes to pay B and return
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
assertLife(playerB, 19);
assertGraveyardCount(playerA, wg, 1);
assertPermanentCount(playerA, nt, 1);
assertTapped("Swamp", true);
}
/* Ruling:
If Nether Traitor and another creature are put into your graveyard at the same time,
Nether Traitor's ability won't trigger.
This is because it must be in your graveyard before the creature dies in order for its ability
that returns it to the battlefield to trigger.
*/
@Test
public void testNetherTraitorGhoulBothDie() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.BATTLEFIELD, playerA, nt);
addCard(Zone.BATTLEFIELD, playerA, "Tooth and Claw");
// Sacrifice two creatures: Create a 3/1 red Beast creature token named Carnivore.
addCard(Zone.BATTLEFIELD, playerA, wg);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice two");
setChoice(playerA, wg + "^" + nt); // to sac
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20);
assertLife(playerB, 20);
assertPermanentCount(playerA, "Carnivore", 1);
assertGraveyardCount(playerA, wg, 1);
assertGraveyardCount(playerA, nt, 1);
assertTapped("Swamp", false);
}
}

View file

@ -10,7 +10,7 @@ import mage.constants.SubType;
public final class CarnivoreToken extends TokenImpl {
public CarnivoreToken() {
super("Carnivore Token", "3/1 red Beast creature token");
super("Carnivore", "3/1 red Beast creature token");
cardType.add(CardType.CREATURE);
color.setRed(true);
subtype.add(SubType.BEAST);