From c261facd61b6b58b88c201bdfe804c449d4e9aae Mon Sep 17 00:00:00 2001 From: theelk801 Date: Tue, 8 Apr 2025 10:00:28 -0400 Subject: [PATCH] create base class for flashback-like abilities --- .../keyword/CastFromGraveyardAbility.java | 197 ++++++++++++++++++ .../abilities/keyword/FlashbackAbility.java | 180 +--------------- .../abilities/keyword/HarmonizeAbility.java | 148 +------------ 3 files changed, 202 insertions(+), 323 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/keyword/CastFromGraveyardAbility.java diff --git a/Mage/src/main/java/mage/abilities/keyword/CastFromGraveyardAbility.java b/Mage/src/main/java/mage/abilities/keyword/CastFromGraveyardAbility.java new file mode 100644 index 00000000000..73de96cf5e7 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/keyword/CastFromGraveyardAbility.java @@ -0,0 +1,197 @@ +package mage.abilities.keyword; + +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.Card; +import mage.cards.ModalDoubleFacedCard; +import mage.cards.SplitCard; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * Base class for Flashback and Harmonize and any future ability which works similarly + * + * @author TheElk801 + */ +public abstract class CastFromGraveyardAbility extends SpellAbility { + + protected String abilityName; + private SpellAbility spellAbilityToResolve; + + protected CastFromGraveyardAbility(Card card, Cost cost, SpellAbilityCastMode spellAbilityCastMode) { + super(null, "", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE, spellAbilityCastMode); + this.setAdditionalCostsRuleVisible(false); + this.name = spellAbilityCastMode + " " + cost.getText(); + this.addCost(cost); + this.timing = card.isSorcery() ? TimingRule.SORCERY : TimingRule.INSTANT; + } + + protected CastFromGraveyardAbility(final CastFromGraveyardAbility ability) { + super(ability); + this.abilityName = ability.abilityName; + this.spellAbilityToResolve = ability.spellAbilityToResolve; + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + // flashback ability dynamicly added to all card's parts (split cards) + if (!super.canActivate(playerId, game).canActivate()) { + return ActivationStatus.getFalse(); + } + Card card = game.getCard(getSourceId()); + if (card == null) { + return ActivationStatus.getFalse(); + } + // Card must be in the graveyard zone + if (game.getState().getZone(card.getId()) != Zone.GRAVEYARD) { + return ActivationStatus.getFalse(); + } + // Cards with no Mana Costs cant't be flashbacked (e.g. Ancestral Vision) + if (card.getManaCost().isEmpty()) { + return ActivationStatus.getFalse(); + } + // CastFromGraveyard can never cast a split card by Fuse, because Fuse only works from hand + // https://tappedout.net/mtg-questions/snapcaster-mage-and-flashback-on-a-fuse-card-one-or-both-halves-legal-targets/ + if (card instanceof SplitCard) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } else if (card instanceof ModalDoubleFacedCard) { + if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } + return card.getSpellAbility().canActivate(playerId, game); + } + + @Override + public SpellAbility getSpellAbilityToResolve(Game game) { + Card card = game.getCard(getSourceId()); + if (card == null || spellAbilityToResolve != null) { + return spellAbilityToResolve; + } + SpellAbility spellAbilityCopy; + if (card instanceof SplitCard) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); + } else { + spellAbilityCopy = null; + } + } else if (card instanceof ModalDoubleFacedCard) { + if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy(); + } else { + spellAbilityCopy = null; + } + } else { + spellAbilityCopy = card.getSpellAbility().copy(); + } + if (spellAbilityCopy == null) { + return null; + } + spellAbilityCopy.setId(this.getId()); + spellAbilityCopy.clearManaCosts(); + spellAbilityCopy.clearManaCostsToPay(); + spellAbilityCopy.addCost(this.getCosts().copy()); + spellAbilityCopy.addCost(this.getManaCosts().copy()); + spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); + spellAbilityToResolve = spellAbilityCopy; + ContinuousEffect effect = new CastFromGraveyardReplacementEffect(); + effect.setTargetPointer(new FixedTarget(getSourceId(), game.getState().getZoneChangeCounter(getSourceId()))); + game.addEffect(effect, this); + return spellAbilityToResolve; + } + + @Override + public Costs getCosts() { + if (spellAbilityToResolve == null) { + return super.getCosts(); + } + return spellAbilityToResolve.getCosts(); + } + + @Override + public String getRule(boolean all) { + return this.getRule(); + } + + /** + * Used for split card in PlayerImpl method: + * getOtherUseableActivatedAbilities + * + * @param abilityName + */ + public CastFromGraveyardAbility setAbilityName(String abilityName) { + this.abilityName = abilityName; + return this; + } +} + +class CastFromGraveyardReplacementEffect extends ReplacementEffectImpl { + + public CastFromGraveyardReplacementEffect() { + super(Duration.OneUse, Outcome.Exile); + } + + protected CastFromGraveyardReplacementEffect(final CastFromGraveyardReplacementEffect effect) { + super(effect); + } + + @Override + public CastFromGraveyardReplacementEffect copy() { + return new CastFromGraveyardReplacementEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Card card = game.getCard(event.getTargetId()); + if (card == null) { + return false; + } + discard(); + return controller.moveCards( + card, Zone.EXILED, source, game, false, + false, false, event.getAppliedEffects() + ); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + UUID cardId = CardUtil.getMainCardId(game, source.getSourceId()); // for split cards + if (!cardId.equals(event.getTargetId()) + || ((ZoneChangeEvent) event).getFromZone() != Zone.STACK + || ((ZoneChangeEvent) event).getToZone() == Zone.EXILED) { + return false; + } + int zcc = game.getState().getZoneChangeCounter(cardId); + return ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc; + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java index e88cf333684..4b7dae1d709 100644 --- a/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/FlashbackAbility.java @@ -1,23 +1,8 @@ package mage.abilities.keyword; -import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.costs.Cost; -import mage.abilities.costs.Costs; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.ReplacementEffectImpl; import mage.cards.Card; -import mage.cards.ModalDoubleFacedCard; -import mage.cards.SplitCard; -import mage.constants.*; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.players.Player; -import mage.target.targetpointer.FixedTarget; -import mage.util.CardUtil; - -import java.util.UUID; +import mage.constants.SpellAbilityCastMode; /** * 702.32. Flashback @@ -33,106 +18,14 @@ import java.util.UUID; * * @author nantuko */ -public class FlashbackAbility extends SpellAbility { - - private String abilityName; - private SpellAbility spellAbilityToResolve; +public class FlashbackAbility extends CastFromGraveyardAbility { public FlashbackAbility(Card card, Cost cost) { - super(null, "", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.FLASHBACK); - this.setAdditionalCostsRuleVisible(false); - this.name = "Flashback " + cost.getText(); - this.addCost(cost); - this.timing = card.isSorcery() ? TimingRule.SORCERY : TimingRule.INSTANT; + super(card, cost, SpellAbilityCastMode.FLASHBACK); } protected FlashbackAbility(final FlashbackAbility ability) { super(ability); - this.spellAbilityType = ability.spellAbilityType; - this.abilityName = ability.abilityName; - this.spellAbilityToResolve = ability.spellAbilityToResolve; - } - - @Override - public ActivationStatus canActivate(UUID playerId, Game game) { - // flashback ability dynamicly added to all card's parts (split cards) - if (super.canActivate(playerId, game).canActivate()) { - Card card = game.getCard(getSourceId()); - if (card != null) { - // Card must be in the graveyard zone - if (game.getState().getZone(card.getId()) != Zone.GRAVEYARD) { - return ActivationStatus.getFalse(); - } - // Cards with no Mana Costs cant't be flashbacked (e.g. Ancestral Vision) - if (card.getManaCost().isEmpty()) { - return ActivationStatus.getFalse(); - } - // Flashback can never cast a split card by Fuse, because Fuse only works from hand - // https://tappedout.net/mtg-questions/snapcaster-mage-and-flashback-on-a-fuse-card-one-or-both-halves-legal-targets/ - if (card instanceof SplitCard) { - if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { - return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); - } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { - return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); - } - } else if (card instanceof ModalDoubleFacedCard) { - if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { - return ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); - } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { - return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); - } - } - return card.getSpellAbility().canActivate(playerId, game); - } - } - return ActivationStatus.getFalse(); - } - - @Override - public SpellAbility getSpellAbilityToResolve(Game game) { - Card card = game.getCard(getSourceId()); - if (card != null) { - if (spellAbilityToResolve == null) { - SpellAbility spellAbilityCopy = null; - if (card instanceof SplitCard) { - if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); - } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); - } - } else if (card instanceof ModalDoubleFacedCard) { - if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().copy(); - } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy(); - } - } else { - spellAbilityCopy = card.getSpellAbility().copy(); - } - if (spellAbilityCopy == null) { - return null; - } - spellAbilityCopy.setId(this.getId()); - spellAbilityCopy.clearManaCosts(); - spellAbilityCopy.clearManaCostsToPay(); - spellAbilityCopy.addCost(this.getCosts().copy()); - spellAbilityCopy.addCost(this.getManaCosts().copy()); - spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); - spellAbilityToResolve = spellAbilityCopy; - ContinuousEffect effect = new FlashbackReplacementEffect(); - effect.setTargetPointer(new FixedTarget(getSourceId(), game.getState().getZoneChangeCounter(getSourceId()))); - game.addEffect(effect, this); - } - } - return spellAbilityToResolve; - } - - @Override - public Costs getCosts() { - if (spellAbilityToResolve == null) { - return super.getCosts(); - } - return spellAbilityToResolve.getCosts(); } @Override @@ -140,11 +33,6 @@ public class FlashbackAbility extends SpellAbility { return new FlashbackAbility(this); } - @Override - public String getRule(boolean all) { - return this.getRule(); - } - @Override public String getRule() { StringBuilder sbRule = new StringBuilder("Flashback"); @@ -170,66 +58,4 @@ public class FlashbackAbility extends SpellAbility { sbRule.append(" (You may cast this card from your graveyard for its flashback cost. Then exile it.)"); return sbRule.toString(); } - - /** - * Used for split card in PlayerImpl method: - * getOtherUseableActivatedAbilities - * - * @param abilityName - */ - public FlashbackAbility setAbilityName(String abilityName) { - this.abilityName = abilityName; - return this; - } - -} - -class FlashbackReplacementEffect extends ReplacementEffectImpl { - - public FlashbackReplacementEffect() { - super(Duration.OneUse, Outcome.Exile); - staticText = "(If the flashback cost was paid, exile this card instead of putting it anywhere else any time it would leave the stack)"; - } - - protected FlashbackReplacementEffect(final FlashbackReplacementEffect effect) { - super(effect); - } - - @Override - public FlashbackReplacementEffect copy() { - return new FlashbackReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Card card = game.getCard(event.getTargetId()); - if (card != null) { - discard(); - return controller.moveCards( - card, Zone.EXILED, source, game, false, false, false, event.getAppliedEffects()); - } - } - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - UUID cardId = CardUtil.getMainCardId(game, source.getSourceId()); // for split cards - if (cardId.equals(event.getTargetId()) - && ((ZoneChangeEvent) event).getFromZone() == Zone.STACK - && ((ZoneChangeEvent) event).getToZone() != Zone.EXILED) { - - int zcc = game.getState().getZoneChangeCounter(cardId); - return ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == zcc; - - } - return false; - } } diff --git a/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java b/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java index cb8d082be43..d18de35de8c 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HarmonizeAbility.java @@ -6,29 +6,21 @@ import mage.abilities.Ability; import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; -import mage.abilities.costs.Costs; import mage.abilities.costs.VariableCostImpl; import mage.abilities.costs.VariableCostType; import mage.abilities.costs.common.TapTargetCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.cards.Card; -import mage.cards.ModalDoubleFacedCard; -import mage.cards.SplitCard; import mage.constants.*; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetControlledPermanent; -import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import java.util.Objects; @@ -37,19 +29,15 @@ import java.util.UUID; /** * @author TheElk801 */ -public class HarmonizeAbility extends SpellAbility { +public class HarmonizeAbility extends CastFromGraveyardAbility { private String abilityName; private SpellAbility spellAbilityToResolve; public HarmonizeAbility(Card card, String manaString) { - super(null, "", Zone.GRAVEYARD, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.HARMONIZE); - this.setAdditionalCostsRuleVisible(false); - this.name = "Harmonize " + manaString; - this.addCost(new ManaCostsImpl<>(manaString)); + super(card, new ManaCostsImpl<>(manaString), SpellAbilityCastMode.HARMONIZE); this.addCost(new HarmonizeCost()); this.addSubAbility(new SimpleStaticAbility(Zone.ALL, new HarmonizeCostReductionEffect()).setRuleVisible(false)); - this.timing = card.isSorcery() ? TimingRule.SORCERY : TimingRule.INSTANT; } private HarmonizeAbility(final HarmonizeAbility ability) { @@ -61,94 +49,6 @@ public class HarmonizeAbility extends SpellAbility { return new HarmonizeAbility(this); } - @Override - public ActivationStatus canActivate(UUID playerId, Game game) { - // harmonize ability dynamicly added to all card's parts (split cards) - if (!super.canActivate(playerId, game).canActivate()) { - return ActivationStatus.getFalse(); - } - Card card = game.getCard(getSourceId()); - if (card == null) { - return ActivationStatus.getFalse(); - } - // Card must be in the graveyard zone - if (game.getState().getZone(card.getId()) != Zone.GRAVEYARD) { - return ActivationStatus.getFalse(); - } - // Cards with no Mana Costs cant't be harmonized (e.g. Ancestral Vision) - if (card.getManaCost().isEmpty()) { - return ActivationStatus.getFalse(); - } - // Harmonize can never cast a split card by Fuse, because Fuse only works from hand - if (card instanceof SplitCard) { - if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { - return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); - } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { - return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); - } - } else if (card instanceof ModalDoubleFacedCard) { - if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { - return ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); - } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { - return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); - } - } - return card.getSpellAbility().canActivate(playerId, game); - } - - @Override - public SpellAbility getSpellAbilityToResolve(Game game) { - Card card = game.getCard(getSourceId()); - if (card == null) { - return spellAbilityToResolve; - } - if (spellAbilityToResolve != null) { - return spellAbilityToResolve; - } - SpellAbility spellAbilityCopy; - if (card instanceof SplitCard) { - if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); - } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); - } else { - spellAbilityCopy = null; - } - } else if (card instanceof ModalDoubleFacedCard) { - if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().copy(); - } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy(); - } else { - spellAbilityCopy = null; - } - } else { - spellAbilityCopy = card.getSpellAbility().copy(); - } - if (spellAbilityCopy == null) { - return null; - } - spellAbilityCopy.setId(this.getId()); - spellAbilityCopy.clearManaCosts(); - spellAbilityCopy.clearManaCostsToPay(); - spellAbilityCopy.addCost(this.getCosts().copy()); - spellAbilityCopy.addCost(this.getManaCosts().copy()); - spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); - spellAbilityToResolve = spellAbilityCopy; - ContinuousEffect effect = new HarmonizeReplacementEffect(); - effect.setTargetPointer(new FixedTarget(getSourceId(), game.getState().getZoneChangeCounter(getSourceId()))); - game.addEffect(effect, this); - return spellAbilityToResolve; - } - - @Override - public Costs getCosts() { - if (spellAbilityToResolve == null) { - return super.getCosts(); - } - return spellAbilityToResolve.getCosts(); - } - @Override public String getRule() { return name + " (You may cast this card from your graveyard for its harmonize cost. " + @@ -157,50 +57,6 @@ public class HarmonizeAbility extends SpellAbility { } } -class HarmonizeReplacementEffect extends ReplacementEffectImpl { - - public HarmonizeReplacementEffect() { - super(Duration.OneUse, Outcome.Exile); - } - - protected HarmonizeReplacementEffect(final HarmonizeReplacementEffect effect) { - super(effect); - } - - @Override - public HarmonizeReplacementEffect copy() { - return new HarmonizeReplacementEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player controller = game.getPlayer(source.getControllerId()); - Card card = game.getCard(event.getTargetId()); - if (controller == null || card == null) { - return false; - } - discard(); - return controller.moveCards( - card, Zone.EXILED, source, game, false, - false, false, event.getAppliedEffects() - ); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - UUID cardId = CardUtil.getMainCardId(game, source.getSourceId()); // for split cards - return event.getTargetId().equals(cardId) - && ((ZoneChangeEvent) event).getFromZone().match(Zone.STACK) - && !((ZoneChangeEvent) event).getToZone().match(Zone.EXILED) - && ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(cardId); - } -} - class HarmonizeCostReductionEffect extends CostModificationEffectImpl { HarmonizeCostReductionEffect() {