Editing Module:AbilityTableGive feedback
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 | |||
-- - get_cells: function(ability) -> { ["Column Name"] = value } | |||
-- 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. | |||
-- | |||
-- 3. Optionally add entries to column_suffix to append a unit | |||
-- string (e.g. "s", "%") after values in specific columns. | |||
-- ============================================================ | |||
local p = {} | |||
local GameData = require("Module:GameData") | |||
local | -- Maps lowercase friendly stat names to internal AbilityData property keys. | ||
-- Used to filter which abilities appear in the table. | |||
local stat_to_props = { | |||
["charges"] = { "AbilityCharges" }, | |||
} | |||
local | -- Maps column header names to their display unit suffix. | ||
-- Applied automatically to all non-nil, non-"—" cell values in that column. | |||
local column_suffix = { | |||
["Time Between Charges"] = "s", | |||
} | |||
-- ============================================================ | |||
-- Charges: human-authored notes | |||
-- Add per-ability annotations here; they are appended after | |||
-- any auto-generated notes in the Notes column. | |||
-- Key is the ability's display Name from AbilityData. | |||
-- ============================================================ | |||
local charges_human_notes = { | |||
["Jar of Dead"] = "Charges by being near when anything dies.", | |||
[" | |||
} | } | ||
-- ============================================================ | -- ============================================================ | ||
-- | -- Shared analysis helpers | ||
-- ============================================================ | -- ============================================================ | ||
-- | -- Returns a numeric value from either a plain number or a { Value = n } object. | ||
local function num_val(v) | |||
local function | if type(v) == "number" then return v end | ||
if | if type(v) == "table" then return v["Value"] end | ||
return nil | |||
end | |||
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 | end | ||
return nil | |||
end | end | ||
-- | -- ============================================================ | ||
-- | -- Charges: analysis | ||
-- ============================================================ | |||
local | |||
if | -- Analyses a raw ability record for charge-related properties. | ||
-- Returns a structured record, or nil if the ability has no charge mechanic. | |||
if | local function analyze_charges(ability) | ||
local base_charges = num_val(ability["AbilityCharges"]) | |||
local base_cooldown = ability["AbilityCooldownBetweenCharge"] | |||
local is_conditional = ability["AbilityChargesConditionally"] ~= nil | |||
local has_base = base_charges ~= nil and base_charges > 0 | |||
local upgrade_info = find_in_upgrades(ability, "AbilityCharges") | |||
if not has_base and not upgrade_info then return nil end | |||
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 | 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 | ||
return { | |||
base_charges = has_base and base_charges or nil, | |||
upgrade_tier = upgrade_info and upgrade_info.tier or nil, | |||
upgrade_charges = upgrade_info and upgrade_info.value or nil, | |||
upgrade_cooldown = upgrade_cooldown, | |||
cooldown = cooldown, | |||
is_conditional = is_conditional, | |||
} | |||
end | end | ||
-- ============================================================ | -- ============================================================ | ||
-- | -- Extra columns per stat | ||
-- | -- ============================================================ | ||
local extra_columns = { | |||
["charges"] = { | |||
headers = { "Charges", "Time Between Charges", "Notes" }, | |||
get_cells = function(ability) | |||
local rec = analyze_charges(ability) | |||
if not rec then return {} end | |||
local cells = {} | |||
if rec.base_charges then | |||
cells["Charges"] = tostring(rec.base_charges) | |||
if rec.cooldown then | |||
cells["Time Between Charges"] = tostring(rec.cooldown) | |||
end | |||
end | |||
local note_parts = {} | |||
if rec.upgrade_tier then | |||
local n = rec.upgrade_charges or "?" | |||
local charge_str = n .. " charge" .. (n ~= 1 and "s" or "") | |||
if rec.base_charges then | |||
table.insert(note_parts, "+" .. charge_str .. " on T" .. rec.upgrade_tier .. " upgrade.") | |||
else | |||
local cd_str = rec.upgrade_cooldown and (rec.upgrade_cooldown .. "s") or "unknown" | |||
table.insert(note_parts, "Becomes charged on T" .. rec.upgrade_tier | |||
.. " upgrade with " .. charge_str | |||
.. " and " .. cd_str .. " time between charges.") | |||
end | |||
end | |||
if rec.is_conditional then | |||
table.insert(note_parts, "Has a conditional charge.") | |||
end | |||
local human = charges_human_notes[ability["Name"]] | |||
if human then table.insert(note_parts, human) end | |||
if #note_parts > 0 then | |||
cells["Notes"] = table.concat(note_parts, " ") | |||
end | |||
return cells | |||
end, | |||
}, | |||
} | |||
-- | -- ============================================================ | ||
-- 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 182: | ||
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 189: | ||
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 200: | ||
end | end | ||
return _hero_lookup | |||
end | end | ||
| Line 212: | Line 207: | ||
-- ============================================================ | -- ============================================================ | ||
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 | |||
local | |||
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) | |||
-- Render wikitable | |||
local out = {} | |||
local header_row = "! Hero !! Ability" | |||
if #headers > 0 then | |||
header_row = header_row .. " !! " .. table.concat(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 cells = { hero_cell, ability_cell } | |||
local extra = get_cells(ability) | local extra = get_cells(ability) | ||
for _, header in ipairs(headers) do | |||
local value = extra[header] | |||
if value ~= nil then | |||
local suffix = column_suffix[header] or "" | |||
table.insert(cells, value .. suffix) | |||
else | else | ||
table.insert(cells, "—") | |||
end | end | ||
end | end | ||
table.insert(out, "|-") | table.insert(out, "|-") | ||
table.insert(out, | table.insert(out, "| " .. table.concat(cells, " || ")) | ||
end | end | ||