mirror of
https://github.com/magefree/mage.git
synced 2026-01-10 21:02:08 -08:00
partial fix for Syrix, Carrier of the Flame
closes #10057, related to #10550
This commit is contained in:
parent
4959ef4d49
commit
038cf01aa8
4 changed files with 165 additions and 85 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue