Module:AbilityTable: Difference between revisionsGive feedback
Jump to navigation
Jump to search
Fix comment: membership predicate is consumed by Module:AbilityHints (with help from vergir-bot LLM) |
Refactor logic so abilitytables now correctly account for subabilities (Sinclair's Bolt, Pocket's Cloak, Rem's Tag Along etc). Ability Hints will break for a minute |
||
| Line 7: | Line 7: | ||
local p = {} | local p = {} | ||
local extra_columns = { | local extra_columns = { | ||
| Line 69: | Line 63: | ||
extra["Notes"] = extra["Notes"] and (extra["Notes"] .. "; " .. human_note) or human_note | extra["Notes"] = extra["Notes"] and (extra["Notes"] .. "; " .. human_note) or human_note | ||
end | end | ||
end | |||
-- ============================================================ | |||
-- Ability views | |||
-- | |||
-- In-game Ability may containe 1 or more records in AbilityData.json | |||
-- Example: Vexing Bolt is 2 records (Launch Bolt + Redirect Bolt). | |||
-- "Ability View" is a main-record + any relevant sub-ability records. | |||
-- ============================================================ | |||
local _views = nil -- card key -> { view = record, heroName = ..., abilityNum = ... } | |||
local _order = nil -- array of card keys, sorted by hero then slot | |||
local function build_views() | |||
if _views then return _views, _order end | |||
_views, _order = {}, {} | |||
local hero_data = mw.loadJsonData("Data:HeroData.json") | |||
local active_heroes = {} | |||
for _, hero in pairs(hero_data) do | |||
if type(hero) == "table" | |||
and hero["IsDisabled"] == false | |||
and type(hero["Name"]) == "string" | |||
and (hero["IsSelectable"] == nil or hero["IsSelectable"] ~= false) | |||
then | |||
active_heroes[hero["Name"]] = true | |||
end | |||
end | |||
local ability_data = mw.loadJsonData(GameData.Dataset.ABILITIES) | |||
local cards = mw.loadJsonData("Data:AbilityCards.json") | |||
local card_of = {} | |||
for _, hero_entry in pairs(cards) do | |||
if type(hero_entry) == "table" then | |||
local hero_name = hero_entry["Name"] | |||
if hero_name and active_heroes[hero_name] then | |||
for slot_str, ability in pairs(hero_entry) do | |||
local slot_num = tonumber(slot_str) | |||
local key = type(ability) == "table" and ability["Key"] or nil | |||
if slot_num and key and ability_data[key] then | |||
card_of[key] = { heroName = hero_name, abilityNum = slot_num } | |||
end | |||
end | |||
end | |||
end | |||
end | |||
-- Attach each non-card record to the longest card key it prefixes. | |||
local subs = {} | |||
for key, record in pairs(ability_data) do | |||
if not card_of[key] and record["IsDisabled"] ~= true then | |||
local parent = nil | |||
for card_key in pairs(card_of) do | |||
if key:sub(1, #card_key + 1) == card_key .. "_" | |||
and (parent == nil or #card_key > #parent) | |||
then | |||
parent = card_key | |||
end | |||
end | |||
if parent then | |||
subs[parent] = subs[parent] or {} | |||
table.insert(subs[parent], record) | |||
end | |||
end | |||
end | |||
for key, info in pairs(card_of) do | |||
local view = {} | |||
for k, v in pairs(ability_data[key]) do view[k] = v end | |||
view["_subabilities"] = subs[key] | |||
_views[key] = { view = view, heroName = info.heroName, abilityNum = info.abilityNum } | |||
table.insert(_order, key) | |||
end | |||
table.sort(_order, function(a, b) | |||
local ia, ib = _views[a], _views[b] | |||
if ia.heroName ~= ib.heroName then return ia.heroName < ib.heroName end | |||
return ia.abilityNum < ib.abilityNum | |||
end) | |||
return _views, _order | |||
end | end | ||
-- ============================================================ | -- ============================================================ | ||
-- Membership | -- Membership | ||
-- | -- Pieces of code that help in telling "is ability X a member of list Y". | ||
-- | -- main function is: p.is_member() | ||
-- ============================================================ | -- ============================================================ | ||
| Line 97: | Line 173: | ||
end | end | ||
-- Public: does | -- Public: does ability `ability_key` belong to list `stat`? | ||
function p.is_member(stat, ability_key, ability_num) | |||
function p.is_member(stat, | |||
local props = Lists.lists[stat] | local props = Lists.lists[stat] | ||
if not props then return false end | if not props then return false end | ||
local views = build_views() | |||
local entry = views[ability_key] | |||
local view = entry and entry.view | |||
if not view then | |||
view = mw.loadJsonData(GameData.Dataset.ABILITIES)[ability_key] | |||
if not view then return false end | |||
end | |||
end | end | ||
local name = view["Name"] | |||
if name and excluded_set(stat)[name] then return false end | |||
ability_num = ability_num or (entry and entry.abilityNum) | |||
if not special_ok(stat, view, ability_num) then return false end | |||
return GameData.entity_matches(view, props) | |||
end | end | ||
| Line 191: | Line 215: | ||
local compact = mw.text.trim(frame.args["compact"] or "") ~= "" | local compact = mw.text.trim(frame.args["compact"] or "") ~= "" | ||
local collapsed = mw.text.trim(frame.args["collapsed"] or ""):lower() | local collapsed = mw.text.trim(frame.args["collapsed"] or ""):lower() | ||
local | local views, order = build_views() | ||
local spec = extra_columns[stat] | local spec = extra_columns[stat] | ||
local headers = spec and spec.headers or {} | local headers = spec and spec.headers or {} | ||
| Line 200: | Line 224: | ||
local stat_notes = Notes[stat] | local stat_notes = Notes[stat] | ||
local | local filtered = {} | ||
for _, key in ipairs(order) do | |||
if p.is_member(stat, key) then | |||
table.insert(filtered, key) | |||
end | |||
end | |||
if #filtered == 0 then | if #filtered == 0 then | ||
return "No abilities found for list: " .. stat | return "No abilities found for list: " .. stat | ||
end | end | ||
local lists_url = mw.uri.fullUrl("Module:AbilityTable/Lists") | local lists_url = mw.uri.fullUrl("Module:AbilityTable/Lists") | ||
| Line 232: | Line 250: | ||
local hero_order = {} | local hero_order = {} | ||
local hero_abilities = {} | local hero_abilities = {} | ||
for _, | for _, key in ipairs(filtered) do | ||
local | local hero = views[key].heroName | ||
if not hero_abilities[hero] then | if not hero_abilities[hero] then | ||
table.insert(hero_order, hero) | table.insert(hero_order, hero) | ||
hero_abilities[hero] = {} | hero_abilities[hero] = {} | ||
end | end | ||
table.insert(hero_abilities[hero], | table.insert(hero_abilities[hero], views[key].view) | ||
end | end | ||
| Line 298: | Line 315: | ||
table.insert(out, header_row) | table.insert(out, header_row) | ||
for _, | for _, key in ipairs(filtered) do | ||
local | local entry = views[key] | ||
local ability = entry.view | |||
local hero_cell | local hero_cell = frame:expandTemplate{ title = "HeroIcon", args = { entry.heroName } } | ||
local ability_cell = frame:expandTemplate{ title = "AbilityIcon", args = { ability["Name"] } } | local ability_cell = frame:expandTemplate{ title = "AbilityIcon", args = { ability["Name"] } } | ||