- Joined
- May 9, 2014
- Messages
- 1,819
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.
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.