• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Lua] Ability Field Wrapper

This snippet is designed to abstract away the most common ability-field related natives into a compact Ability table.
While it may initially be confusing / disorienting to work with rawcodes provided directly from the object editor, it
may prove to be more intuitive for the user in the long run, coupled with the OOP syntax that this snippet provides.

Lua:
--[[-----------------------------------------------------------------
     ----------------------------------
    |   Ability Field Wrapper
    |   - v.1.0
    |       MyPad
    |----------------------------------
    |
    |   Ability Wrapper abstracts away most of the ability-field related natives
    |   using a single table, providing OOP syntax to the otherwise cumbersome
    |   natives which require a lot of care to handle traditionally.
    |
    |----------------------------------
    |   API:
    |   Available operators:
    |       Ability[abilCode][whichUnit][dataField].<type>[opt? level][opt? index].value
    |       Ability[abilCode][whichUnit][dataField].<type>[opt? level][opt? index].value=
    |           - abilCode can either be an integer or a string; the Ability table
    |             will coerce it anyway (if string) into an integer.
    |           - whichUnit refers to the desired unit (handle).
    |           - dataField, like abilCode, can either be an integer or string.
    |               - dataField will not accept the abilityfield constants already provided in the
    |                 common.j. You can only pass the rawcode along.
    |           - level? is an optional field that tells the Ability table to use
    |             the Ability<T>LevelField equivalent functions instead.
    |               - Unlike the natives, you can use the actual level of the ability.
    |           - index? is an optional field that tells the Ability table to use
    |             the Ability<T>LevelArrayField equivalent functions instead.
    |
    |           - When attempting to set a certain field of an ability to a specified value,
    |             the operation may fail, or return false positives. There's probably no
    |             workaround for the latter, but you can immediately get the result of
    |             the operation by referring to Ability.result (see below for more details).
    |
    |       Ability[abilCode][whichUnit].refresh(func?: fun(whichUnit, abilCode))
    |           - Refreshes the ability for the given unit. Will also run
    |             the inner function while refreshing the ability when provided.
    |
    |       Ability.result  -> boolean
    |           - After performing a set operation from the above, the
    |             result of the operation is stored here, usually a boolean value.
    |
    |       AbilityData(abilCode, whichUnit, dataField)         -> AbilityTemplateObject
    |       AbilityData.create(abilCode, whichUnit, dataField)  -> AbilityTemplateObject
    |           - Returns a table with the values stored for compact use,
    |             allowing one to perform a shorthand version of the operations detailed
    |             above, as shown below:
    |               - <AbilityData>.<type>[opt? level][opt? index]
    |               - <AbilityData>.<type>[opt? level][opt? index]=
    |           - The parameters supplied are stored starting at index 0, with the
    |             given order as originally provided.
    |           - The values stored within the table are considered weak values; there's
    |             no need to worry about the garbage collector missing these.
    |
    |----------------------------------
    |   Credits:
    |       Eikonium    - For DebugUtils (Wc3Type)       
]]-----------------------------------------------------------------
do
    ---@class Ability
    local abilityTable      = {
        _curAbilID          = 0,
        _curAbilHandle      = nil,
        result              = false,
        __metatable         = true,     -- Prevent metatable overwrite
    }
    local unitTable         = {
        _lastAbilID         = 0,
        _curUnit            = nil,
        __mode              = 'v'
    }
    ---@class AbilityTemplate
    local abilityTemplate       = {}
    ---@class AbilityTemplateObject
    local abilityMetaTemplate   = {__mode   = 'v', __metatable  = true}
    local unitChildTable
    Ability                 = setmetatable({}, abilityTable)
    AbilityData             = setmetatable({}, abilityTemplate)
    unitChildTable          = setmetatable({}, unitTable)
    -- ------------------------------------------------------------ --
    --              Store natives for ease of access                --
    -- ------------------------------------------------------------ --
    local _getAbil          = BlzGetUnitAbility
    local _natives, _G      = {}, _G
    local _fourcc           = FourCC
    local function _pop_references(name)
        return {
            get             = _G["BlzGetAbility" .. name .. "Field"],
            set             = _G["BlzSetAbility" .. name .. "Field"],
            conv            = _G["ConvertAbility" .. name .. "Field"],
            level           = {
                get         = _G["BlzGetAbility" .. name .. "LevelField"],
                set         = _G["BlzSetAbility" .. name .. "LevelField"],
                conv        = _G["ConvertAbility" .. name .. "LevelField"],
            },
            array           = {
                get         = _G["BlzGetAbility" .. name .. "LevelArrayField"],
                set         = _G["BlzSetAbility" .. name .. "LevelArrayField"],
                conv        = _G["ConvertAbility" .. name .. "LevelArrayField"],
            }
        }
    end
    _natives.real           = _pop_references("Real")
    _natives.integer        = _pop_references("Integer")
    _natives.boolean        = _pop_references("Boolean")
    _natives.string         = _pop_references("String")
    -- For when typing letters are too hard.
    _natives.int            = _natives.integer
    _natives.bool           = _natives.boolean
    _natives.str            = _natives.string
    -- ------------------------------------------------------------ --
    --  ---------------------------------  --
    --          WC3Type by Eikonium        --
    --  ---------------------------------  --
    if not Wc3Type then
    do
        -------------------
        ----| Wc3Type |----
        -------------------
        ---Returns the type of a warcraft object as string, e.g. "location", when inputting a location.
        ---@param input any
        ---@return string
        function Wc3Type(input)
            local typeString = type(input)
            if typeString == 'number' then
                return (math.type(input) =='float' and 'real') or 'integer'
            elseif typeString == 'userdata' then
                typeString = tostring(input) --toString returns the warcraft type plus a colon and some hashstuff.
                return string.sub(typeString, 1, (string.find(typeString, ":", nil, true) or 0) -1) --string.find returns nil, if the argument is not found, which would break string.sub. So we need or as coalesce.
            else
                return typeString
            end
        end   
    end
    end
    --  ------------------------------------------  --
    --          Define Accessibility options        --
    --  ------------------------------------------  --
    do
        -- ------------------------------------------------------------ --
        local fieldTable        = {
            _curHandle          = nil,
            _curParam           = 0,
        }
        local actionTable       = {
            _curType            = "none",
            _optParams          = {count=0,}
        }
        local mimicFieldTable   = setmetatable({}, fieldTable)
        local mimicActionTable  = setmetatable({}, actionTable)
        -- ------------------------------------------------------------ --
        -- ------------------------------------------------------------ --
        abilityTable.__index    =
        function(t, k)
            k = (type(k) == "string") and (_fourcc(k)) or k
            if (type(k) == 'number') then
                abilityTable._curAbilID = k
                return unitChildTable
            end
            return abilityTable[k]
        end
        abilityTable.__newindex = DoNothing
        unitTable.__index       =
        function(t, k)
            if (type(k) ~= 'userdata') or (Wc3Type(k) ~= 'unit') then
                return unitTable[k]
            end
            if (unitTable._lastAbilID ~= abilityTable._curAbilID) or (unitTable._curUnit ~= k) then
                abilityTable._curAbilHandle = _getAbil(k, abilityTable._curAbilID)
            end
            unitTable._lastAbilID       = abilityTable._curAbilID
            unitTable._curUnit          = k
            return mimicFieldTable
        end
        fieldTable.__index      =
        function(t, k)
            if fieldTable[k] then
                return fieldTable[k]
            end
            k           = (type(k) == 'string' and _fourcc(k)) or k
            if (fieldTable._curParam ~= k) then
                fieldTable._curParam = k
            end
            return mimicActionTable
        end
        local _incAbil          = IncUnitAbilityLevel
        local _decAbil          = DecUnitAbilityLevel
        local try               = try or pcall
        ---Refreshes the unit's ability while applying crucial changes within the callback
        ---function, if provided. Cooldown and manacost are NOT refunded in this context.
        ---@param func? fun(whichUnit:unit, abilCode:integer)
        function fieldTable.refresh(func)
            _incAbil(unitTable._curUnit, abilityTable._curAbilID)
            if type(func) == 'function' then
                try(func, unitTable._curUnit, abilityTable._curAbilID)
            end
            _decAbil(unitTable._curUnit, abilityTable._curAbilID)
            return mimicFieldTable
        end
        actionTable.__index     =
        function(t, k)
            if (k == "value") then
                local accessType        = actionTable._curType
                actionTable._curType    = "none"
                local result
                if actionTable._optParams.count == 2 then
                    result  = _natives[accessType].array.get(abilityTable._curAbilHandle,
                                                      _natives[accessType].array.conv(fieldTable._curParam),
                                                      actionTable._optParams[1] - 1,
                                                      actionTable._optParams[2])
                elseif actionTable._optParams.count == 1 then
                    result  = _natives[accessType].level.get(abilityTable._curAbilHandle,
                                                      _natives[accessType].level.conv(fieldTable._curParam),
                                                      actionTable._optParams[1] - 1)
                else
                    result  = _natives[accessType].get(abilityTable._curAbilHandle,
                                                      _natives[accessType].conv(fieldTable._curParam))
                end
                actionTable._optParams.count    = 0
                actionTable._optParams[1]       = nil
                actionTable._optParams[2]       = nil
                return result
            end
            if (_natives[k]) then
                actionTable._curType    = k
                return mimicActionTable
            end
            k           = (type(k) == 'string' and _fourcc(k)) or k
            if (type(k) == 'number') then
                if actionTable._optParams.count >= 2 then
                    return mimicActionTable
                end
                actionTable._optParams.count    = actionTable._optParams.count + 1
                actionTable._optParams[actionTable._optParams.count]    = k
                return mimicActionTable
            end
            return nil
        end
        actionTable.__newindex  =
        function(t, k, v)
            if (k == "value") then
                local accessType        = actionTable._curType
                actionTable._curType    = "none"
                local result
                if actionTable._optParams.count == 2 then
                    result  = _natives[accessType].array.set(abilityTable._curAbilHandle,
                                                      unitTable._curUnit,
                                                      _natives[accessType].array.conv(fieldTable._curParam),
                                                      actionTable._optParams[1] - 1,
                                                      actionTable._optParams[2],
                                                      v)
                elseif actionTable._optParams.count == 1 then
                    result  = _natives[accessType].level.set(abilityTable._curAbilHandle,
                                                      unitTable._curUnit,
                                                      _natives[accessType].level.conv(fieldTable._curParam),
                                                      actionTable._optParams[1] - 1,
                                                      v)
                else
                    result  = _natives[accessType].set(abilityTable._curAbilHandle,
                                                      unitTable._curUnit,
                                                      _natives[accessType].conv(fieldTable._curParam),
                                                      v)
                end
                actionTable._optParams.count    = 0
                actionTable._optParams[1]       = nil
                actionTable._optParams[2]       = nil
                abilityTable.result             = result
                return result
            end
            abilityTable.result                 = false
            if actionTable[k] then
                return false
            end
            return false
        end
    end
    --  -----------------------------  --
    --          Define Wrappers        --
    --  -----------------------------  --
    do
        --- Creates a template table for our convenience (just in case multiple referencing instances of a certain ability are required).
        --- The template abstracts away the need to invoke the Ability syntax when needed.
        ---@param abilCode integer
        ---@param whichUnit unit
        ---@param dataField integer
        ---@return AbilityTemplateObject
        function abilityTemplate.create(abilCode, whichUnit, dataField)
            return setmetatable({
                [0] = abilCode,
                [1] = whichUnit,
                [2] = dataField
            }, abilityMetaTemplate)
        end
        abilityTemplate.__call          = abilityTemplate.create
        abilityTemplate.__index         = abilityTemplate
        abilityMetaTemplate.__newindex  = DoNothing
        abilityMetaTemplate.__index     =
        function(t, k)
            if _natives[k] then
                return Ability[ t[0] ][ t[1] ][ t[2] ][k]
            end
            return abilityMetaTemplate[k]
        end
        abilityMetaTemplate.__newindex  =
        function(t, k, v)
            if _natives[k] or abilityMetaTemplate[k] then
                return t
            end
            return rawset(t, k, v)
        end
    end
end

Lua:
function foo(whichUnit, abilID, lvl)
    Ability[abilID][whichUnit].refresh(
    function(whichUnit, abilID)
        Ability[abilID][whichUnit]['acdn'].real[lvl].value = Ability[abilID][whichUnit]['acdn'].real[lvl].value - 1.0
    end) -- This refreshes the ability with the updated cooldown.
    print(Ability.result)
end

  • Eikonium - for DebugUtils, specifically the Wc3Type function, which trivializes the need to check for the actual type of the provided userdata.
 
Top