It is currently 25 Apr 2024, 14:12
   
Text Size

Offering mechanic

Moderator: CCGHQ Admins

Offering mechanic

Postby 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:
  • 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 {BR} would be counted as {B} {R}. Actually, I think there will be problems managing any multicolored sacrifice, so I recommend using the creatures in mono-colored decks only.
At least it doesn't need to be an activated ability anymore, which is a step ahead from DotP2013. :D

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>
Any improvement suggestion is still welcome.
DATA_DLC_Offering.zip
Offering mechanic for DotP2014 (fixed)
(551.77 KiB) Downloaded 336 times

EDIT: I had left 2 "&gt;" 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
And this is the "Moonfolk offering" ability (it can be adapted for any card of the offering cycle, you just have to change the creature type in the filter):
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 &gt; 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>
As you can see, this is an approximation:
  • 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.
Of course if anybody comes up with a better idea, I'll be happy to see it.
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...
User avatar
thefiremind
Programmer
 
Posts: 3515
Joined: 07 Nov 2011, 10:55
Has thanked: 118 times
Been thanked: 721 times

Re: Offering mechanic

Postby 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?
User avatar
GrovyleXShinyCelebi
 
Posts: 294
Joined: 12 Jun 2013, 18:23
Has thanked: 14 times
Been thanked: 37 times

Re: Offering mechanic

Postby 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...
User avatar
thefiremind
Programmer
 
Posts: 3515
Joined: 07 Nov 2011, 10:55
Has thanked: 118 times
Been thanked: 721 times


Return to Programming Talk

Who is online

Users browsing this forum: No registered users and 20 guests


Who is online

In total there are 20 users online :: 0 registered, 0 hidden and 20 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 20 guests

Login Form