Editing Module:AbilityTableGive feedback
Jump to navigation
Jump to search
The edit can be undone.
Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
| Latest revision | Your text | ||
| Line 1: | Line 1: | ||
-- Module:AbilityTables | |||
-- Renders sortable wikitables of abilities filtered by a named stat. | -- Renders sortable wikitables of abilities filtered by a named stat. | ||
-- Backing module for Template:AbilityTable. | |||
-- | |||
-- Usage on wiki pages: {{AbilityTable|charges}} | |||
-- | |||
-- ============================================================ | |||
-- ADDING A NEW STAT | |||
-- ============================================================ | |||
-- 1. Add an entry to stat_to_props mapping the friendly name | |||
-- (lowercase) to the internal AbilityData property keys that | |||
-- determine whether an ability appears in the table. | |||
-- | |||
-- 2. Optionally add a matching entry to extra_columns to show | |||
-- additional data columns beyond Hero and Ability. | |||
-- Copy the "charges" block as a template: | |||
-- - headers: ordered list of column header strings | |||
-- (do not include "Notes" — it is added automatically) | |||
-- - get_cells: function(ability) -> { ["Column Name"] = value } | |||
-- The ability is guaranteed to match the stat. | |||
-- Return nil or omit a key to show "—" in that cell. | |||
-- If no extra_columns entry is added, only Hero and Ability | |||
-- columns are shown (plus Notes if human notes exist). | |||
-- | |||
-- 3. Optionally add entries to column_suffix to append a unit | |||
-- string (e.g. "s", "%") after values in specific columns. | |||
-- | |||
-- 4. Optionally add human-authored notes to human_notes: | |||
-- human_notes["stat"]["Ability Name"] = "Note text." | |||
-- Notes are appended as a Notes column automatically. | |||
-- ============================================================ | |||
local p = {} | |||
local GameData = require("Module:GameData") | |||
local | -- Maps lowercase friendly stat names to internal AbilityData property keys. | ||
local stat_to_props = { | |||
["charges"] = { "AbilityCharges" }, | |||
} | |||
local | -- Maps column header names to their display unit suffix. | ||
local column_suffix = { | |||
["Time Between Charges"] = "s", | |||
} | |||
-- Human-authored notes, appended to the Notes column. | |||
-- Keyed by stat name, then by ability display Name. | |||
local human_notes = { | |||
["charges"] = { | ["charges"] = { | ||
["Jar of Dead"] = "Charges by being near when anything dies.", | |||
}, | }, | ||
} | } | ||
-- ============================================================ | -- ============================================================ | ||
-- | -- Shared helpers | ||
-- ============================================================ | |||
local function num_val(v) | |||
if type(v) == "number" then return v end | |||
if type(v) == "table" then return v["Value"] end | |||
return nil | |||
end | |||
-- Searches Upgrades[] for a field and returns { tier = N, value = V } at its | |||
-- first appearance, or nil if the field is absent from all upgrade tiers. | |||
local function find_in_upgrades(ability, field) | |||
local upgrades = ability["Upgrades"] | |||
if type(upgrades) ~= "table" then return nil end | |||
for i, tier in ipairs(upgrades) do | |||
if type(tier) == "table" and tier[field] ~= nil then | |||
return { tier = i, value = num_val(tier[field]) } | |||
end | |||
end | |||
return nil | |||
end | |||
-- ============================================================ | |||
-- Per-stat cell renderers | |||
-- Each function receives a guaranteed-matching ability record | |||
-- and returns { ["Column Name"] = value } for extra columns. | |||
-- Human notes and the Notes column are handled by the framework. | |||
-- ============================================================ | -- ============================================================ | ||
local function charges_cells(ability) | |||
local base_charges = num_val(ability["AbilityCharges"]) | |||
local | local base_cooldown = ability["AbilityCooldownBetweenCharge"] | ||
if | local has_base = base_charges ~= nil and base_charges > 0 | ||
local upgrade_info = find_in_upgrades(ability, "AbilityCharges") | |||
local cooldown, upgrade_cooldown | |||
if has_base then | |||
if type(base_cooldown) == "number" and base_cooldown > 0 then | |||
cooldown = base_cooldown | |||
else | |||
local cd = find_in_upgrades(ability, "AbilityCooldownBetweenCharge") | |||
if cd and cd.value and cd.value > 0 then cooldown = cd.value end | |||
end | |||
else | |||
local cd = find_in_upgrades(ability, "AbilityCooldownBetweenCharge") | |||
if cd and cd.value and cd.value > 0 then upgrade_cooldown = cd.value end | |||
end | end | ||
if | |||
local cells = {} | |||
if has_base then | |||
cells["Charges"] = tostring(base_charges) | |||
if cooldown then | |||
cells["Time Between Charges"] = tostring(cooldown) | |||
end | |||
end | end | ||
local note_parts = {} | |||
if upgrade_info then | |||
local n = upgrade_info.value or "?" | |||
local charge_str = n .. " charge" .. (n ~= 1 and "s" or "") | |||
if has_base then | |||
table.insert(note_parts, "+" .. charge_str .. " on T" .. upgrade_info.tier .. " upgrade.") | |||
else | |||
local cd_str = upgrade_cooldown and (upgrade_cooldown .. "s") or "unknown" | |||
table.insert(note_parts, "Becomes charged on T" .. upgrade_info.tier | |||
.. " upgrade with " .. charge_str | |||
.. " and " .. cd_str .. " time between charges.") | |||
end | end | ||
end | end | ||
if ability["AbilityChargesConditionally"] ~= nil then | |||
table.insert(note_parts, "Has a conditional charge.") | |||
end | |||
if #note_parts > 0 then | |||
cells["Notes"] = table.concat(note_parts, " ") | |||
end | |||
return cells | |||
end | end | ||
local extra_columns = { | |||
["charges"] = { | |||
headers = { "Charges", "Time Between Charges" }, | |||
get_cells = charges_cells, | |||
}, | |||
} | |||
-- ============================================================ | -- ============================================================ | ||
-- | -- Hero lookup (ability key -> { heroName, abilityNum }) | ||
-- ============================================================ | -- ============================================================ | ||
local | local _hero_lookup = nil | ||
local function | local function build_hero_lookup() | ||
if | if _hero_lookup then return _hero_lookup end | ||
_hero_lookup = {} | |||
local hero_data = mw.loadJsonData("Data:HeroData.json") | local hero_data = mw.loadJsonData("Data:HeroData.json") | ||
| Line 105: | Line 162: | ||
end | end | ||
local cards = mw.loadJsonData("Data:AbilityCards.json") | |||
local cards | |||
for _, hero_entry in pairs(cards) do | for _, hero_entry in pairs(cards) do | ||
if type(hero_entry) == "table" then | if type(hero_entry) == "table" then | ||
| Line 115: | Line 169: | ||
for slot_str, ability in pairs(hero_entry) do | for slot_str, ability in pairs(hero_entry) do | ||
local slot_num = tonumber(slot_str) | local slot_num = tonumber(slot_str) | ||
if slot_num and type(ability) == "table" and ability["Key"] then | |||
_hero_lookup[ability["Key"]] = { | |||
heroName = hero_name, | |||
abilityNum = slot_num, | |||
} | |||
end | end | ||
end | end | ||
| Line 124: | Line 180: | ||
end | end | ||
return _hero_lookup | |||
end | end | ||
| Line 212: | Line 187: | ||
-- ============================================================ | -- ============================================================ | ||
function p. | function p.abilityPropTable(frame) | ||
local stat = mw.text.trim(frame.args[1] or ""):lower() | local stat = mw.text.trim(frame.args[1] or ""):lower() | ||
local internal_keys = | local internal_keys = stat_to_props[stat] | ||
if not internal_keys then | if not internal_keys then | ||
return '<span class="error">AbilityTable: unknown | return '<span class="error">AbilityTable: unknown stat "' .. stat .. '".</span>' | ||
end | end | ||
local | local lookup = build_hero_lookup() | ||
local spec = extra_columns[stat] | |||
local headers = spec and spec.headers or {} | |||
local get_cells = spec and spec.get_cells or function() return {} end | |||
local stat_notes = human_notes[stat] | |||
local | local abilities = GameData.get_entities(GameData.Dataset.ABILITIES, internal_keys) | ||
-- Keep only abilities belonging to active, released heroes | |||
local filtered = {} | local filtered = {} | ||
for _, | for _, ability in ipairs(abilities) do | ||
if | local key = ability["Key"] | ||
table.insert(filtered, | if key and lookup[key] then | ||
table.insert(filtered, ability) | |||
end | end | ||
end | end | ||
if #filtered == 0 then | if #filtered == 0 then | ||
return "No abilities found for | return "No abilities found for stat: " .. stat | ||
end | end | ||
-- Sort by hero name, then ability slot number | |||
table.sort(filtered, function(a, b) | |||
local ia = lookup[a["Key"]] | |||
local ib = lookup[b["Key"]] | |||
if ia.heroName ~= ib.heroName then | |||
return ia.heroName < ib.heroName | |||
. | |||
end | end | ||
return ia.abilityNum < ib.abilityNum | |||
end) | |||
-- Determine whether a Notes column is needed: | |||
-- either the stat renderer produces notes, or human notes exist for this stat. | |||
local needs_notes = stat_notes ~= nil | |||
if not needs_notes then | |||
for _, ability in ipairs(filtered) do | |||
local extra = get_cells(ability) | |||
if extra["Notes"] then | |||
needs_notes = true | |||
break | |||
end | end | ||
end | end | ||
end | end | ||
-- Build final header list: declared headers + Notes if needed | |||
local final_headers = {} | local final_headers = {} | ||
for _, h in ipairs(headers) do | for _, h in ipairs(headers) do table.insert(final_headers, h) end | ||
if needs_notes then table.insert(final_headers, "Notes") end | if needs_notes then table.insert(final_headers, "Notes") end | ||
local | -- Render wikitable | ||
local out = {} | |||
local header_row = "! Hero !! Ability" | |||
if #final_headers > 0 then | |||
header_row = header_row .. " !! " .. table.concat(final_headers, " !! ") | |||
end | end | ||
table.insert(out, '{| class="wikitable sortable"') | |||
table.insert(out, '{| class=" | |||
table.insert(out, header_row) | table.insert(out, header_row) | ||
for _, ability in ipairs(filtered) do | |||
for _, | local info = lookup[ability["Key"]] | ||
local | |||
local hero_cell = frame:expandTemplate{ | |||
title = "HeroIcon", | |||
args = { info.heroName } | |||
} | |||
local ability_cell = frame:expandTemplate{ | local ability_cell = frame:expandTemplate{ | ||
title = "AbilityIcon", | title = "AbilityIcon", | ||
args = { ability["Name"] } | args = { ability["Name"] } | ||
} | } | ||
local extra = get_cells(ability | local cells = { hero_cell, ability_cell } | ||
local extra = get_cells(ability) | |||
-- Append human note to any renderer-generated note | |||
if stat_notes and stat_notes[ability["Name"]] then | |||
if | local human = stat_notes[ability["Name"]] | ||
local | extra["Notes"] = extra["Notes"] and (extra["Notes"] .. " " .. human) or human | ||
end | end | ||
for _, header in ipairs(final_headers) do | for _, header in ipairs(final_headers) do | ||
local value = extra[header] | local value = extra[header] | ||
local | if value ~= nil then | ||
local suffix = column_suffix[header] or "" | |||
table.insert(cells, value .. suffix) | |||
else | |||
table.insert(cells, "—") | |||
end | |||
end | end | ||
table.insert(out, "|-") | table.insert(out, "|-") | ||
table.insert(out, | table.insert(out, "| " .. table.concat(cells, " || ")) | ||
end | end | ||