Board index
Programs with AI or Rules Enforcement
Magic: The Gathering - Duels of the Planeswalkers
Programming Talk



Offering mechanic
Moderator: CCGHQ Admins
Offering mechanic
by thefiremind » 29 Sep 2013, 12:26
I updated my DotP2013 offering mechanic to DotP2014, and I made a small WAD with the needed functions plus the 5 Patron creatures.
Please be aware that my offering mechanic is an approximation:

For those who like to see the code without downloading, here's a transcription of the LOL file plus Goblin offering as an example:
EDIT: I had left 2 ">" in the LOL file, now it's fixed.
Please be aware that my offering mechanic is an approximation:
- It doesn't take cost reductions or increases into account. The game will probably try to enforce the increases anyway, but in case the mana isn't enough to pay them I'm not sure about what will happen.
- It won't work properly with creatures that contain hybrid mana in their costs. The functions use ChromaCount in order to retrieve the cost of the possible sacrifices, and this means that
would be counted as
. Actually, I think there will be problems managing any multicolored sacrifice, so I recommend using the creatures in mono-colored decks only.

For those who like to see the code without downloading, here's a transcription of the LOL file plus Goblin offering as an example:
- | Open
- Code: Select all
-- Functions for the offering mechanic (some of them can be useful for other purposes, too)
OFF_ColourToString = function(colour)
-- converts color constants (i.e. COLOUR_RED) to mana strings (i.e. "{R}")
local mana_table = {"{W}", "{U}", "{B}", "{R}", "{G}"}
return mana_table[colour]
end
OFF_ManaString = function(colour, amount)
-- generates a mana string of the selected color and amount
local string = ""
if amount == 0 then
return "{0}"
end
for i=1,amount do
string = string..OFF_ColourToString(colour)
end
return string
end
OFF_GetCostOfColour = function(object, colour)
-- returns the amount of mana of the selected color in object's cost
local filter = ClearFilter()
filter:SetZone(ZONE_ANYWHERE)
filter:Add(FE_CARD_INSTANCE, OP_IS, object)
return filter:ChromaCount(colour)
end
OFF_GetCostStringOfColour = function(object, colour)
-- same as OFF_GetCostOfColour, but it generates a string (useful for PayManaCost, CanPayManaCost, etc.)
local amount = OFF_GetCostOfColour(object, colour)
return OFF_ManaString(colour, amount)
end
OFF_GetCostString = function(object)
-- returns the full mana cost string but it doesn't work properly with hybrid or phyrexian mana, for obvious reasons
local cmc = object:GetConvertedManaCost()
if cmc > 0 then
local string = ""
local coloured_mana = 0
for i=1,5 do
local amount = OFF_GetCostOfColour(object, i)
coloured_mana = coloured_mana + amount
if amount > 0 then
string = string..OFF_GetCostStringOfColour(object, i)
end
end
if cmc > coloured_mana then
string = "{"..(cmc-coloured_mana).."}"..string
end
return string
else
return "{0}"
end
end
OFF_GetSubtractionCostString = function(object1, object2)
-- returns the full mana cost string of object1's cost minus object2's cost
local object1cost = {}
local object2cost = {}
local subtraction = {}
object1cost[0] = object1:GetConvertedManaCost()
for i=1,5 do
object1cost[i] = OFF_GetCostOfColour(object1, i)
object1cost[0] = object1cost[0] - object1cost[i]
end
object2cost[0] = object2:GetConvertedManaCost()
for i=1,5 do
object2cost[i] = OFF_GetCostOfColour(object2, i)
object2cost[0] = object2cost[0] - object2cost[i]
end
subtraction[0] = object1cost[0] - object2cost[0]
for i=1,5 do
subtraction[i] = object1cost[i] - object2cost[i]
if subtraction[i] < 0 then
subtraction[0] = subtraction[0] + subtraction[i]
subtraction[i] = 0
end
end
if subtraction[0] < 0 then
subtraction[0] = 0
end
local string = ""
if subtraction[0] > 0 then
string = "{"..subtraction[0].."}"
end
for i=1,5 do
if subtraction[i] > 0 then
string = string..OFF_ManaString(i, subtraction[i])
end
end
if string == "" then
return "{0}"
else
return string
end
end
OFF_Offering_Prerequisite = function(type)
-- to be returned as PREREQUISITE for "<type> offering" generic cost
local player = EffectController()
local filter = ClearFilter()
filter:Add(FE_SUBTYPE, OP_IS, type)
filter:Add( FE_CONTROLLER, OP_IS, EffectController() )
local filter_count = filter:EvaluateObjects()
if filter_count > 0 then
local candidates = {}
for i=0,filter_count-1 do
candidates[i] = filter:GetNthEvaluatedObject(i)
end
for i=0,filter_count-1 do
local remaining = OFF_GetSubtractionCostString( EffectSource(), candidates[i] )
if player:CanPayManaCost(remaining) then
return true
end
end
end
return false
end
OFF_Offering_Resolution1 = function(type)
-- to be used as first RESOLUTION_TIME_ACTION for "<type> offering" generic cost
MTG():ClearFilterMark()
local player = EffectController()
local filter = ClearFilter()
filter:Add(FE_SUBTYPE, OP_IS, type)
filter:Add( FE_CONTROLLER, OP_IS, EffectController() )
local filter_count = filter:EvaluateObjects()
if filter_count > 0 then
local candidates = {}
for i=0,filter_count-1 do
candidates[i] = filter:GetNthEvaluatedObject(i)
end
for i=0,filter_count-1 do
local remaining = OFF_GetSubtractionCostString( EffectSource(), candidates[i] )
if player:CanPayManaCost(remaining) == false then
candidates[i] = nil
end
end
for i=0,filter_count-1 do
if candidates[i] ~= nil then
candidates[i]:MarkForFilter()
end
end
end
filter:Clear()
filter:SetMarkedObjectsOnly()
player:ChooseItem( "CARD_QUERY_CHOOSE_PERMANENT_TO_SACRIFICE", EffectDC():Make_Targets(0) )
end
OFF_Offering_Resolution2 = function()
-- to be used as second (and last) RESOLUTION_TIME_ACTION for "<type> offering" generic cost (this doesn't need the type anymore)
local sac = EffectDC():Get_Targets(0) and EffectDC():Get_Targets(0):Get_CardPtr(0)
if sac ~= nil then
local player = EffectController()
local remaining = OFF_GetSubtractionCostString( EffectSource(), sac )
player:Sacrifice(sac)
player:PayManaCost(remaining)
end
end
- Code: Select all
<UTILITY_ABILITY qualifier="Alternate" ignore_timing="1">
<LOCALISED_TEXT LanguageCode="en-US"><![CDATA[Goblin offering]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="fr-FR"><![CDATA[Offrande de gobelin]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="es-ES"><![CDATA[Ofrenda de trasgo]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="de-DE"><![CDATA[Goblinopfer]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="it-IT"><![CDATA[Obolo del Goblin]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="jp-JA"><![CDATA[献身(ゴブリン)]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="ko-KR"><![CDATA[Goblin offering]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="ru-RU"><![CDATA[Goblin offering]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="pt-BR"><![CDATA[Oferenda dos Goblins]]></LOCALISED_TEXT>
<COST type="generic">
<PREREQUISITE>
return OFF_Offering_Prerequisite(CREATURE_TYPE_GOBLIN)
</PREREQUISITE>
<RESOLUTION_TIME_ACTION>
OFF_Offering_Resolution1(CREATURE_TYPE_GOBLIN)
</RESOLUTION_TIME_ACTION>
<RESOLUTION_TIME_ACTION>
OFF_Offering_Resolution2()
</RESOLUTION_TIME_ACTION>
</COST>
</UTILITY_ABILITY>
DATA_DLC_Offering.zip
- Offering mechanic for DotP2014 (fixed)
- (551.77 KiB) Downloaded 373 times
EDIT: I had left 2 ">" in the LOL file, now it's fixed.
- Old topic contents for DotP2013 (might still be useful to someone) | Open
- Challenged by a request, I just made a decent approximation of Betrayers of Kamigawa's "offering" mechanic. In order to do that with ease, I added a new function to my mana functions' LOL file. I'm pasting here the already existing related functions, plus the new GetSubtractionCost:
- Code: Select all
ColourToString = function(colour)
-- converts color constants (i.e. COLOUR_RED) to mana strings (i.e. "{R}")
local mana_table = {"{W}", "{U}", "{B}", "{R}", "{G}"}
return mana_table[colour]
end
ManaString = function(colour, amount)
-- generates a mana string of the selected color and amount
local string = ""
if amount == 0 then
return "{0}"
end
for i=1,amount do
string = string..ColourToString(colour)
end
return string
end
GetCostOfColour = function(object, colour)
-- returns the amount of mana of the selected color in object's cost
local filter = Object():GetFilter()
filter:Clear()
filter:NotTargetted()
filter:SetCardInstance(object)
return filter:ChromaCount(colour)
end
GetCostStringOfColour = function(object, colour)
-- same as GetCostOfColour, but it generates a string (useful for TapLand, CanAfford, etc.)
local amount = GetCostOfColour(object, colour)
return ManaString(colour, amount)
end
GetCostString = function(object)
-- returns the full mana cost string but it doesn't work with hybrid or phyrexian mana, for obvious reasons
local cmc = object:GetConvertedManaCost()
if cmc > 0 then
local string = ""
local coloured_mana = 0
for i=1,5 do
local amount = GetCostOfColour(object, i)
coloured_mana = coloured_mana + amount
if amount > 0 then
string = string..GetCostStringOfColour(object, i)
end
end
if cmc > coloured_mana then
string = "{"..(cmc-coloured_mana).."}"..string
end
return string
else
return "{0}"
end
end
GetSubtractionCostString = function(object1, object2)
-- returns the full mana cost string of object1's cost minus object2's cost
local object1cost = {}
local object2cost = {}
local subtraction = {}
object1cost[0] = object1:GetConvertedManaCost()
for i=1,5 do
object1cost[i] = GetCostOfColour(object1, i)
object1cost[0] = object1cost[0] - object1cost[i]
end
object2cost[0] = object2:GetConvertedManaCost()
for i=1,5 do
object2cost[i] = GetCostOfColour(object2, i)
object2cost[0] = object2cost[0] - object2cost[i]
end
subtraction[0] = object1cost[0] - object2cost[0]
for i=1,5 do
subtraction[i] = object1cost[i] - object2cost[i]
if subtraction[i] < 0 then
subtraction[0] = subtraction[0] + subtraction[i]
subtraction[i] = 0
end
end
if subtraction[0] < 0 then
subtraction[0] = 0
end
local string = ""
if subtraction[0] > 0 then
string = "{"..subtraction[0].."}"
end
for i=1,5 do
if subtraction[i] > 0 then
string = string..ManaString(i, subtraction[i])
end
end
if string == "" then
return "{0}"
else
return string
end
end
- Code: Select all
<ACTIVATED_ABILITY forced_skip="1" active_zone="ZONE_HAND">
<LOCALISED_TEXT LanguageCode="en-US"><![CDATA[Moonfolk offering]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="fr-FR"><![CDATA[Offrande de lunaréen]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="es-ES"><![CDATA[Ofrenda de pueblo lunar.]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="de-DE"><![CDATA[Mondvolkopfer]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="it-IT"><![CDATA[Obolo del Lunantropo]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="jp-JA"><![CDATA[献身]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="ko-KR"><![CDATA[Moonfolk offering]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="ru-RU"><![CDATA[Moonfolk offering]]></LOCALISED_TEXT>
<LOCALISED_TEXT LanguageCode="pt-BR"><![CDATA[Oferenda dos Cidadãos-da-Lua]]></LOCALISED_TEXT>
<COST type="generic">
<TARGET_DEFINITION id="6">
MTG():ClearFilterMarkedObjectsInZone( ZONE_IN_PLAY )
local filter = Object():GetFilter()
local player = EffectController()
filter:Clear()
filter:AddSubType( CREATURE_TYPE_MOONFOLK )
filter:SetZone( ZONE_IN_PLAY )
filter:SetController( player )
filter:NotTargetted()
local filter_count = filter:EvaluateObjects()
if filter_count > 0 then
local candidates = {}
for i=0,filter_count-1 do
candidates[i] = filter:GetNthEvaluatedObject(i)
end
for i=0,filter_count-1 do
if player:CanAfford( GetSubtractionCostString( Object(), candidates[i] ) ) == 0 then
candidates[i] = nil
end
end
for i=0,filter_count-1 do
if candidates[i] ~= nil then
candidates[i]:MarkForFilter()
end
end
end
filter:SetMarkedObjectsOnly()
filter:SetHint( HINT_ENEMY, player )
</TARGET_DEFINITION>
<TARGET_DETERMINATION>
return AtLeastOneTargetFromDefinition(6)
</TARGET_DETERMINATION>
<PLAY_TIME_ACTION>
EffectController():ChooseTarget( 6, "CARD_QUERY_CHOOSE_CREATURE_TO_SACRIFICE", EffectDC():Make_Targets(0) )
</PLAY_TIME_ACTION>
<RESOLUTION_TIME_ACTION>
local sac = EffectDC():Get_Targets(0):Get_CardPtr(0)
if sac ~= nil then
local player = EffectController()
local remaining = GetSubtractionCostString( Object(), sac )
sac:Sacrifice( EffectController() )
player:TapLand(remaining)
end
</RESOLUTION_TIME_ACTION>
</COST>
<RESOLUTION_TIME_ACTION>
Object():PlayFreeFromAnywhere( EffectController() )
</RESOLUTION_TIME_ACTION>
<AI_AVAILABILITY step="end_of_turn" turn="their_turn" />
<AI_AVAILABILITY step="declare_attackers" turn="their_turn" />
<AI_AVAILABILITY type="in_response" />
<AI_AVAILABILITY step="main_1" turn="my_turn" />
<AI_AVAILABILITY step="main_2" turn="my_turn" />
</ACTIVATED_ABILITY>
- it uses an activated ability for something that shouldn't be activated (so it will trigger Burning-Tree Shaman and similar cards while it shouldn't),
- it doesn't take any cost-reducing effects (Sapphire Medallion and similar cards) into account,
- it won't work properly if you end up with a Moonfolk (or whatever other creature type you must offer) that contains hybrid or phyrexian mana in its cost.
Last edited by thefiremind on 03 Dec 2013, 10:57, edited 2 times in total.
< Former DotP 2012/2013/2014 modder >
Currently busy with life...
Currently busy with life...
-
thefiremind - Programmer
- Posts: 3515
- Joined: 07 Nov 2011, 10:55
- Has thanked: 118 times
- Been thanked: 722 times
Re: Offering mechanic
by GrovyleXShinyCelebi » 04 Oct 2013, 01:41
Nice job mate.
Two questions:
Will "return true" break any loop (like at end of OFF_Offering_Prerequisite)?
Can tables/arrays serve as an alternative to interrogations?
Two questions:
Will "return true" break any loop (like at end of OFF_Offering_Prerequisite)?
Can tables/arrays serve as an alternative to interrogations?
(in Duels 2014)
Duels 2012: viewtopic.php?f=109&t=12152
Duels 2013: viewtopic.php?f=109&t=12481&p=137458#p137458
Duels 2012: viewtopic.php?f=109&t=12152
Duels 2013: viewtopic.php?f=109&t=12481&p=137458#p137458
-
GrovyleXShinyCelebi - Posts: 294
- Joined: 12 Jun 2013, 18:23
- Has thanked: 14 times
- Been thanked: 37 times
Re: Offering mechanic
by thefiremind » 04 Oct 2013, 09:12
I moved my answers here since they don't seem specific about the offering mechanic.
< Former DotP 2012/2013/2014 modder >
Currently busy with life...
Currently busy with life...
-
thefiremind - Programmer
- Posts: 3515
- Joined: 07 Nov 2011, 10:55
- Has thanked: 118 times
- Been thanked: 722 times
3 posts
• Page 1 of 1
Who is online
Users browsing this forum: No registered users and 2 guests