Module:AbilityTable: Difference between revisionsGive feedback
Add debuff stat with Effects/Duration columns, manual_entries mechanism, prop_to_effect map (with help from vergir-bot LLM) |
Add hero field to manual_entries; add DebuffAccuracy/TimeSlowDuration/LateCheckoutStun props; resolve manual entry heroes via name lookup (with help from vergir-bot LLM) |
||
| Line 11: | Line 11: | ||
"PetrifyDuration", "HexDuration", "ImmobilizeDuration", "LiftDuration", | "PetrifyDuration", "HexDuration", "ImmobilizeDuration", "LiftDuration", | ||
"SlowPercent", "DebuffDuration", "HoldInPlaceDuration", | "SlowPercent", "DebuffDuration", "HoldInPlaceDuration", | ||
"DebuffAccuracy", "TimeSlowDuration", "LateCheckoutStun", | |||
}, | }, | ||
} | } | ||
| Line 24: | Line 25: | ||
} | } | ||
-- Manually specified entries for abilities | -- Manually specified entries for abilities whose effects cannot be inferred from | ||
-- data alone (e.g. engine-side CC with no matching data fields). | |||
-- Each entry: | -- Each entry requires: | ||
-- hero = "Hero Name" (as it appears on the wiki, used for HeroIcon) | |||
-- duration nil renders as "—" | -- effects = { "EffectName", ... } (supports "Effect (TN)" for upgrade-only) | ||
-- duration = <number or nil> (nil renders as "—") | |||
local manual_entries = { | local manual_entries = { | ||
["debuff"] = { | ["debuff"] = { | ||
["Combo"] = { | ["Combo"] = { | ||
hero = "Mo & Krill", | |||
effects = { "Stun" }, | effects = { "Stun" }, | ||
duration = 2.4, | duration = 2.4, | ||
}, | }, | ||
["Sand Blast"] = { | ["Sand Blast"] = { | ||
hero = "Mo & Krill", | |||
effects = { "Disarm" }, | effects = { "Disarm" }, | ||
duration = 2.5, | duration = 2.5, | ||
}, | }, | ||
["Spirit Lasso"] = { | ["Spirit Lasso"] = { | ||
hero = "Holliday", | |||
effects = { "Stun" }, | effects = { "Stun" }, | ||
duration = 2.25, | duration = 2.25, | ||
| Line 140: | Line 145: | ||
end | end | ||
local function find_base_melee_scale(ability) | local function find_base_melee_scale(ability) | ||
for _, v in pairs(ability) do | for _, v in pairs(ability) do | ||
| Line 158: | Line 161: | ||
end | end | ||
local function build_melee_upgrade_notes(ability, base_melee_scale) | local function build_melee_upgrade_notes(ability, base_melee_scale) | ||
local upgrades = ability["Upgrades"] | local upgrades = ability["Upgrades"] | ||
if type(upgrades) ~= "table" then return {} end | if type(upgrades) ~= "table" then return {} end | ||
local notes = {} | local notes = {} | ||
for i, tier in ipairs(upgrades) do | for i, tier in ipairs(upgrades) do | ||
if type(tier) == "table" then | if type(tier) == "table" then | ||
| Line 175: | Line 173: | ||
local stype = scale["Type"] | local stype = scale["Type"] | ||
local delta = scale["Value"] | local delta = scale["Value"] | ||
if stype == "heavy_melee" and delta > 0 then | if stype == "heavy_melee" and delta > 0 then | ||
table.insert(notes, "T" .. i .. " upgrade changes scaling to ×" | table.insert(notes, "T" .. i .. " upgrade changes scaling to ×" | ||
.. delta .. " of Heavy Melee damage.") | .. delta .. " of Heavy Melee damage.") | ||
elseif stype == "melee" and delta ~= 0 and base_melee_scale then | elseif stype == "melee" and delta ~= 0 and base_melee_scale then | ||
local | local final = math.floor((base_melee_scale + delta) * 1000 + 0.5) / 1000 | ||
table.insert(notes, "T" .. i .. " upgrade adds ×" | table.insert(notes, "T" .. i .. " upgrade adds ×" | ||
.. delta .. " scaling (total ×" .. final .. ").") | .. delta .. " scaling (total ×" .. final .. ").") | ||
| Line 190: | Line 186: | ||
end | end | ||
end | end | ||
return notes | return notes | ||
end | end | ||
| Line 196: | Line 191: | ||
local function melee_cells(ability) | local function melee_cells(ability) | ||
local cells = {} | local cells = {} | ||
local base_scale = find_base_melee_scale(ability) | local base_scale = find_base_melee_scale(ability) | ||
if base_scale then | if base_scale then cells["Light Melee Scaling"] = "×" .. base_scale end | ||
local upgrade_notes = build_melee_upgrade_notes(ability, base_scale) | local upgrade_notes = build_melee_upgrade_notes(ability, base_scale) | ||
if #upgrade_notes > 0 then | if #upgrade_notes > 0 then cells["Notes"] = table.concat(upgrade_notes, " ") end | ||
return cells | return cells | ||
end | end | ||
local function find_base_duration(ability) | local function find_base_duration(ability) | ||
for _, field in ipairs({ "VenomDuration", "DebuffDuration", "AbilityDuration", "BurnDuration" }) do | for _, field in ipairs({ "VenomDuration", "DebuffDuration", "AbilityDuration", "BurnDuration" }) do | ||
local v = num_val(ability[field]) | local v = num_val(ability[field]) | ||
if v and v > 0 then | if v and v > 0 then return v, field end | ||
end | end | ||
return nil, nil | return nil, nil | ||
| Line 225: | Line 209: | ||
local cells = {} | local cells = {} | ||
local notes = {} | local notes = {} | ||
local base_dur, dur_field = find_base_duration(ability) | local base_dur, dur_field = find_base_duration(ability) | ||
local first_pct | local first_pct, first_tier, first_is_disable = nil, nil, false | ||
local base_receive = num_val(ability["HealAmpReceivePenaltyPercent"]) | local base_receive = num_val(ability["HealAmpReceivePenaltyPercent"]) | ||
local base_regen = num_val(ability["HealAmpRegenPenaltyPercent"]) | local base_regen = num_val(ability["HealAmpRegenPenaltyPercent"]) | ||
local base_pct_raw = base_receive or base_regen | local base_pct_raw = base_receive or base_regen | ||
if base_pct_raw and base_pct_raw ~= 0 then | if base_pct_raw and base_pct_raw ~= 0 then | ||
first_pct | first_pct = math.abs(base_pct_raw) | ||
elseif ability["DisableHealing"] and ability["DisableHealing"] ~= 0 then | elseif ability["DisableHealing"] and ability["DisableHealing"] ~= 0 then | ||
first_is_disable = true | first_is_disable = true | ||
else | else | ||
local upgrades = ability["Upgrades"] | local upgrades = ability["Upgrades"] | ||
| Line 246: | Line 224: | ||
for i, tier in ipairs(upgrades) do | for i, tier in ipairs(upgrades) do | ||
if type(tier) == "table" then | if type(tier) == "table" then | ||
local | local up_raw = num_val(tier["HealAmpReceivePenaltyPercent"]) | ||
or num_val(tier["HealAmpRegenPenaltyPercent"]) | |||
if up_raw and up_raw ~= 0 then | if up_raw and up_raw ~= 0 then | ||
first_pct | first_pct, first_tier = math.abs(up_raw), i | ||
break | break | ||
elseif tier["DisableHealing"] and tier["DisableHealing"] ~= 0 then | elseif tier["DisableHealing"] and tier["DisableHealing"] ~= 0 then | ||
first_is_disable = true | first_is_disable, first_tier = true, i | ||
break | break | ||
end | end | ||
| Line 264: | Line 239: | ||
if first_is_disable then | if first_is_disable then | ||
cells["Heal Reduction"] = "100%" .. (first_tier and " (T" .. first_tier .. ")" or "") | |||
elseif first_pct then | elseif first_pct then | ||
cells["Heal Reduction"] = tostring(first_pct) .. "%" .. (first_tier and " (T" .. first_tier .. ")" or "") | |||
end | end | ||
if base_dur then cells["Duration"] = tostring(base_dur) end | |||
local upgrades = ability["Upgrades"] | local upgrades = ability["Upgrades"] | ||
if type(upgrades) == "table" then | if type(upgrades) == "table" then | ||
local running_pct | local running_pct, running_dur = first_pct, base_dur | ||
local first_pct_seen = (first_tier == nil) | local first_pct_seen = (first_tier == nil) | ||
for i, tier in ipairs(upgrades) do | for i, tier in ipairs(upgrades) do | ||
if type(tier) == "table" then | if type(tier) == "table" then | ||
local tier_notes = {} | local tier_notes = {} | ||
local pct_delta = num_val(tier["HealAmpReceivePenaltyPercent"]) | |||
local | or num_val(tier["HealAmpRegenPenaltyPercent"]) | ||
if first_tier == i then | |||
first_pct_seen = true | first_pct_seen = true | ||
if first_pct then running_pct = first_pct end | if first_pct then running_pct = first_pct end | ||
elseif first_pct_seen and pct_delta and pct_delta ~= 0 then | elseif first_pct_seen and pct_delta and pct_delta ~= 0 then | ||
local delta_abs = math.abs(pct_delta) | local delta_abs = math.abs(pct_delta) | ||
local total | local total = (running_pct or 0) + delta_abs | ||
table.insert(tier_notes, "T" .. i .. " upgrade increases heal reduction by " | table.insert(tier_notes, "T" .. i .. " upgrade increases heal reduction by " | ||
.. delta_abs .. "% (total " .. total .. "%).") | .. delta_abs .. "% (total " .. total .. "%).") | ||
running_pct = total | running_pct = total | ||
end | end | ||
if dur_field then | if dur_field then | ||
local dur_delta = num_val(tier[dur_field]) | local dur_delta = num_val(tier[dur_field]) | ||
if dur_delta and dur_delta ~= 0 and running_dur then | if dur_delta and dur_delta ~= 0 and running_dur then | ||
local total_dur = running_dur + dur_delta | local total_dur = running_dur + dur_delta | ||
table.insert(tier_notes, | table.insert(tier_notes, "T" .. i .. " upgrade " | ||
.. (dur_delta > 0 and "increases" or "decreases") | .. (dur_delta > 0 and "increases" or "decreases") | ||
.. " duration by " .. math.abs(dur_delta) | .. " duration by " .. math.abs(dur_delta) | ||
| Line 315: | Line 275: | ||
end | end | ||
end | end | ||
for _, n in ipairs(tier_notes) do table.insert(notes, n) end | |||
for _, n in ipairs(tier_notes) do | |||
end | end | ||
end | end | ||
end | end | ||
if #notes > 0 then | if #notes > 0 then cells["Notes"] = table.concat(notes, " ") end | ||
return cells | return cells | ||
end | end | ||
-- Maps AbilityData property names to human-readable effect names for the debuff table. | -- Maps AbilityData property names to human-readable effect names for the debuff table. | ||
-- | -- DebuffDuration is absent: it signals "(See notes)" on Duration rather than a named effect. | ||
local debuff_prop_to_effect = { | local debuff_prop_to_effect = { | ||
StunDuration = "Stun", | StunDuration = "Stun", | ||
| Line 344: | Line 297: | ||
SlowPercent = "Slow", | SlowPercent = "Slow", | ||
HoldInPlaceDuration = "Stop", | HoldInPlaceDuration = "Stop", | ||
DebuffAccuracy = "Inaccuracy", | |||
TimeSlowDuration = "Time Slow", | |||
LateCheckoutStun = "Stun", | |||
} | } | ||
-- Duration fields | -- Duration fields for each CC prop (the field whose numeric value is the duration). | ||
-- | -- Props that are themselves duration fields map to themselves. | ||
local | -- Props that are flags or percentages map to a separate duration field. | ||
local debuff_prop_to_dur_field = { | |||
StunDuration = "StunDuration", | StunDuration = "StunDuration", | ||
SleepDuration = "SleepDuration", | SleepDuration = "SleepDuration", | ||
SilenceDuration = "SilenceDuration", | SilenceDuration = "SilenceDuration", | ||
SilenceDebuff = "SilenceDuration", | SilenceDebuff = "SilenceDuration", | ||
PetrifyDuration = "PetrifyDuration", | PetrifyDuration = "PetrifyDuration", | ||
HexDuration = "HexDuration", | HexDuration = "HexDuration", | ||
| Line 359: | Line 316: | ||
SlowPercent = "SlowDuration", | SlowPercent = "SlowDuration", | ||
HoldInPlaceDuration = "HoldInPlaceDuration", | HoldInPlaceDuration = "HoldInPlaceDuration", | ||
DebuffAccuracy = "AbilityDuration", | |||
TimeSlowDuration = "TimeSlowDuration", | |||
LateCheckoutStun = "LateCheckoutStun", | |||
} | } | ||
| Line 364: | Line 324: | ||
local cells = {} | local cells = {} | ||
-- | -- Manual entry takes full precedence. | ||
local manual = stat_manual and stat_manual[ability["Name"]] | local manual = stat_manual and stat_manual[ability["Name"]] | ||
if manual then | if manual then | ||
| Line 375: | Line 335: | ||
return cells | return cells | ||
end | end | ||
local CC_PROPS = { | local CC_PROPS = { | ||
"StunDuration", "SleepDuration", "SilenceDuration", "SilenceDebuff", | "StunDuration", "SleepDuration", "SilenceDuration", "SilenceDebuff", | ||
"PetrifyDuration", "HexDuration", "ImmobilizeDuration", "LiftDuration", | "PetrifyDuration", "HexDuration", "ImmobilizeDuration", "LiftDuration", | ||
"SlowPercent", "HoldInPlaceDuration", | "SlowPercent", "HoldInPlaceDuration", "DebuffAccuracy", | ||
"TimeSlowDuration", "LateCheckoutStun", | |||
} | } | ||
-- | local effects_seen = {} -- ordered list of { name, tier } | ||
local effects_set = {} -- effect name -> true, for dedup | |||
-- Base fields | |||
for _, prop in ipairs(CC_PROPS) do | for _, prop in ipairs(CC_PROPS) do | ||
local val = ability[prop] | local val = ability[prop] | ||
local effect = debuff_prop_to_effect[prop] | local effect = debuff_prop_to_effect[prop] | ||
if val and val ~= 0 and effect and not effects_set[effect] then | if val and val ~= 0 and effect and not effects_set[effect] then | ||
| Line 398: | Line 356: | ||
end | end | ||
-- | -- Upgrade fields | ||
local upgrades = ability["Upgrades"] | local upgrades = ability["Upgrades"] | ||
if type(upgrades) == "table" then | if type(upgrades) == "table" then | ||
| Line 404: | Line 362: | ||
if type(tier) == "table" then | if type(tier) == "table" then | ||
for _, prop in ipairs(CC_PROPS) do | for _, prop in ipairs(CC_PROPS) do | ||
local val = tier[prop] | local val = tier[prop] | ||
local effect = debuff_prop_to_effect[prop] | local effect = debuff_prop_to_effect[prop] | ||
if val and val ~= 0 and effect and not effects_set[effect] then | if val and val ~= 0 and effect and not effects_set[effect] then | ||
| Line 415: | Line 373: | ||
end | end | ||
-- | -- DebuffDuration: present anywhere → Duration shows "(See notes)" | ||
local has_debuff_dur = (ability["DebuffDuration"] and ability["DebuffDuration"] ~= 0) | local has_debuff_dur = (ability["DebuffDuration"] and ability["DebuffDuration"] ~= 0) | ||
if not has_debuff_dur and type(upgrades) == "table" then | if not has_debuff_dur and type(upgrades) == "table" then | ||
| Line 426: | Line 384: | ||
end | end | ||
-- | -- Effects cell | ||
local effect_parts = {} | local effect_parts = {} | ||
for _, e in ipairs(effects_seen) do | for _, e in ipairs(effects_seen) do | ||
table.insert(effect_parts, e.tier and (e.name .. " (T" .. e.tier .. ")") or e.name) | |||
end | end | ||
if #effect_parts > 0 then | if #effect_parts > 0 then | ||
| Line 439: | Line 393: | ||
end | end | ||
-- | -- Duration cell: collect distinct non-DebuffDuration duration fields (base or upgrade) | ||
local DUR_FIELDS = { | local DUR_FIELDS = { | ||
"StunDuration", "SleepDuration", "SilenceDuration", "PetrifyDuration", | "StunDuration", "SleepDuration", "SilenceDuration", "PetrifyDuration", | ||
"HexDuration", "ImmobilizeDuration", "LiftDuration", "SlowDuration", | "HexDuration", "ImmobilizeDuration", "LiftDuration", "SlowDuration", | ||
"HoldInPlaceDuration", | "HoldInPlaceDuration", "AbilityDuration", "TimeSlowDuration", "LateCheckoutStun", | ||
} | } | ||
local dur_found = {} | |||
for _, f in ipairs(DUR_FIELDS) do | for _, f in ipairs(DUR_FIELDS) do | ||
local val = num_val(ability[f]) | local val = num_val(ability[f]) | ||
if val and val > 0 then | if val and val > 0 then dur_found[f] = val end | ||
end | end | ||
if type(upgrades) == "table" then | if type(upgrades) == "table" then | ||
for _, tier in ipairs(upgrades) do | for _, tier in ipairs(upgrades) do | ||
if type(tier) == "table" then | if type(tier) == "table" then | ||
for _, f in ipairs(DUR_FIELDS) do | for _, f in ipairs(DUR_FIELDS) do | ||
if not | if not dur_found[f] then | ||
local val = num_val(tier[f]) | local val = num_val(tier[f]) | ||
if val and val > 0 then | if val and val > 0 then dur_found[f] = val end | ||
end | end | ||
end | end | ||
| Line 474: | Line 417: | ||
end | end | ||
-- AbilityDuration is only meaningful as a duration source when DebuffAccuracy is the | |||
local | -- sole CC prop (Call Bell) — otherwise it's too generic. Remove it unless that's the case. | ||
local sole_cc_is_accuracy = (effects_set["Inaccuracy"] and not effects_set["Slow"] | |||
and not effects_set["Stun"] and not effects_set["Sleep"] | |||
and not effects_set["Silence"] and not effects_set["Petrify"] | |||
and not effects_set["Hex"] and not effects_set["Root"] | |||
and not effects_set["Lift"] and not effects_set["Stop"] | |||
and not effects_set["Time Slow"]) | |||
if not sole_cc_is_accuracy then | |||
dur_found["AbilityDuration"] = nil | |||
end | end | ||
if | local count, sole_val = 0, nil | ||
cells["Duration"] = tostring( | for _, val in pairs(dur_found) do count = count + 1; sole_val = val end | ||
elseif | |||
if count == 1 then | |||
cells["Duration"] = tostring(sole_val) | |||
elseif count > 1 or has_debuff_dur then | |||
cells["Duration"] = "(See notes)" | cells["Duration"] = "(See notes)" | ||
end | end | ||
return cells | return cells | ||
| Line 512: | Line 462: | ||
-- ============================================================ | -- ============================================================ | ||
-- Hero lookup (ability key -> { heroName, abilityNum }) | -- Hero lookup (ability key -> { heroName, abilityNum }) | ||
-- Also builds a name-keyed lookup for manual entries. | |||
-- ============================================================ | -- ============================================================ | ||
local _hero_lookup = nil | local _hero_lookup = nil -- key -> { heroName, abilityNum } | ||
local _name_lookup = nil -- name -> { heroName, abilityNum } | |||
local function build_hero_lookup() | local function build_hero_lookup() | ||
if _hero_lookup then return _hero_lookup end | if _hero_lookup then return _hero_lookup, _name_lookup end | ||
_hero_lookup = {} | _hero_lookup = {} | ||
_name_lookup = {} | |||
local hero_data = mw.loadJsonData("Data:HeroData.json") | local hero_data = mw.loadJsonData("Data:HeroData.json") | ||
| Line 539: | Line 492: | ||
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" | if slot_num and type(ability) == "table" then | ||
local info = { heroName = hero_name, abilityNum = slot_num } | |||
if ability["Key"] then | |||
_hero_lookup[ability["Key"]] = info | |||
end | |||
if ability["Name"] then | |||
_name_lookup[ability["Name"]] = info | |||
end | |||
end | end | ||
end | end | ||
| Line 550: | Line 506: | ||
end | end | ||
return _hero_lookup | return _hero_lookup, _name_lookup | ||
end | end | ||
| Line 565: | Line 521: | ||
end | end | ||
local hide_set = {} | local hide_set = {} | ||
local hide_param = mw.text.trim(frame.args["hide"] or "") | local hide_param = mw.text.trim(frame.args["hide"] or "") | ||
if hide_param ~= "" then | if hide_param ~= "" then | ||
for col in hide_param:gmatch("[^,]+") do | for col in hide_param:gmatch("[^,]+") do hide_set[mw.text.trim(col)] = true end | ||
end | end | ||
local lookup | local lookup, name_lookup = build_hero_lookup() | ||
local spec | local spec = extra_columns[stat] | ||
local headers | local headers = spec and spec.headers or {} | ||
local stat_notes = human_notes[stat] | local stat_notes = human_notes[stat] | ||
local stat_manual = manual_entries[stat] | local stat_manual = manual_entries[stat] | ||
local get_cells | local get_cells | ||
if spec then | if spec then | ||
| Line 596: | Line 548: | ||
-- Keep only abilities belonging to active, released heroes | -- Keep only abilities belonging to active, released heroes | ||
local filtered = {} | local filtered = {} | ||
local filtered_names = {} | |||
for _, ability in ipairs(abilities) do | for _, ability in ipairs(abilities) do | ||
local key = ability["Key"] | local key = ability["Key"] | ||
if key and lookup[key] then | if key and lookup[key] then | ||
table.insert(filtered, ability) | table.insert(filtered, ability) | ||
filtered_names[ability["Name"]] = true | |||
end | end | ||
end | end | ||
-- | -- Inject manual entries not already covered by prop-matching | ||
if stat_manual then | if stat_manual then | ||
for name, entry in pairs(stat_manual) do | |||
for name, | |||
if not filtered_names[name] then | if not filtered_names[name] then | ||
-- | -- Look up hero and slot via the name-keyed lookup first, | ||
-- then fall back to the hero field on the entry. | |||
-- | local info = name_lookup[name] | ||
table.insert(filtered, { | |||
Name = name, | |||
table.insert(filtered, { Name = name, Key = "__manual__" . | Key = "__manual__", | ||
_manualInfo = info or { heroName = entry.hero, abilityNum = 9999 }, | |||
}) | |||
end | end | ||
end | end | ||
| Line 625: | Line 577: | ||
end | end | ||
-- Sort by hero name, then ability slot number | -- Sort by hero name, then ability slot number | ||
table.sort(filtered, function(a, b) | table.sort(filtered, function(a, b) | ||
local ia = lookup[a["Key"]] | local ia = lookup[a["Key"]] or a._manualInfo | ||
local ib = lookup[b["Key"]] | local ib = lookup[b["Key"]] or b._manualInfo | ||
if not ia and not ib then return (a["Name"] or "") < (b["Name"] or "") end | if not ia and not ib then return (a["Name"] or "") < (b["Name"] or "") end | ||
if not ia then return false end | if not ia then return false end | ||
if not ib then return true end | if not ib then return true end | ||
if ia.heroName ~= ib.heroName then | if ia.heroName ~= ib.heroName then return ia.heroName < ib.heroName end | ||
return ia.abilityNum < ib.abilityNum | return ia.abilityNum < ib.abilityNum | ||
end) | end) | ||
-- Determine final | -- Determine final headers | ||
local has_notes_header = false | local has_notes_header = false | ||
local final_headers = {} | local final_headers = {} | ||
| Line 650: | Line 599: | ||
end | end | ||
local needs_notes = (has_notes_header or stat_notes ~= nil) and not hide_set["Notes"] | local needs_notes = (has_notes_header or stat_notes ~= nil) and not hide_set["Notes"] | ||
if needs_notes then | if needs_notes then table.insert(final_headers, "Notes") end | ||
-- Disclaimer | -- Disclaimer | ||
| Line 664: | Line 611: | ||
) | ) | ||
local out = {} | local out = {} | ||
table.insert(out, disclaimer) | table.insert(out, disclaimer) | ||
table.insert(out, '{| class="wikitable sortable ability-table"') | table.insert(out, '{| class="wikitable sortable ability-table"') | ||
local | local header_row = '! class="ability-table-col-hero" | Hero' | ||
.. ' !! class="ability-table-col-ability" | Ability' | |||
for _, h in ipairs(final_headers) do | for _, h in ipairs(final_headers) do | ||
header_row = header_row .. ' !! class="ability-table-col-' | |||
.. h:lower():gsub("%s+", "-") .. '" | ' .. h | |||
end | end | ||
table.insert(out, | table.insert(out, header_row) | ||
for _, ability in ipairs(filtered) do | for _, ability in ipairs(filtered) do | ||
local info = lookup[ability["Key"]] | local info = lookup[ability["Key"]] or ability._manualInfo | ||
local hero_cell, ability_cell | local hero_cell, ability_cell | ||
if info then | if info and info.heroName then | ||
hero_cell = frame:expandTemplate{ | hero_cell = frame:expandTemplate{ title = "HeroIcon", args = { info.heroName } } | ||
else | else | ||
hero_cell = "—" | |||
hero_cell | |||
end | end | ||
ability_cell = frame:expandTemplate{ title = "AbilityIcon", args = { ability["Name"] } } | |||
local extra = get_cells(ability) | local extra = get_cells(ability) | ||
if stat_notes and stat_notes[ability["Name"]] then | if stat_notes and stat_notes[ability["Name"]] then | ||
local human = stat_notes[ability["Name"]] | local human = stat_notes[ability["Name"]] | ||
| Line 707: | Line 641: | ||
end | end | ||
local | local row = '| class="ability-table-cell-hero" | ' .. hero_cell | ||
.. ' || class="ability-table-cell-ability" | ' .. ability_cell | |||
for _, header in ipairs(final_headers) do | for _, header in ipairs(final_headers) do | ||
local value = extra[header] | local value = extra[header] | ||
| Line 715: | Line 649: | ||
if value ~= nil then | if value ~= nil then | ||
local suffix = column_suffix[header] or "" | local suffix = column_suffix[header] or "" | ||
content = (type(value) == "string" and value:sub(1,1) == "(") | |||
and value | |||
or (value .. suffix) | |||
else | else | ||
content = "—" | content = "—" | ||
end | end | ||
row = row .. ' || class="' .. cls .. '" | ' .. content | |||
end | end | ||
table.insert(out, "|-") | table.insert(out, "|-") | ||
table.insert(out, | table.insert(out, row) | ||
end | end | ||