diff --git a/Mage.Sets/src/mage/cards/h/HarnessTheStorm.java b/Mage.Sets/src/mage/cards/h/HarnessTheStorm.java
index e2dfac76c1e..2f5fe12510c 100644
--- a/Mage.Sets/src/mage/cards/h/HarnessTheStorm.java
+++ b/Mage.Sets/src/mage/cards/h/HarnessTheStorm.java
@@ -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. (you still pay its costs.)";
+ + "spell from your graveyard. (You still pay its costs.)";
}
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;
}
}
diff --git a/Mage.Sets/src/mage/cards/s/SyrixCarrierOfTheFlame.java b/Mage.Sets/src/mage/cards/s/SyrixCarrierOfTheFlame.java
index a3879363f5e..df82d3450e2 100644
--- a/Mage.Sets/src/mage/cards/s/SyrixCarrierOfTheFlame.java
+++ b/Mage.Sets/src/mage/cards/s/SyrixCarrierOfTheFlame.java
@@ -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 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";
}
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SyrixCarrierOfTheFlameTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SyrixCarrierOfTheFlameTest.java
new file mode 100644
index 00000000000..5408e56ea6e
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ncc/SyrixCarrierOfTheFlameTest.java
@@ -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);
+
+ }
+
+}
diff --git a/Mage/src/main/java/mage/watchers/common/CardsLeftGraveyardWatcher.java b/Mage/src/main/java/mage/watchers/common/CardsLeftGraveyardWatcher.java
new file mode 100644
index 00000000000..68f3efe3add
--- /dev/null
+++ b/Mage/src/main/java/mage/watchers/common/CardsLeftGraveyardWatcher.java
@@ -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> 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 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();
+ }
+}