MagicStack: ThisTurnActivated

This commit is contained in:
Hans Mackowiak
2024-04-13 19:11:08 +02:00
parent abba1c7230
commit d6422688b8
11 changed files with 53 additions and 69 deletions

View File

@@ -2269,14 +2269,6 @@ public class AbilityUtils {
return doXMath(player.getOpponentsGreatestLifeTotal(), expr, c, ctb); return doXMath(player.getOpponentsGreatestLifeTotal(), expr, c, ctb);
} }
if (sq[0].equals("YouCycledThisTurn")) {
return doXMath(player.getCycledThisTurn(), expr, c, ctb);
}
if (sq[0].equals("YouEquippedThisTurn")) {
return doXMath(player.getEquippedThisTurn(), expr, c, ctb);
}
if (sq[0].equals("YouDrewThisTurn")) { if (sq[0].equals("YouDrewThisTurn")) {
return doXMath(player.getNumDrawnThisTurn(), expr, c, ctb); return doXMath(player.getNumDrawnThisTurn(), expr, c, ctb);
} }
@@ -2727,6 +2719,12 @@ public class AbilityUtils {
someCards = CardUtil.getLastTurnCast(validFilter, c, ctb, player); someCards = CardUtil.getLastTurnCast(validFilter, c, ctb, player);
} }
} }
if (sq[0].startsWith("ThisTurnActivated")) {
final String[] workingCopy = paidparts[0].split("_");
final String validFilter = workingCopy[1];
// use objectXCount ?
return CardUtil.getThisTurnActivated(validFilter, c, ctb, player).size();
}
// Count$ThisTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid> // Count$ThisTurnEntered <ZoneDestination> [from <ZoneOrigin>] <Valid>
if (sq[0].startsWith("ThisTurnEntered") || sq[0].startsWith("LastTurnEntered")) { if (sq[0].startsWith("ThisTurnEntered") || sq[0].startsWith("LastTurnEntered")) {

View File

@@ -3679,7 +3679,6 @@ public class CardFactoryUtil {
sb.append("| SpellDescription$ (").append(inst.getReminderText()).append(")"); sb.append("| SpellDescription$ (").append(inst.getReminderText()).append(")");
SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card); SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card);
sa.setAlternativeCost(AlternativeCost.Cycling);
sa.setIntrinsic(intrinsic); sa.setIntrinsic(intrinsic);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
} else if (keyword.startsWith("TypeCycling")) { } else if (keyword.startsWith("TypeCycling")) {
@@ -3704,7 +3703,6 @@ public class CardFactoryUtil {
sb.append(" | SpellDescription$ (").append(inst.getReminderText()).append(")"); sb.append(" | SpellDescription$ (").append(inst.getReminderText()).append(")");
SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card); SpellAbility sa = AbilityFactory.getAbility(sb.toString(), card);
sa.setAlternativeCost(AlternativeCost.Cycling);
sa.setIntrinsic(intrinsic); sa.setIntrinsic(intrinsic);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
} }

View File

@@ -21,6 +21,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -36,6 +37,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.TextUtil; import forge.util.TextUtil;
@@ -138,6 +140,10 @@ public final class CardUtil {
return CardLists.getValidCardsAsList(src.getGame().getStack().getSpellsCastLastTurn(), valid, controller, src, ctb); return CardLists.getValidCardsAsList(src.getGame().getStack().getSpellsCastLastTurn(), valid, controller, src, ctb);
} }
public static List<SpellAbility> getThisTurnActivated(final String valid, final Card src, final CardTraitBase ctb, final Player controller) {
return Lists.newArrayList(Iterables.filter(src.getGame().getStack().getAbilityActivatedThisTurn(), SpellAbilityPredicates.isValid(valid.split(","), controller, src, ctb)));
}
public static CardCollection getRadiance(final SpellAbility sa) { public static CardCollection getRadiance(final SpellAbility sa) {
SpellAbility targetSA = sa.getSATargetingCard(); SpellAbility targetSA = sa.getSATargetingCard();
if (targetSA == null || !targetSA.usesTargeting() || !targetSA.hasParam("Radiance")) { if (targetSA == null || !targetSA.usesTargeting() || !targetSA.hasParam("Radiance")) {

View File

@@ -92,8 +92,6 @@ public class Player extends GameEntity implements Comparable<Player> {
private int landsPlayedLastTurn; private int landsPlayedLastTurn;
private int investigatedThisTurn; private int investigatedThisTurn;
private int surveilThisTurn; private int surveilThisTurn;
private int cycledThisTurn;
private int equippedThisTurn;
private int lifeLostThisTurn; private int lifeLostThisTurn;
private int lifeLostLastTurn; private int lifeLostLastTurn;
private int lifeGainedThisTurn; private int lifeGainedThisTurn;
@@ -2482,8 +2480,6 @@ public class Player extends GameEntity implements Comparable<Player> {
resetLandsPlayedThisTurn(); resetLandsPlayedThisTurn();
resetInvestigatedThisTurn(); resetInvestigatedThisTurn();
resetSurveilThisTurn(); resetSurveilThisTurn();
resetCycledThisTurn();
resetEquippedThisTurn();
resetDiscardedThisTurn(); resetDiscardedThisTurn();
resetSacrificedThisTurn(); resetSacrificedThisTurn();
resetVenturedThisTurn(); resetVenturedThisTurn();
@@ -2969,23 +2965,23 @@ public class Player extends GameEntity implements Comparable<Player> {
public boolean allCardsUniqueManaSymbols() { public boolean allCardsUniqueManaSymbols() {
for (final Card c : getCardsIn(ZoneType.Library)) { for (final Card c : getCardsIn(ZoneType.Library)) {
Set<CardStateName> cardStateNames = c.isSplitCard() ? EnumSet.of(CardStateName.LeftSplit, CardStateName.RightSplit) : EnumSet.of(CardStateName.Original); Set<CardStateName> cardStateNames = c.isSplitCard() ? EnumSet.of(CardStateName.LeftSplit, CardStateName.RightSplit) : EnumSet.of(CardStateName.Original);
Set<ManaCostShard> coloredManaSymbols = new HashSet<>(); Set<ManaCostShard> coloredManaSymbols = new HashSet<>();
Set<Integer> genericManaSymbols = new HashSet<>(); Set<Integer> genericManaSymbols = new HashSet<>();
for (final CardStateName cardStateName : cardStateNames) { for (final CardStateName cardStateName : cardStateNames) {
final ManaCost manaCost = c.getState(cardStateName).getManaCost(); final ManaCost manaCost = c.getState(cardStateName).getManaCost();
for (final ManaCostShard manaSymbol : manaCost) { for (final ManaCostShard manaSymbol : manaCost) {
if (!coloredManaSymbols.add(manaSymbol)) { if (!coloredManaSymbols.add(manaSymbol)) {
return false; return false;
} }
} }
int generic = manaCost.getGenericCost(); int generic = manaCost.getGenericCost();
if (generic > 0 || manaCost.getCMC() == 0) { if (generic > 0 || manaCost.getCMC() == 0) {
if (!genericManaSymbols.add(Integer.valueOf(generic))) { if (!genericManaSymbols.add(Integer.valueOf(generic))) {
return false; return false;
} }
} }
} }
} }
return true; return true;
} }
@@ -3028,9 +3024,9 @@ public class Player extends GameEntity implements Comparable<Player> {
legalCompanions.add(c); legalCompanions.add(c);
} }
} else if (specialRules.equals("UniqueManaSymbols")) { } else if (specialRules.equals("UniqueManaSymbols")) {
if (this.allCardsUniqueManaSymbols()) { if (this.allCardsUniqueManaSymbols()) {
legalCompanions.add(c); legalCompanions.add(c);
} }
} else if (specialRules.equals("DeckSizePlus20")) { } else if (specialRules.equals("DeckSizePlus20")) {
// +20 deck size to min deck size // +20 deck size to min deck size
if (deckSize >= minSize + 20) { if (deckSize >= minSize + 20) {
@@ -3114,7 +3110,7 @@ public class Player extends GameEntity implements Comparable<Player> {
else if (game.getRules().hasAppliedVariant(GameType.Oathbreaker)) { else if (game.getRules().hasAppliedVariant(GameType.Oathbreaker)) {
moved += " | Destination$ Graveyard,Exile,Hand,Library | Description$ If a commander would be exiled or put into hand, graveyard, or library from anywhere, that player may put it into the command zone instead."; moved += " | Destination$ Graveyard,Exile,Hand,Library | Description$ If a commander would be exiled or put into hand, graveyard, or library from anywhere, that player may put it into the command zone instead.";
} else { } else {
// rule 903.9b // rule 903.9b
moved += " | Destination$ Hand,Library | Description$ If a commander would be put into its owner's hand or library from anywhere, its owner may put it into the command zone instead."; moved += " | Destination$ Hand,Library | Description$ If a commander would be put into its owner's hand or library from anywhere, its owner may put it into the command zone instead.";
} }
ReplacementEffect re = ReplacementHandler.parseReplacement(moved, eff, true); ReplacementEffect re = ReplacementHandler.parseReplacement(moved, eff, true);
@@ -3154,7 +3150,7 @@ public class Player extends GameEntity implements Comparable<Player> {
SpellAbility planarRoll = AbilityFactory.getAbility(specialA, eff); SpellAbility planarRoll = AbilityFactory.getAbility(specialA, eff);
planarRoll.setSVar("X", "Count$PlanarDiceSpecialActionThisTurn"); planarRoll.setSVar("X", "Count$PlanarDiceSpecialActionThisTurn");
eff.addSpellAbility(planarRoll); eff.addSpellAbility(planarRoll);
eff.updateStateForView(); eff.updateStateForView();
com.add(eff); com.add(eff);
this.updateZoneForView(com); this.updateZoneForView(com);
@@ -3691,33 +3687,13 @@ public class Player extends GameEntity implements Comparable<Player> {
} }
public void addCycled(SpellAbility sp) { public void addCycled(SpellAbility sp) {
cycledThisTurn++;
Map<AbilityKey, Object> cycleParams = AbilityKey.mapFromCard(CardCopyService.getLKICopy(game.getCardState(sp.getHostCard()))); Map<AbilityKey, Object> cycleParams = AbilityKey.mapFromCard(CardCopyService.getLKICopy(game.getCardState(sp.getHostCard())));
cycleParams.put(AbilityKey.Cause, sp); cycleParams.put(AbilityKey.Cause, sp);
cycleParams.put(AbilityKey.Player, this); cycleParams.put(AbilityKey.Player, this);
cycleParams.put(AbilityKey.FirstTime, cycledThisTurn == 1); cycleParams.put(AbilityKey.FirstTime, CardUtil.getThisTurnActivated("Activated.Cycling+YouCtrl", sp.getHostCard(), sp, this).size() == 1);
game.getTriggerHandler().runTrigger(TriggerType.Cycled, cycleParams, false); game.getTriggerHandler().runTrigger(TriggerType.Cycled, cycleParams, false);
} }
public int getCycledThisTurn() {
return cycledThisTurn;
}
public void resetCycledThisTurn() {
cycledThisTurn = 0;
}
public void addEquipped() { equippedThisTurn++; }
public int getEquippedThisTurn() {
return equippedThisTurn;
}
public void resetEquippedThisTurn() {
equippedThisTurn = 0;
}
public boolean hasUrzaLands() { public boolean hasUrzaLands() {
final CardCollectionView landsControlled = getCardsIn(ZoneType.Battlefield); final CardCollectionView landsControlled = getCardsIn(ZoneType.Battlefield);
return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine"))) return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine")))

View File

@@ -4,7 +4,6 @@ public enum AlternativeCost {
Awaken, Awaken,
Bestow, Bestow,
Blitz, Blitz,
Cycling, // ActivatedAbility
Dash, Dash,
Disturb, Disturb,
Emerge, Emerge,

View File

@@ -560,7 +560,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
public boolean isCycling() { public boolean isCycling() {
return isAlternativeCost(AlternativeCost.Cycling); return isKeyword(Keyword.CYCLING) || isKeyword(Keyword.TYPECYCLING);
} }
public boolean isBackup() { public boolean isBackup() {

View File

@@ -85,6 +85,8 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
private final List<Card> thisTurnCast = Lists.newArrayList(); private final List<Card> thisTurnCast = Lists.newArrayList();
private List<Card> lastTurnCast = Lists.newArrayList(); private List<Card> lastTurnCast = Lists.newArrayList();
private final List<SpellAbility> abilitiesActivatedThisTurn = Lists.newArrayList();
private Card curResolvingCard = null; private Card curResolvingCard = null;
private final Map<String, List<GameCommand>> commandList = Maps.newHashMap(); private final Map<String, List<GameCommand>> commandList = Maps.newHashMap();
@@ -263,7 +265,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
if (sp.isManaAbility()) { // Mana Abilities go straight through if (sp.isManaAbility()) { // Mana Abilities go straight through
if (!sp.isCopied() && !sp.isTrigger()) { if (!sp.isCopied() && !sp.isTrigger()) {
// Copied abilities aren't activated, so they shouldn't change these values // Copied abilities aren't activated, so they shouldn't change these values
source.addAbilityActivated(sp); addAbilityActivatedThisTurn(sp, source);
} }
Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(source.getController()); Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(source.getController());
runParams.put(AbilityKey.Cost, sp.getPayCosts()); runParams.put(AbilityKey.Cost, sp.getPayCosts());
@@ -336,7 +338,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
} }
if (sp.isAbility() && !sp.isCopied() && !sp.isTrigger()) { if (sp.isAbility() && !sp.isCopied() && !sp.isTrigger()) {
source.addAbilityActivated(sp); addAbilityActivatedThisTurn(sp, source);
} }
if (sp instanceof AbilityStatic || (sp.isTrigger() && sp.getTrigger().getOverridingAbility() instanceof AbilityStatic)) { if (sp instanceof AbilityStatic || (sp.isTrigger() && sp.getTrigger().getOverridingAbility() instanceof AbilityStatic)) {
@@ -390,11 +392,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
activator.addCycled(sp); activator.addCycled(sp);
} }
// Log number of Equips
if (sp.isEquip()) {
activator.addEquipped();
}
if (sp.hasParam("Crew")) { if (sp.hasParam("Crew")) {
// Trigger crews! // Trigger crews!
runParams.put(AbilityKey.Vehicle, sp.getHostCard()); runParams.put(AbilityKey.Vehicle, sp.getHostCard());
@@ -894,6 +891,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
public final void onNextTurn() { public final void onNextTurn() {
game.getStackZone().resetCardsAddedThisTurn(); game.getStackZone().resetCardsAddedThisTurn();
this.abilitiesActivatedThisTurn.clear();
if (thisTurnCast.isEmpty()) { if (thisTurnCast.isEmpty()) {
lastTurnCast = Lists.newArrayList(); lastTurnCast = Lists.newArrayList();
return; return;
@@ -907,6 +905,15 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
return lastTurnCast; return lastTurnCast;
} }
public void addAbilityActivatedThisTurn(SpellAbility sa, final Card source) {
source.addAbilityActivated(sa);
abilitiesActivatedThisTurn.add(sa.copy(CardCopyService.getLKICopy(source), true));
}
public List<SpellAbility> getAbilityActivatedThisTurn() {
return abilitiesActivatedThisTurn;
}
public final void addCastCommand(final String valid, final GameCommand c) { public final void addCastCommand(final String valid, final GameCommand c) {
if (commandList.containsKey(valid)) { if (commandList.containsKey(valid)) {
commandList.get(valid).add(0, c); commandList.get(valid).add(0, c);

View File

@@ -5,5 +5,5 @@ PT:5/3
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AffectedZone$ Battlefield | AddPower$ AffectedX | Description$ Each creature you control gets +2/+0 for each Equipment attached to it. S:Mode$ Continuous | Affected$ Creature.YouCtrl | AffectedZone$ Battlefield | AddPower$ AffectedX | Description$ Each creature you control gets +2/+0 for each Equipment attached to it.
SVar:AffectedX:Count$Valid Equipment.Attached/Times.2 SVar:AffectedX:Count$Valid Equipment.Attached/Times.2
S:Mode$ Continuous | Affected$ You | AddKeyword$ You may pay 0 rather than pay equip costs. | CheckSVar$ X | SVarCompare$ LT1 | Description$ You may pay {0} rather than pay the equip cost of the first equip ability you activate each turn. S:Mode$ Continuous | Affected$ You | AddKeyword$ You may pay 0 rather than pay equip costs. | CheckSVar$ X | SVarCompare$ LT1 | Description$ You may pay {0} rather than pay the equip cost of the first equip ability you activate each turn.
SVar:X:Count$YouEquippedThisTurn SVar:X:Count$ThisTurnActivated_Activated.Equip+YouCtrl
Oracle:Each creature you control gets +2/+0 for each Equipment attached to it.\nYou may pay {0} rather than pay the equip cost of the first equip ability you activate each turn. Oracle:Each creature you control gets +2/+0 for each Equipment attached to it.\nYou may pay {0} rather than pay the equip cost of the first equip ability you activate each turn.

View File

@@ -5,7 +5,7 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S
SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Card.Equipment+YouCtrl SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Card.Equipment+YouCtrl
S:Mode$ CastWithFlash | ValidSA$ Activated.Equip | Caster$ You | Condition$ PlayerTurn | Description$ As long as it's your turn, you may activate equip abilities any time you could cast an instant. S:Mode$ CastWithFlash | ValidSA$ Activated.Equip | Caster$ You | Condition$ PlayerTurn | Description$ As long as it's your turn, you may activate equip abilities any time you could cast an instant.
S:Mode$ Continuous | Affected$ You | AddKeyword$ You may pay 0 rather than pay equip costs. | CheckSVar$ X | SVarCompare$ LT1 | Condition$ PlayerTurn | Description$ You may pay {0} rather than pay the equip cost of the first equip ability you activate during each of your turns. S:Mode$ Continuous | Affected$ You | AddKeyword$ You may pay 0 rather than pay equip costs. | CheckSVar$ X | SVarCompare$ LT1 | Condition$ PlayerTurn | Description$ You may pay {0} rather than pay the equip cost of the first equip ability you activate during each of your turns.
SVar:X:Count$YouEquippedThisTurn SVar:X:Count$ThisTurnActivated_Activated.Equip+YouCtrl
DeckHas:Ability$Graveyard DeckHas:Ability$Graveyard
DeckNeeds:Type$Equipment DeckNeeds:Type$Equipment
Oracle:When Forge Anew enters the battlefield, return target Equipment card from your graveyard to the battlefield.\nAs long as it's your turn, you may activate equip abilities any time you could cast an instant.\nYou may pay {0} rather than pay the equip cost of the first equip ability you activate during each of your turns. Oracle:When Forge Anew enters the battlefield, return target Equipment card from your graveyard to the battlefield.\nAs long as it's your turn, you may activate equip abilities any time you could cast an instant.\nYou may pay {0} rather than pay the equip cost of the first equip ability you activate during each of your turns.

View File

@@ -3,7 +3,7 @@ ManaCost:2 U R W
Types:Legendary Creature Human Shaman Types:Legendary Creature Human Shaman
PT:2/5 PT:2/5
S:Mode$ Continuous | Affected$ You | AddKeyword$ CyclingForZero | CheckSVar$ X | SVarCompare$ LT1 | Description$ You may pay {0} rather than pay the cycling cost of the first card you cycle each turn. S:Mode$ Continuous | Affected$ You | AddKeyword$ CyclingForZero | CheckSVar$ X | SVarCompare$ LT1 | Description$ You may pay {0} rather than pay the cycling cost of the first card you cycle each turn.
SVar:X:Count$YouCycledThisTurn SVar:X:Count$ThisTurnActivated_Activated.Cycling+YouCtrl
T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 2 | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you draw your second card each turn, create a 2/2 red and white Dinosaur Cat creature token. T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 2 | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you draw your second card each turn, create a 2/2 red and white Dinosaur Cat creature token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ rw_2_2_dinosaur_cat | TokenOwner$ You SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ rw_2_2_dinosaur_cat | TokenOwner$ You
DeckHas:Ability$Token DeckHas:Ability$Token

View File

@@ -7,5 +7,5 @@ T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefi
SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Instant.YouOwn+withCycling,Instant.YouOwn+withTypeCycling,Sorcery.YouOwn+withCycling,Sorcery.YouOwn+withTypeCycling | TgtPrompt$ Select target instant or sorcery card with a cycling ability from your graveyard SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Instant.YouOwn+withCycling,Instant.YouOwn+withTypeCycling,Sorcery.YouOwn+withCycling,Sorcery.YouOwn+withTypeCycling | TgtPrompt$ Select target instant or sorcery card with a cycling ability from your graveyard
T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Graveyard | CheckSVar$ YouCycled | SVarCompare$ GE2 | Execute$ TrigReturn | TriggerDescription$ At the beginning of each end step, if you cycled two or more cards this turn, return CARDNAME from your graveyard to your hand. T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Graveyard | CheckSVar$ YouCycled | SVarCompare$ GE2 | Execute$ TrigReturn | TriggerDescription$ At the beginning of each end step, if you cycled two or more cards this turn, return CARDNAME from your graveyard to your hand.
SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Hand SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Hand
SVar:YouCycled:Count$YouCycledThisTurn SVar:YouCycled:Count$ThisTurnActivated_Activated.Cycling+YouCtrl
Oracle:Flying\nWhen Spellpyre Phoenix enters the battlefield, you may return target instant or sorcery card with a cycling ability from your graveyard to your hand.\nAt the beginning of each end step, if you cycled two or more cards this turn, return Spellpyre Phoenix from your graveyard to your hand. Oracle:Flying\nWhen Spellpyre Phoenix enters the battlefield, you may return target instant or sorcery card with a cycling ability from your graveyard to your hand.\nAt the beginning of each end step, if you cycled two or more cards this turn, return Spellpyre Phoenix from your graveyard to your hand.