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 friendly_to_internal 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) that returns a table of | |||
-- { ["Column Name"] = value } for one ability row. | |||
-- 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. | |||
-- ============================================================ | |||
local | local p = {} | ||
local GameData = require("Module:GameData") | |||
local GameData | |||
local | -- Maps lowercase friendly stat names to internal AbilityData property keys. | ||
-- Used to filter which abilities appear in the table. | |||
local friendly_to_internal = { | |||
["charges"] = { "AbilityCharges" }, | |||
} | |||
-- ============================================================ | |||
-- 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.", | |||
[" | |||
} | } | ||
-- ============================================================ | -- ============================================================ | ||
-- | -- Charges: analysis helpers | ||
-- Ported from Module:Interaction/Charges and Module:Interactions. | |||
-- ============================================================ | -- ============================================================ | ||
-- | -- 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 | |||
-- Returns the 1-based index of the first Upgrades tier that contains a field, | |||
-- or nil if the field never appears in any upgrade tier. | |||
local function upgrade_grants_tier(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 i | |||
end | |||
end | end | ||
return nil | |||
end | |||
-- Returns the numeric value of a field at its first appearance in Upgrades[]. | |||
local function upgrade_field_value(ability, field) | |||
local upgrades = ability["Upgrades"] | |||
if type(upgrades) ~= "table" then return nil end | |||
for _, tier in ipairs(upgrades) do | |||
if type(tier) == "table" and tier[field] ~= nil then | |||
return num_val(tier[field]) | |||
end | |||
end | end | ||
return nil | |||
end | end | ||
-- | -- Analyses a raw ability record for charge-related properties. | ||
-- | -- Returns a record table, or nil if the ability has no charge mechanic. | ||
local function analyze_charges(ability) | |||
local | local base_charges = num_val(ability["AbilityCharges"]) | ||
if | local base_cooldown = ability["AbilityCooldownBetweenCharge"] | ||
local is_conditional = ability["AbilityChargesConditionally"] ~= nil | |||
if | local has_base = base_charges ~= nil and base_charges > 0 | ||
local upgrade_tier = upgrade_grants_tier(ability, "AbilityCharges") | |||
local upgrade_charges = upgrade_tier and upgrade_field_value(ability, "AbilityCharges") or nil | |||
if not has_base and not upgrade_tier 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 v = upgrade_field_value(ability, "AbilityCooldownBetweenCharge") | |||
if v and v > 0 then cooldown = v end | |||
end | end | ||
elseif upgrade_tier then | |||
local v = upgrade_field_value(ability, "AbilityCooldownBetweenCharge") | |||
if v and v > 0 then upgrade_cooldown = v end | |||
end | end | ||
return { | |||
base_charges = has_base and base_charges or nil, | |||
upgrade_tier = upgrade_tier, | |||
upgrade_charges = upgrade_charges, | |||
upgrade_cooldown = upgrade_cooldown, | |||
cooldown = cooldown, | |||
is_conditional = is_conditional, | |||
} | |||
end | end | ||
-- ============================================================ | -- ============================================================ | ||
-- | -- Extra columns definition | ||
-- | -- ============================================================ | ||
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"] = rec.cooldown .. "s" | |||
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 186: | ||
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 193: | ||
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 204: | ||
end | end | ||
return _hero_lookup | |||
end | end | ||
| Line 212: | Line 211: | ||
-- ============================================================ | -- ============================================================ | ||
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 = friendly_to_internal[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 | local headers = spec and spec.headers or {} | ||
local get_cells = spec and spec.get_cells or function() return {} end | |||
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 | |||
table.insert(cells, extra[header] or "—") | |||
for _, header in ipairs( | |||
end | end | ||
table.insert(out, "|-") | table.insert(out, "|-") | ||
table.insert(out, | table.insert(out, "| " .. table.concat(cells, " || ")) | ||
end | end | ||