Let's talk about suspend...

I think I made a suspend mechanic that is close enough to the real deal. I'm presenting it so that together we can find eventual improvements or things that should work differently.
First, these are important things you may not know that make my implementation possible:
First, these are important things you may not know that make my implementation possible:
- We can put counters on exiled cards. I think we can put counters on cards no matter where they are (maybe we should thank Aretopolis for that?
).
- Usually you can't make a query that shows exiled cards, but you can do it with the "filterDC" trick that you can see on Wild Pair: you populate a data chest with card pointers, and then you pass it to the query. No matter where those cards are, they will be displayed right in front of the player, as they were in hand or library.
- Code: Select all
<?xml version='1.0'?>
<CARD_V2>
<FILENAME text="KELDON_HALBERDIER_108891" />
<CARDNAME text="KELDON_HALBERDIER" />
<TITLE>
<LOCALISED_TEXT LanguageCode="en-US"><![CDATA[Keldon Halberdier]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="it-IT"><![CDATA[Alabardiere di Keld]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="de-DE"><![CDATA[Keldonischer Hellebardier]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="fr-FR"><![CDATA[Hallebardier kelde]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="es-ES"><![CDATA[Alabardero keldon]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="jp-JA"><![CDATA[ケルドの矛槍兵]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="ko-KR"><![CDATA[Keldon Halberdier]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="ru-RU"><![CDATA[Келдонский Алебардщик]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="pt-BR"><![CDATA[Alabardeiro Keldoniano]]></LOCALISED_TEXT>
</TITLE>
<MULTIVERSEID value="108891" />
<ARTID value="A108891" />
<ARTIST name="Parente" />
<CASTING_COST cost="{4}{R}" />
<TYPE metaname="Creature" />
<SUB_TYPE metaname="Human" order_de-DE="0" order_es-ES="1" order_fr-FR="0" order_it-IT="1" order_jp-JA="0" order_ko-KR="0" order_pt-BR="0" order_ru-RU="0" />
<SUB_TYPE metaname="Warrior" order_de-DE="1" order_es-ES="0" order_fr-FR="1" order_it-IT="0" order_jp-JA="1" order_ko-KR="1" order_pt-BR="1" order_ru-RU="1" />
<EXPANSION value="TSP" />
<RARITY metaname="C" />
<POWER value="4" />
<TOUGHNESS value="1" />
<STATIC_ABILITY>
<LOCALISED_TEXT LanguageCode="en-US"><![CDATA[First strike]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="fr-FR"><![CDATA[Initiative]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="es-ES"><![CDATA[Daña primero.]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="de-DE"><![CDATA[Erstschlag]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="it-IT"><![CDATA[Attacco improvviso]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="jp-JA"><![CDATA[先制攻撃]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="ko-KR"><![CDATA[선제공격]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="ru-RU"><![CDATA[Первый удар]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="pt-BR"><![CDATA[Iniciativa]]></LOCALISED_TEXT>
<CONTINUOUS_ACTION>
local characteristics = Object():GetCurrentCharacteristics()
characteristics:Characteristic_Set( CHARACTERISTIC_FIRST_STRIKE, 1 )
</CONTINUOUS_ACTION>
</STATIC_ABILITY>
<ACTIVATED_ABILITY forced_skip="1" active_zone="ZONE_HAND">
<LOCALISED_TEXT LanguageCode="en-US"><![CDATA[Suspend 4—{R}]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="it-IT"><![CDATA[Sospendere 4—{R}]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="de-DE"><![CDATA[Aussetzen 4 — {R}]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="fr-FR"><![CDATA[Suspension 4 — {R}]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="es-ES"><![CDATA[Suspender 4—{R}]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="jp-JA"><![CDATA[待機 4―{R}]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="ko-KR"><![CDATA[Suspend 4—{R}]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="ru-RU"><![CDATA[Отсрочка 4—{R}]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="pt-BR"><![CDATA[Suspender 4—{R}]]></LOCALISED_TEXT>
<COST type="Mana" cost="{R}" />
<AVAILABILITY>
return Object():CanBePlayed( EffectController() ) and
(Object():GetCurrentCharacteristics():Characteristic_Get( CHARACTERISTIC_FLASH ) ~= 0 or
EffectController():SorceryTime() ~= 0)
</AVAILABILITY>
<RESOLUTION_TIME_ACTION>
Object():RemoveFromGame()
</RESOLUTION_TIME_ACTION>
<RESOLUTION_TIME_ACTION>
local suspend = 4
Object():AddCounters( MTG():GetCountersType("TIME"), suspend )
</RESOLUTION_TIME_ACTION>
</ACTIVATED_ABILITY>
<TRIGGERED_ABILITY auto_skip="1" active_zone="ZONE_REMOVED_FROM_GAME">
<TRIGGER value="BEGINNING_OF_STEP" simple_qualifier="controller">
return ( EffectController():MyTurn() ~= 0 ) and ( MTG():GetStep() == STEP_UPKEEP )
</TRIGGER>
<RESOLUTION_TIME_ACTION>
if Object():GetZone() == ZONE_REMOVED_FROM_GAME and Object():CountCounters(MTG():GetCountersType("TIME")) > 0 then
Object():RemoveCounters(MTG():GetCountersType("TIME"), 1)
end
</RESOLUTION_TIME_ACTION>
</TRIGGERED_ABILITY>
<TRIGGERED_ABILITY auto_skip="1" active_zone="ZONE_REMOVED_FROM_GAME">
<TRIGGER value="COUNTERS_CHANGED" simple_qualifier="self">
return CounterTypeIndex() == MTG():GetCountersType("TIME")
</TRIGGER>
<RESOLUTION_TIME_ACTION>
if Object():GetZone() == ZONE_REMOVED_FROM_GAME and Object():CountCounters(CounterTypeIndex()) > 0 then
local filterDC = EffectDC():Make_Chest(0)
filterDC:Set_CardPtr(0, Object())
for i=0,MTG():GetNumberOfPlayers()-1 do
local nthPlayer = MTG():GetNthPlayer(i)
if nthPlayer ~= nil and nthPlayer:IsAI() == 0 then
nthPlayer:ChooseTargetFromDCWithFlags( NO_VALIDATION, "CARD_QUERY_LOOK_AT_SUSPENDED_CARD", filterDC,
EffectDC():Make_Chest(1), QUERY_FLAG_CAN_BE_FINISHED_EARLY )
end
end
end
</RESOLUTION_TIME_ACTION>
</TRIGGERED_ABILITY>
<TRIGGERED_ABILITY auto_skip="1" priority="-1" active_zone="ZONE_REMOVED_FROM_GAME">
<TRIGGER value="COUNTERS_CHANGED" simple_qualifier="self">
return CounterTypeIndex() == MTG():GetCountersType("TIME") and Object():CountCounters(CounterTypeIndex()) == 0
</TRIGGER>
<RESOLUTION_TIME_ACTION>
if Object():GetZone() == ZONE_REMOVED_FROM_GAME and Object():CanBePlayed( Object():GetOwner() ) then
Object():PlayFreeFromAnywhere( Object():GetOwner() )
if Object():GetCardType():Test( CARD_TYPE_CREATURE ) ~= 0 then
EffectDC():Set_Int(2, 1)
end
end
</RESOLUTION_TIME_ACTION>
<RESOLUTION_TIME_ACTION>
if EffectDC():Get_Int(2) == 1 then
local delayDC = EffectDC():Make_Chest(1)
delayDC:Set_CardPtr(0, Object())
delayDC:Protect_CardPtr(0)
MTG():CreateDelayedTrigger(1, delayDC)
end
</RESOLUTION_TIME_ACTION>
</TRIGGERED_ABILITY>
<TRIGGERED_ABILITY resource_id="1" internal="1" filter_zone="ZONE_IN_PLAY">
<CLEANUP fire_once="1" />
<TRIGGER value="ZONECHANGE_END" to_zone="ZONE_IN_PLAY" from_zone="ZONE_STACK">
return EffectDC():Get_CardPtr(0) ~= nil and
TriggerObject() == EffectDC():Get_CardPtr(0) and
TriggerObject():GetErstwhileErstwhileZone() == ZONE_REMOVED_FROM_GAME
</TRIGGER>
<CONTINUOUS_ACTION layer="6">
local object = EffectDC():Get_CardPtr(0)
if object ~= nil then
local characteristics = object:GetCurrentCharacteristics()
characteristics:Characteristic_Set( CHARACTERISTIC_HASTE, 1 )
end
</CONTINUOUS_ACTION>
<DURATION>
local object = EffectDC():Get_CardPtr(0)
return object == nil or object:GetZone() ~= ZONE_IN_PLAY or object:GetController() ~= EffectController()
</DURATION>
</TRIGGERED_ABILITY>
<HELP title="MORE_INFO_BADGE_TITLE_0" body="MORE_INFO_BADGE_BODY_0" zone="ZONE_ANY" />
<SFX text="COMBAT_CHOP_LARGE_ATTACK" power_boundary_min="4" power_boundary_max="-1" />
<SFX text="COMBAT_CHOP_SMALL_ATTACK" power_boundary_min="1" power_boundary_max="3" />
</CARD_V2>
- The first ability is activated and it works from the hand. Rules say that you must be able to play the card normally if you want to suspend it, so I'm checking that the card can be played and that either it has flash or it's sorcery time (on instants we would make only the first check, of course). When I activate the ability, I remove the card from game and place the right number of counters. It's on forced_skip because rules say that you can't answer to suspend abilities. But don't worry, players will have a feedback on each suspended card, just wait and see.
- The second ability is triggered and it removes a time counter at the beginning of the owner's upkeep. I'm not checking if I removed the last counter, I'll do that in another ability because the two events are unrelated: if I remove the last counter by other means, it must work anyway.
- The third ability is triggered and it serves for the feedback I was talking about before: when the number of time counters on the card changes without going to 0, every non-AI player gets a query that does nothing if you choose the card, but it shows the card so that everyone knows the current number of counters.
- The fourth ability triggers when the last counter is removed: it plays the card, and then it sets the delayed trigger that gives haste if it's a creature. Why did I use 2 separate actions? Because it seems that Protect_CardPtr protects the pointer for 1 zone change only, so I had to wait for the card to be on the stack before protecting the card pointer.
- The fifth ability gives haste to the card when it enters the battlefield after being suspended.
- You have to turn on the option for keeping priority if you want to be able to play suspend abilities in sorcery time when you don't have enough mana to play anything else, otherwise the game will just move on.
- The AI doesn't understand how to use suspend, and I think it's not just because I didn't write the AI_AVAILABILITY. Either we add a triggered ability that forces the AI to suspend the cards when it's possible, or... I don't know.