Module:GameData: Difference between revisionsGive feedback
Jump to navigation
Jump to search
get_prop: optional third argument for Street Brawl ability values, falling back to base data when unchanged (with help from vergir-bot LLM) |
Support "Key:Value" and dot-path prop targets in entity matching (with help from vergir-bot LLM) |
||
| Line 38: | Line 38: | ||
-- - a plain value: matches if the string appears as a value anywhere in the record | -- - a plain value: matches if the string appears as a value anywhere in the record | ||
-- Returns true if a match is found, false otherwise. | -- Returns true if a match is found, false otherwise. | ||
-- This handles plain single-word targets; the "Key:Value" and dotted-path forms | |||
-- are handled by record_matches_path below. | |||
local function record_matches_prop(record, prop) | local function record_matches_prop(record, prop) | ||
for k, v in pairs(record) do | for k, v in pairs(record) do | ||
| Line 64: | Line 66: | ||
end | end | ||
-- Returns true if a record matches at least one of the given properties, | -- True if a value counts as "present": non-zero, non-empty, and not a | ||
-- | -- placeholder distance. A table is judged by its .Value field when it has one, | ||
-- otherwise by whether it holds anything at all. | |||
local function value_is_meaningful(v) | |||
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 | |||
return v ~= nil and v ~= 0 and v ~= "" and v ~= "0" and v ~= "0m" | |||
end | |||
-- True if a value equals `expected`, which must already be lower-cased. A table | |||
-- is unwrapped to its .Value field, so "Damage:90" matches | |||
-- { Value = 90, Scale = {...} }. A table with no .Value never matches, which is | |||
-- what keeps "Damage:spirit" from matching on a nested Damage.Scale.Type. | |||
local function value_equals(v, expected) | |||
if type(v) == "table" then v = v["Value"] end | |||
if v == nil or type(v) == "table" then return false end | |||
return mw.ustring.lower(tostring(v)) == expected | |||
end | |||
-- Follows path segments from `index` onward, starting at `node`. Segments after | |||
-- the first are followed strictly: no searching, one step per segment. Numeric | |||
-- segments fall back to array indices ("Upgrades.1.Damage"), and array elements | |||
-- are stepped through transparently, so "Upgrades.Damage" reaches the Damage | |||
-- field of any upgrade entry. | |||
local function follow_path(node, segments, index, value) | |||
if index > #segments then | |||
if value ~= nil then return value_equals(node, value) end | |||
return value_is_meaningful(node) | |||
end | |||
if type(node) ~= "table" then return false end | |||
local segment = segments[index] | |||
local next_node = node[segment] | |||
if next_node == nil then | |||
local num = tonumber(segment) | |||
if num then next_node = node[num] end | |||
end | |||
if next_node ~= nil and follow_path(next_node, segments, index + 1, value) then | |||
return true | |||
end | |||
for _, element in ipairs(node) do | |||
if type(element) == "table" and follow_path(element, segments, index, value) then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
-- Recursively locates the first path segment at any depth, then follows the rest | |||
-- from there. Searching for the first segment is what lets a path reach into | |||
-- sub-ability records the same way plain key targets already do. | |||
local function record_matches_path(record, segments, value) | |||
for k, v in pairs(record) do | |||
if k == segments[1] and follow_path(v, segments, 2, value) then | |||
return true | |||
end | |||
if type(v) == "table" and k ~= "DisabledStateMask" then | |||
if record_matches_path(v, segments, value) then return true end | |||
end | |||
end | |||
return false | |||
end | |||
-- Splits a target into path segments plus an optional expected value. The first | |||
-- colon separates the two halves, so dots inside the value are safe | |||
-- ("Radius:3.5"). Returns nil for a plain single-word target with no colon, | |||
-- which keeps its existing key-name-or-string-value meaning. | |||
local function parse_target(prop) | |||
if type(prop) ~= "string" then return nil end | |||
local keys, value = prop, nil | |||
local colon = prop:find(":", 1, true) | |||
if colon then | |||
keys = mw.text.trim(prop:sub(1, colon - 1)) | |||
value = mw.text.trim(prop:sub(colon + 1)) | |||
if keys == "" or value == "" then return nil end | |||
value = mw.ustring.lower(value) | |||
end | |||
local segments = {} | |||
for segment in keys:gmatch("[^%.]+") do table.insert(segments, segment) end | |||
if #segments == 0 then return nil end | |||
if #segments == 1 and value == nil then return nil end | |||
return segments, value | |||
end | |||
-- Returns true if a record matches at least one of the given properties. A | |||
-- property may take any of four forms: | |||
-- "Key" the key exists anywhere in the record with a meaningful | |||
-- value, or the string appears as a value anywhere | |||
-- "Key:Value" the key exists anywhere and its own value equals Value, | |||
-- compared case-insensitively | |||
-- "A.B.C" that exact nested chain exists with a meaningful value | |||
-- "A.B.C:Value" that chain exists and ends in Value | |||
-- Only the first segment of a path is searched for; the rest are followed one | |||
-- step at a time, so a value nested deeper under the key does not match. A | |||
-- structured target that resolves to nothing falls back to plain matching, so a | |||
-- value that happens to contain a dot ("38.1 50.8") still works as a target. | |||
-- Public so other modules (e.g. Module:AbilityTable membership) can reuse it. | -- Public so other modules (e.g. Module:AbilityTable membership) can reuse it. | ||
function p.entity_matches(record, properties) | function p.entity_matches(record, properties) | ||
for _, prop in ipairs(properties) do | for _, prop in ipairs(properties) do | ||
if record_matches_prop(record, prop) then | local segments, value = parse_target(prop) | ||
if segments and record_matches_path(record, segments, value) then | |||
return true | |||
elseif record_matches_prop(record, prop) then | |||
return true | return true | ||
end | end | ||
| Line 77: | Line 186: | ||
-- Returns all active entities from a dataset that match at least one of the | -- Returns all active entities from a dataset that match at least one of the | ||
-- given properties. A property may be | -- given properties. A property may be a key name (the property exists with a | ||
-- non-zero value), a plain string value anywhere in the record (e.g. "melee" | |||
-- matching Scale.Type = "melee"), a "Key:Value" pair, or a dotted path with an | |||
-- optional value ("Scale.Type:cooldown"). See p.entity_matches for details. | |||
-- @param dataset string one of the GameData.Dataset constants | -- @param dataset string one of the GameData.Dataset constants | ||
-- @param properties table array of property strings | -- @param properties table array of property strings | ||