partial fix for Syrix, Carrier of the Flame

closes #10057, related to #10550
This commit is contained in:
xenohedron 2024-01-21 00:11:16 -05:00
parent 4959ef4d49
commit 038cf01aa8
4 changed files with 165 additions and 85 deletions

View file

@ -1,10 +1,8 @@
package mage.cards.h;
import java.util.UUID;
import mage.ApprovingObject;
import mage.abilities.Ability;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
@ -12,7 +10,6 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.filter.FilterCard;
import mage.filter.FilterSpell;
import mage.filter.common.FilterInstantOrSorcerySpell;
import mage.filter.predicate.mageobject.NamePredicate;
import mage.game.Game;
@ -22,6 +19,8 @@ import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import mage.watchers.common.CastFromHandWatcher;
import java.util.UUID;
/**
*
* @author LevelX2
@ -33,9 +32,7 @@ public final class HarnessTheStorm extends CardImpl {
// Whenever you cast an instant or sorcery spell from your hand, you may cast
// target card with the same name as that spell from your graveyard.
this.addAbility(new HarnessTheStormTriggeredAbility(new HarnessTheStormEffect(),
new FilterInstantOrSorcerySpell("an instant or sorcery spell from your hand"),
false), new CastFromHandWatcher());
this.addAbility(new HarnessTheStormTriggeredAbility(), new CastFromHandWatcher());
}
private HarnessTheStorm(final HarnessTheStorm card) {
@ -51,8 +48,10 @@ public final class HarnessTheStorm extends CardImpl {
class HarnessTheStormTriggeredAbility extends SpellCastControllerTriggeredAbility {
public HarnessTheStormTriggeredAbility(Effect effect, FilterSpell filter, boolean optional) {
super(effect, filter, optional);
private static final FilterInstantOrSorcerySpell filterSpell = new FilterInstantOrSorcerySpell("an instant or sorcery spell from your hand");
HarnessTheStormTriggeredAbility() {
super(new HarnessTheStormEffect(), filterSpell, false);
}
private HarnessTheStormTriggeredAbility(final HarnessTheStormTriggeredAbility ability) {
@ -89,7 +88,7 @@ class HarnessTheStormEffect extends OneShotEffect {
HarnessTheStormEffect() {
super(Outcome.Benefit);
this.staticText = "you may cast target card with the same name as that "
+ "spell from your graveyard. <i>(you still pay its costs.)</i>";
+ "spell from your graveyard. <i>(You still pay its costs.)</i>";
}
private HarnessTheStormEffect(final HarnessTheStormEffect effect) {
@ -104,20 +103,20 @@ class HarnessTheStormEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Card card = controller.getGraveyard().get(getTargetPointer().getFirst(game, source), game);
if (card != null) {
if (controller.chooseUse(outcome.Benefit, "Cast " + card.getIdName() + " from your graveyard?", source, game)) {
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, false),
game, false, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
}
}
return true;
if (controller == null) {
return false;
}
Card card = controller.getGraveyard().get(getTargetPointer().getFirst(game, source), game);
if (card == null) {
return false;
}
if (controller.chooseUse(outcome, "Cast " + card.getIdName() + " from your graveyard?", source, game)) {
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, false),
game, false, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
}
return true;
return false;
}
}

View file

@ -18,29 +18,22 @@ import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetAnyTarget;
import mage.watchers.Watcher;
import mage.watchers.common.CardsLeftGraveyardWatcher;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* @author Alex-Vasile
* @author Alex-Vasile, Merlingilb, xenohedron
*/
public class SyrixCarrierOfTheFlame extends CardImpl {
private static final String description = "Phoenix you control";
private static final FilterPermanent anotherPhoenixFilter = new FilterControlledPermanent("another Phoenix you control");
private static final FilterPermanent phoenixFilter = new FilterControlledPermanent(description);
private static final FilterPermanent anotherPhoenixFilter = new FilterControlledPermanent(SubType.PHOENIX, "another Phoenix you control");
private static final FilterPermanent phoenixFilter = new FilterControlledPermanent(SubType.PHOENIX, "Phoenix you control");
static {
anotherPhoenixFilter.add(AnotherPredicate.instance);
anotherPhoenixFilter.add(SubType.PHOENIX.getPredicate());
phoenixFilter.add(SubType.PHOENIX.getPredicate());
}
public SyrixCarrierOfTheFlame(UUID ownerId, CardSetInfo setInfo) {
@ -57,16 +50,15 @@ public class SyrixCarrierOfTheFlame extends CardImpl {
// At the beginning of each end step, if a creature card left your graveyard this turn,
// target Phoenix you control deals damage equal to its power to any target.
BeginningOfEndStepTriggeredAbility ability = new BeginningOfEndStepTriggeredAbility(
Ability ability = new BeginningOfEndStepTriggeredAbility(
new DamageWithPowerFromOneToAnotherTargetEffect(),
TargetController.EACH_PLAYER,
TargetController.ANY,
SyrixCarrierOfTheFlameCondition.instance,
false
);
ability.addTarget(new TargetPermanent(phoenixFilter));
ability.addTarget(new TargetAnyTarget());
ability.addWatcher(new SyrixCarrierOfTheFlameWatcher());
this.addAbility(ability);
this.addAbility(ability, new CardsLeftGraveyardWatcher());
// Whenever another Phoenix you control dies, you may cast Syrix, Carrier of the Flame from your graveyard.
this.addAbility(new DiesCreatureTriggeredAbility(
@ -88,10 +80,9 @@ public class SyrixCarrierOfTheFlame extends CardImpl {
}
}
/**
* Based on Harness the Storm
*/
// Based on Harness the Storm
class SyrixCarrierOfTheFlameCastEffect extends OneShotEffect {
SyrixCarrierOfTheFlameCastEffect() {
super(Outcome.Benefit);
this.staticText = "you may cast {this} from your graveyard";
@ -133,56 +124,17 @@ class SyrixCarrierOfTheFlameCastEffect extends OneShotEffect {
enum SyrixCarrierOfTheFlameCondition implements Condition {
instance;
private static final String string = "a creature card left your graveyard this turn";
@Override
public boolean apply(Game game, Ability source) {
SyrixCarrierOfTheFlameWatcher watcher = game.getState().getWatcher(SyrixCarrierOfTheFlameWatcher.class);
return watcher != null && watcher.hadACreatureLeave(source.getControllerId());
CardsLeftGraveyardWatcher watcher = game.getState().getWatcher(CardsLeftGraveyardWatcher.class);
return watcher != null && watcher
.getCardsThatLeftGraveyard(source.getControllerId(), game)
.stream()
.anyMatch(card -> card.isCreature(game));
}
@Override
public String toString() {
return string;
}
}
/**
* Creature card left your graveyard this turn
*/
class SyrixCarrierOfTheFlameWatcher extends Watcher {
// Player IDs who had a creature card leave their graveyard
private final Set<UUID> creatureCardLeftPlayerIds = new HashSet<>();
SyrixCarrierOfTheFlameWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (!(event.getType() == GameEvent.EventType.ZONE_CHANGE && event instanceof ZoneChangeEvent)) {
return;
}
ZoneChangeEvent zoneChangeEvent = (ZoneChangeEvent) event;
if (zoneChangeEvent.getFromZone() != Zone.GRAVEYARD) {
return;
}
Card card = zoneChangeEvent.getTarget();
if (card != null && card.isCreature(game)) {
creatureCardLeftPlayerIds.add(card.getOwnerId());
}
}
public boolean hadACreatureLeave(UUID playerId) {
return creatureCardLeftPlayerIds.contains(playerId);
}
@Override
public void reset() {
super.reset();
creatureCardLeftPlayerIds.clear();
return "a creature card left your graveyard this turn";
}
}

View file

@ -0,0 +1,73 @@
package org.mage.test.cards.single.ncc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
public class SyrixCarrierOfTheFlameTest extends CardTestPlayerBase {
private static final String syrix = "Syrix, Carrier of the Flame"; // 3/3
// At the beginning of each end step, if a creature card left your graveyard this turn,
// target Phoenix you control deals damage equal to its power to any target.
// Whenever another Phoenix you control dies, you may cast Syrix, Carrier of the Flame from your graveyard.
private static final String phoenix = "Firewing Phoenix"; // 4/2
private static final String shock = "Shock";
private static final String historian = "Illustrious Historian";
// {5}, Exile Illustrious Historian from your graveyard: Create a tapped 3/2 red and white Spirit creature token.
@Test
public void testDamageTrigger() {
addCard(Zone.BATTLEFIELD, playerA, syrix);
addCard(Zone.BATTLEFIELD, playerA, phoenix);
addCard(Zone.GRAVEYARD, playerA, historian);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{5}, Exile ");
checkExileCount("exiled", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, historian, 1);
checkPT("token", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Spirit Token", 3, 2);
checkLife("before trigger", 2, PhaseStep.POSTCOMBAT_MAIN, playerA, 20);
checkLife("before trigger", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, 20);
addTarget(playerA, phoenix); // target Phoenix
addTarget(playerA, playerB); // deals damage
setStrictChooseMode(true);
setStopAt(3, PhaseStep.UPKEEP);
execute();
assertLife(playerA, 20);
assertLife(playerB, 16);
assertPowerToughness(playerA, syrix, 3, 3);
assertPowerToughness(playerA, phoenix, 4, 2);
}
@Ignore("Usable zone issue, see #10550")
@Test
public void testCast() {
addCard(Zone.GRAVEYARD, playerA, syrix);
addCard(Zone.BATTLEFIELD, playerA, phoenix);
addCard(Zone.HAND, playerA, shock);
addCard(Zone.BATTLEFIELD, playerA, "Badlands", 6);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, shock, phoenix);
// phoenix dies, syrix ability triggers
setChoice(playerA, true); // yes to cast
setStrictChooseMode(true);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 20);
assertLife(playerB, 20);
assertPowerToughness(playerA, syrix, 3, 3);
assertGraveyardCount(playerA, phoenix, 1);
assertGraveyardCount(playerA, shock, 1);
assertTappedCount("Badlands", true, 6);
}
}

View file

@ -0,0 +1,56 @@
package mage.watchers.common;
import mage.cards.Card;
import mage.constants.WatcherScope;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.watchers.Watcher;
import java.util.*;
import java.util.stream.Collectors;
/**
* Keeps track of the UUIDs of the cards that left graveyard this turn.
*/
public class CardsLeftGraveyardWatcher extends Watcher {
// Player id -> card ids
private final Map<UUID, Set<UUID>> cardsLeftGraveyardThisTurn = new HashMap<>();
public CardsLeftGraveyardWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() != GameEvent.EventType.ZONE_CHANGE
|| ((ZoneChangeEvent) event).getFromZone() != Zone.GRAVEYARD) {
return;
}
UUID playerId = event.getPlayerId();
if (playerId == null || game.getCard(event.getTargetId()) == null) {
return;
}
cardsLeftGraveyardThisTurn.computeIfAbsent(playerId, k -> new HashSet<>())
.add(event.getTargetId());
}
/**
* The cards that left a specific player's graveyard this turn.
*/
public Set<Card> getCardsThatLeftGraveyard(UUID playerId, Game game) {
return cardsLeftGraveyardThisTurn.getOrDefault(playerId, Collections.emptySet())
.stream()
.map(game::getCard)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
@Override
public void reset() {
super.reset();
cardsLeftGraveyardThisTurn.clear();
}
}