Module:GameData: Difference between revisionsGive feedback
Jump to navigation
Jump to search
Add public entity_matches() and refactor get_entities to reuse it, so ability-list membership can share one matching implementation (with help from vergir-bot LLM) |
add comment about subabilities |
||
| Line 18: | Line 18: | ||
-- - Name must not be nil | -- - Name must not be nil | ||
-- - IsSelectable, if present, must not be false | -- - IsSelectable, if present, must not be false | ||
-- Specifically for hero abilities, some of them are split into two entities, eg. Vexing Bolt has a sub-ability: Redirect Bolt. | |||
-- The secondary sub-abilities most often have nil name and get filtered out by this function | |||
local function is_active(record) | local function is_active(record) | ||
if record["IsDisabled"] == true then return false end | if record["IsDisabled"] == true then return false end | ||
Revision as of 13:16, 17 July 2026
Documentation for this module may be created at Module:GameData/doc
-- Provides generic access to game data files
local p = {}
-- Dataset descriptors. Values are the data file paths passed to mw.loadJsonData.
-- Callers should use these constants rather than raw strings.
p.Dataset = {
ITEMS = "Data:ItemData.json",
ABILITIES = "Data:AbilityData.json",
HEROES = "Data:HeroData.json",
CONVARS = "Data:Convars.json",
}
-- Returns true if a record represents an active, usable entity.
-- Applies consistently across all datasets:
-- - IsDisabled must not be true
-- - Name must not be nil
-- - IsSelectable, if present, must not be false
-- Specifically for hero abilities, some of them are split into two entities, eg. Vexing Bolt has a sub-ability: Redirect Bolt.
-- The secondary sub-abilities most often have nil name and get filtered out by this function
local function is_active(record)
if record["IsDisabled"] == true then return false end
if record["Name"] == nil then return false end
if record["IsSelectable"] ~= nil and record["IsSelectable"] == false then return false end
return true
end
-- Recursively searches a record for a match against prop, which may be:
-- - a key name: matches if the key exists with a non-zero/non-empty value
-- - a plain value: matches if the string appears as a value anywhere in the record
-- Returns true if a match is found, false otherwise.
local function record_matches_prop(record, prop)
for k, v in pairs(record) do
-- Key match: property name exists with a meaningful value
if k == prop then
if type(v) == "table" then
if v["Value"] ~= nil then
v = v["Value"]
else
for _ in pairs(v) do return true end
return false
end
end
if v ~= nil and v ~= 0 and v ~= "" and v ~= "0" and v ~= "0m" then
return true
end
end
if type(v) == "table" and k ~= "DisabledStateMask" then
if record_matches_prop(v, prop) then return true end
elseif type(v) == "string" then
-- Value match: prop string appears as a value (e.g. Scale.Type = "melee")
if v == prop then return true end
end
end
return false
end
-- Returns true if a record matches at least one of the given properties,
-- using the same key-name-or-string-value semantics as get_entities.
-- Public so other modules (e.g. Module:AbilityTable membership) can reuse it.
function p.entity_matches(record, properties)
for _, prop in ipairs(properties) do
if record_matches_prop(record, prop) then
return true
end
end
return false
end
-- Returns all active entities from a dataset that match at least one of the
-- given properties. A property may be matched either as a key name (the
-- property exists with a non-zero value) or as a plain string value anywhere
-- in the record (e.g. "melee" matching Scale.Type = "melee").
-- @param dataset string one of the GameData.Dataset constants
-- @param properties table array of property strings
-- @return table array of matching entity records
function p.get_entities(dataset, properties)
local data = mw.loadJsonData(dataset)
local results = {}
for _, record in pairs(data) do
if is_active(record) and p.entity_matches(record, properties) then
table.insert(results, record)
end
end
return results
end
--------------------------------------------------------------------------------
-- Unified property lookup: get_prop
-- Uses ResourceLookup to route directly to the correct dataset.
-- Returns raw values for use in templates and expressions.
--------------------------------------------------------------------------------
-- Find an entity by display name or internal key.
-- Uses ResourceLookup type field to go directly to the right dataset.
-- Returns (entity_record, type_string) or (nil, nil).
local function find_entity(identifier)
local resource = mw.loadJsonData("Data:ResourceLookup.json")[identifier:lower()]
if resource then
local data
if resource.type == "ability" then
data = mw.loadJsonData(p.Dataset.ABILITIES)
elseif resource.type == "hero" then
data = mw.loadJsonData(p.Dataset.HEROES)
elseif resource.type == "item" then
data = mw.loadJsonData(p.Dataset.ITEMS)
end
if data and data[resource.key] then
return data[resource.key], resource.type
end
end
-- Fallback: try as direct internal key
local datasets = {
{ p.Dataset.ABILITIES, "ability" },
{ p.Dataset.HEROES, "hero" },
{ p.Dataset.ITEMS, "item" },
}
for _, ds in ipairs(datasets) do
local data = mw.loadJsonData(ds[1])
if data[identifier] then return data[identifier], ds[2] end
end
return nil, nil
end
-- Traverse a table using dot notation, unwrap tables with a .Value field.
-- Supports numeric indices for arrays (e.g. "Upgrades.1.WeaponDamageBonus").
local function resolve_prop(tbl, prop)
if not prop or prop == "" then return nil end
local element = tbl
for segment in string.gmatch(prop, "[^%.]+") do
if type(element) ~= "table" then return nil end
local next = element[segment]
if next == nil then
local num = tonumber(segment)
if num then next = element[num] end
end
element = next
if element == nil then return nil end
end
if type(element) == "table" then
return element.Value or ""
end
return element
end
-- Looks up a console variable in Data:Convars.json. A convar value is stored
-- either as a plain scalar (e.g. "adsp_alley_min": 122) or as a table holding
-- the value plus a description (e.g. { value = 75, description = "..." }), in
-- which case only the value is returned. The exact key is tried first, then a
-- lower-case form. Returns (value, found).
local function lookup_convar(key)
local data = mw.loadJsonData(p.Dataset.CONVARS)
local entry = data[key]
if entry == nil then
entry = data[mw.ustring.lower(key)]
end
if entry == nil then
return nil, false
end
if type(entry) == "table" then
return entry.value, true
end
return entry, true
end
-- {{#invoke:GameData|get_prop|...}}
-- Two modes:
-- Entity: {{#invoke:GameData|get_prop|ENTITY_NAME|PROPERTY}}
-- Finds entity via ResourceLookup, returns the raw value (no
-- formatting). Supports dot notation for nested properties
-- (e.g. "Scale.Value"); tables with a .Value field are unwrapped.
-- Convar: {{#invoke:GameData|get_prop|Convar|CONVAR_NAME}}
-- Looks up the convar in Data:Convars.json and returns its value,
-- unwrapping the { value, description } form when present.
function p.get_prop(frame)
local arg1 = frame.args[1]
local arg2 = frame.args[2]
if not arg1 then return "" end
-- Convar mode: first argument is the literal keyword "Convar".
if mw.ustring.lower(arg1) == "convar" then
if not arg2 or arg2 == "" then return "" end
local value, found = lookup_convar(arg2)
if not found then
return '<span style="color:red;">Convar not found: ' .. arg2 .. '</span>'
end
if value == nil then return "" end
return value
end
-- Entity mode: search abilities, heroes, and items.
local name = arg1
local prop = arg2
if not prop then return "" end
local entity, etype = find_entity(name)
if not entity then
return '<span style="color:red;">Entity not found: ' .. name .. '</span>'
end
local result = resolve_prop(entity, prop)
if result ~= nil then return result end
return '<span style="color:red;">Prop not found: ' .. name .. '/' .. prop .. '</span>'
end
return p