Editing Module:AbilityTable

Jump to navigation Jump to search
Warning: You are not logged in. Once you make an edit, a temporary account will be created for you. Learn more. Log in or create an account to continue receiving notifications after this account expires, and to access other features.
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:
-- Renders sortable wikitables of abilities filtered by a named stat.
-- Renders sortable wikitables of abilities filtered by a named stat.


local Lists     = require("Module:AbilityTable/Lists")
-- Maps lowercase friendly stat names to internal AbilityData property keys or values.
local Notes     = require("Module:AbilityTable/Notes")
local stat_to_props = {
local Renderers = require("Module:AbilityTable/ComplexRenderers")
     ["charges"]    = { "AbilityCharges" },
local GameData  = require("Module:GameData")
     ["melee"]      = { "melee", "heavy_melee" },
 
    ["wpndmg"]    = { "melee", "heavy_melee", "weapon_damage_increase" },
local p = {}
    ["healreduce"] = { "HealAmpReceivePenaltyPercent", "HealAmpRegenPenaltyPercent", "DisableHealing" },
}


local extra_columns = {
-- Editor-authored notes appended to the Notes column.
    ["barrier"] = {
local human_notes = {
        headers  = { "Barrier" },
        get_cells = Renderers.RenderBarrier,
    },
     ["charges"] = {
     ["charges"] = {
         headers  = { "Charges", "Time Between Charges", "Notes" },
         ["Jar of Dead"]  = "Charges by being near when anything dies.",
        get_cells = Renderers.RenderCharges,
     },
     },
     ["melee"] = {
     ["melee"] = {
         headers  = { "Base Damage", "Light Melee Scaling", "Notes" },
         ["Puddle Punch"] = "Can be parried.",
        get_cells = Renderers.RenderMelee,
    },
    ["heavymelee"] = {
        headers  = { "Base Damage", "Heavy Melee Scaling", "Notes" },
        get_cells = Renderers.RenderHeavyMelee,
    },
    ["scalingspirit"] = {
        headers  = { "Spirit Scaling", "Notes" },
        get_cells = Renderers.RenderSpiritScaling,
    },
    ["healreduce"] = {
        headers  = { "Heal Reduction", "Notes" },
        get_cells = Renderers.RenderHealReduce,
    },
    ["moveslow"] = {
        headers  = { "Move Slow", "Notes" },
        get_cells = Renderers.RenderMoveSlow,
    },
    ["dashslow"] = {
        headers  = { "Dash Slow", "Notes" },
        get_cells = Renderers.RenderDashSlow,
    },
    ["extradebuffresist"] = {
        headers  = { "Debuff Resist" },
        get_cells = Renderers.RenderExtraDebuffResist,
     },
     },
}
}
-- Unit strings appended to cell values by column name.
local column_suffix = {
    ["Time Between Charges"] = "s",
    ["Duration"]            = "s",
}
-- Actual code below
local p = {}
local GameData = require("Module:GameData")


-- ============================================================
-- ============================================================
-- Note enrichment
-- Shared helpers
-- ============================================================
-- ============================================================


-- Merges auto-generated and human notes for full-table mode.
local function num_val(v)
-- Capitalizes human note, joins with ". ", ensures trailing period.
     if type(v) == "number" then return v end
local function enrich_notes_full(extra, human_note)
     if type(v) == "table" then return v["Value"] end
     if human_note then
     return nil
        human_note = human_note:sub(1,1):upper() .. human_note:sub(2)
        extra["Notes"] = extra["Notes"] and (extra["Notes"] .. ". " .. human_note) or human_note
    end
     if extra["Notes"] and extra["Notes"]:sub(-1) ~= "." then
        extra["Notes"] = extra["Notes"] .. "."
     end
end
end


-- Merges auto-generated and human notes for compact mode.
-- Searches Upgrades[] for a field and returns { tier = N, value = V } at its
-- Decapitalizes human note (skips T{N} prefixes like "T1 slow"),
-- first appearance, or nil if the field is absent from all upgrade tiers.
-- joins with "; ", strips trailing period.
local function find_in_upgrades(ability, field)
local function enrich_notes_compact(extra, human_note)
    local upgrades = ability["Upgrades"]
     if human_note then
     if type(upgrades) ~= "table" then return nil end
        local first = human_note:sub(1,1)
    for i, tier in ipairs(upgrades) do
         if first:match("%u") and not human_note:sub(2,2):match("%d") then
         if type(tier) == "table" and tier[field] ~= nil then
             human_note = first:lower() .. human_note:sub(2)
             return { tier = i, value = num_val(tier[field]) }
         end
         end
        if human_note:sub(-1) == "." then human_note = human_note:sub(1, -2) end
        extra["Notes"] = extra["Notes"] and (extra["Notes"] .. "; " .. human_note) or human_note
     end
     end
    return nil
end
end


-- ============================================================
-- ============================================================
-- Ability views
-- Per-stat cell renderers
--
-- Each function receives a guaranteed-matching ability record
-- In-game Ability may containe 1 or more records in AbilityData.json
-- and returns { ["Column Name"] = value }.
-- Example: Vexing Bolt is 2 records (Launch Bolt + Redirect Bolt).
-- Human notes are appended by the framework, not here.
-- ============================================================


-- "Ability View" is a main-record + any relevant sub-ability records.
local function charges_cells(ability)
-- ============================================================
    local base_charges  = num_val(ability["AbilityCharges"])
    local base_cooldown = ability["AbilityCooldownBetweenCharge"]
    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
 
    local cells = {}
 
    if has_base then
        cells["Charges"] = tostring(base_charges)
        if cooldown then
            cells["Time Between Charges"] = tostring(cooldown)
        end
    end


local _views = nil  -- card key -> { view = record, heroName = ..., abilityNum = ... }
    local note_parts = {}
local _order = nil   -- array of card keys, sorted by hero then slot
    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
    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


local function build_views()
     return cells
     if _views then return _views, _order end
end
    _views, _order = {}, {}


    local hero_data = mw.loadJsonData("Data:HeroData.json")
-- Finds the base light melee scale value: the first top-level property
    local active_heroes = {}
-- whose Scale.Type is "melee" and whose scale value is > 0.
     for _, hero in pairs(hero_data) do
local function find_base_melee_scale(ability)
         if type(hero) == "table"
     for _, v in pairs(ability) do
             and hero["IsDisabled"] == false
         if type(v) == "table" then
            and type(hero["Name"]) == "string"
             local scale = v["Scale"]
            and (hero["IsSelectable"] == nil or hero["IsSelectable"] ~= false)
            if type(scale) == "table"
        then
                and scale["Type"] == "melee"
            active_heroes[hero["Name"]] = true
                and type(scale["Value"]) == "number"
                and scale["Value"] > 0
            then
                return scale["Value"]
            end
         end
         end
     end
     end
    return nil
end


    local ability_data = mw.loadJsonData(GameData.Dataset.ABILITIES)
-- Scans all upgrade tiers for melee-scale changes. Upgrade objects are
     local cards        = mw.loadJsonData("Data:AbilityCards.json")
-- additive in the engine, so the final scale = base + upgrade delta.
-- Returns a list of note strings, one per relevant upgrade tier found.
-- Handles two cases:
--  heavy_melee scale > 0 introduced  → ability gains heavy melee scaling
--  melee scale changed (delta ~= 0)  → existing melee scaling is modified
local function build_melee_upgrade_notes(ability, base_melee_scale)
     local upgrades = ability["Upgrades"]
    if type(upgrades) ~= "table" then return {} end


     local card_of = {}
     local notes = {}
     for _, hero_entry in pairs(cards) do
 
         if type(hero_entry) == "table" then
     for i, tier in ipairs(upgrades) do
             local hero_name = hero_entry["Name"]
         if type(tier) == "table" then
            if hero_name and active_heroes[hero_name] then
             for _, v in pairs(tier) do
                for slot_str, ability in pairs(hero_entry) do
                if type(v) == "table" then
                    local slot_num = tonumber(slot_str)
                     local scale = v["Scale"]
                     local key = type(ability) == "table" and ability["Key"] or nil
                    if type(scale) == "table" and type(scale["Value"]) == "number" then
                    if slot_num and key and ability_data[key] then
                        local stype = scale["Type"]
                         card_of[key] = { heroName = hero_name, abilityNum = slot_num }
                         local delta = scale["Value"]
 
                        if stype == "heavy_melee" and delta > 0 then
                            -- Introduces heavy melee scaling (additive on top of zeroed melee)
                            table.insert(notes, "T" .. i .. " upgrade changes scaling to ×"
                                .. delta .. " of Heavy Melee damage.")
 
                        elseif stype == "melee" and delta ~= 0 and base_melee_scale then
                            -- Modifies existing melee scaling additively
                            local final = base_melee_scale + delta
                            -- Round to avoid float noise (e.g. 1.2 + 0.4 = 1.5999...)
                            final = math.floor(final * 1000 + 0.5) / 1000
                            table.insert(notes, "T" .. i .. " upgrade adds ×"
                                .. delta .. " scaling (total ×" .. final .. ").")
                        end
                     end
                     end
                 end
                 end
Line 124: Line 169:
     end
     end


     -- Attach each non-card record to the longest card key it prefixes.
     return notes
     local subs = {}
end
     for key, record in pairs(ability_data) do
 
         if not card_of[key] and record["IsDisabled"] ~= true then
local function melee_cells(ability)
            local parent = nil
    local cells = {}
             for card_key in pairs(card_of) do
 
                 if key:sub(1, #card_key + 1) == card_key .. "_"
    local base_scale = find_base_melee_scale(ability)
                     and (parent == nil or #card_key > #parent)
    if base_scale then
                then
        cells["Light Melee Scaling"] = "×" .. base_scale
                     parent = card_key
    end
 
    local upgrade_notes = build_melee_upgrade_notes(ability, base_scale)
    if #upgrade_notes > 0 then
        cells["Notes"] = table.concat(upgrade_notes, " ")
    end
 
    return cells
end
 
-- Returns the base duration value using priority: VenomDuration > DebuffDuration > AbilityDuration > BurnDuration.
-- Also returns the field name that was found (for upgrade scanning).
local function find_base_duration(ability)
    for _, field in ipairs({ "VenomDuration", "DebuffDuration", "AbilityDuration", "BurnDuration" }) do
        local v = num_val(ability[field])
        if v and v > 0 then
            return v, field
        end
    end
     return nil, nil
end
 
local function healreduce_cells(ability)
    local cells = {}
    local notes = {}
 
     local base_dur, dur_field = find_base_duration(ability)
 
    -- Determine the first heal reduction value and where it comes from.
    -- Check base fields first, then scan upgrades in order.
    local first_pct      = nil  -- absolute percent value (positive)
    local first_tier      = nil  -- nil = base ability, N = upgrade tier
    local first_is_disable = false
 
    local base_receive = num_val(ability["HealAmpReceivePenaltyPercent"])
    local base_regen  = num_val(ability["HealAmpRegenPenaltyPercent"])
    local base_pct_raw = base_receive or base_regen
    if base_pct_raw and base_pct_raw ~= 0 then
        first_pct  = math.abs(base_pct_raw)
         first_tier = nil
    elseif ability["DisableHealing"] and ability["DisableHealing"] ~= 0 then
        first_is_disable = true
        first_tier = nil
    else
        -- Scan upgrades for first occurrence
        local upgrades = ability["Upgrades"]
        if type(upgrades) == "table" then
             for i, tier in ipairs(upgrades) do
                 if type(tier) == "table" then
                     local up_receive = num_val(tier["HealAmpReceivePenaltyPercent"])
                    local up_regen  = num_val(tier["HealAmpRegenPenaltyPercent"])
                    local up_raw    = up_receive or up_regen
                    if up_raw and up_raw ~= 0 then
                        first_pct  = math.abs(up_raw)
                        first_tier = i
                        break
                    elseif tier["DisableHealing"] and tier["DisableHealing"] ~= 0 then
                        first_is_disable = true
                        first_tier = i
                        break
                     end
                 end
                 end
            end
            if parent then
                subs[parent] = subs[parent] or {}
                table.insert(subs[parent], record)
             end
             end
         end
         end
     end
     end


     for key, info in pairs(card_of) do
     -- Build Heal Reduction cell value
         local view = {}
    if first_is_disable then
         for k, v in pairs(ability_data[key]) do view[k] = v end
         local val = "100%"
         view["_subabilities"] = subs[key]
         if first_tier then val = val .. " (T" .. first_tier .. ")" end
         _views[key] = { view = view, heroName = info.heroName, abilityNum = info.abilityNum }
         cells["Heal Reduction"] = val
         table.insert(_order, key)
    elseif first_pct then
         local val = tostring(first_pct) .. "%"
         if first_tier then val = val .. " (T" .. first_tier .. ")" end
        cells["Heal Reduction"] = val
     end
     end


     table.sort(_order, function(a, b)
     -- Duration column
         local ia, ib = _views[a], _views[b]
    if base_dur then
        if ia.heroName ~= ib.heroName then return ia.heroName < ib.heroName end
        cells["Duration"] = tostring(base_dur)
        return ia.abilityNum < ib.abilityNum
    end
    end)
 
    -- Upgrade notes: percent changes AFTER the first value, and duration changes.
    -- We skip the tier that introduced the first value (already shown in the cell).
    local upgrades = ability["Upgrades"]
    if type(upgrades) == "table" then
         local running_pct = first_pct  -- track cumulative for additive note text
        local running_dur = base_dur
        local first_pct_seen = (first_tier == nil)  -- if base, we've already "seen" it
 
        for i, tier in ipairs(upgrades) do
            if type(tier) == "table" then
                local tier_notes = {}
 
                -- Heal reduction changes
                local up_receive = num_val(tier["HealAmpReceivePenaltyPercent"])
                local up_regen  = num_val(tier["HealAmpRegenPenaltyPercent"])
                local pct_delta  = up_receive or up_regen
                local is_this_first = (first_tier == i)
 
                if is_this_first then
                    -- This tier introduced the first value; already shown in cell, skip note.
                    first_pct_seen = true
                    if first_pct then running_pct = first_pct end
                elseif first_pct_seen and pct_delta and pct_delta ~= 0 then
                    -- Subsequent percent change — note the new total
                    local delta_abs = math.abs(pct_delta)
                    local total = (running_pct or 0) + delta_abs
                    table.insert(tier_notes, "T" .. i .. " upgrade increases heal reduction to " .. total .. "%.")
                    running_pct = total
                end


    return _views, _order
                -- Duration changes (additive deltas in the engine)
end
                if dur_field then
                    local dur_delta = num_val(tier[dur_field])
                    if dur_delta and dur_delta ~= 0 and running_dur then
                        local total_dur = running_dur + dur_delta
                        table.insert(tier_notes,
                            "T" .. i .. " upgrade "
                            .. (dur_delta > 0 and "increases" or "decreases")
                            .. " duration by " .. math.abs(dur_delta)
                            .. "s (total " .. total_dur .. "s).")
                        running_dur = total_dur
                    end
                end


-- ============================================================
                for _, n in ipairs(tier_notes) do
-- Membership
                    table.insert(notes, n)
-- Pieces of code that help in telling  "is ability X a member of list Y".
                end
-- main function is: p.is_member()
            end
-- ============================================================
        end
    end


-- Memoized name-exclusion set per list (from Lists.exclude_abilities).
     if #notes > 0 then
local _excluded = {}
        cells["Notes"] = table.concat(notes, " ")
local function excluded_set(stat)
     if _excluded[stat] then return _excluded[stat] end
    local set = {}
    for _, name in ipairs(Lists.exclude_abilities[stat] or {}) do
        set[name] = true
     end
     end
    _excluded[stat] = set
 
     return set
     return cells
end
end


-- Per-list rules that can't be expressed as prop lists or exclusions.
local extra_columns = {
-- Returns true to keep the ability, false to drop it.
    ["charges"] = {
local function special_ok(stat, record, ability_num)
        headers  = { "Charges", "Time Between Charges", "Notes" },
     if stat == "dispelmagic" and ability_num == 4 then return false end
        get_cells = charges_cells,
     return true
    },
end
     ["melee"] = {
        headers  = { "Light Melee Scaling", "Notes" },
        get_cells = melee_cells,
    },
    ["healreduce"] = {
        headers  = { "Heal Reduction", "Duration", "Notes" },
        get_cells = healreduce_cells,
     },
}
 
-- ============================================================
-- Hero lookup (ability key -> { heroName, abilityNum })
-- ============================================================


-- Public: does ability `ability_key` belong to list `stat`?
local _hero_lookup = nil
function p.is_member(stat, ability_key, ability_num)
    local props = Lists.lists[stat]
    if not props then return false end


    local views = build_views()
local function build_hero_lookup()
     local entry = views[ability_key]
     if _hero_lookup then return _hero_lookup end
     local view  = entry and entry.view
     _hero_lookup = {}


     if not view then
     local hero_data = mw.loadJsonData("Data:HeroData.json")
        view = mw.loadJsonData(GameData.Dataset.ABILITIES)[ability_key]
    local active_heroes = {}
         if not view then return false end
    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
     end


     local name = view["Name"]
     local cards = mw.loadJsonData("Data:AbilityCards.json")
    if name and excluded_set(stat)[name] then return false end
    for _, hero_entry in pairs(cards) do
 
        if type(hero_entry) == "table" then
    ability_num = ability_num or (entry and entry.abilityNum)
            local hero_name = hero_entry["Name"]
    if not special_ok(stat, view, ability_num) then return false end
            if hero_name and active_heroes[hero_name] then
                for slot_str, ability in pairs(hero_entry) do
                    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
    end


     return GameData.entity_matches(view, props)
     return _hero_lookup
end
end


Line 215: Line 383:
     local stat = mw.text.trim(frame.args[1] or ""):lower()
     local stat = mw.text.trim(frame.args[1] or ""):lower()


     local internal_keys = Lists.lists[stat]
     local internal_keys = stat_to_props[stat]
     if not internal_keys then
     if not internal_keys then
         return '<span class="error">AbilityTable: unknown list "' .. stat .. '".</span>'
         return '<span class="error">AbilityTable: unknown stat "' .. stat .. '".</span>'
     end
     end


    -- Build set of columns to hide from the "hide" parameter
     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 hide_set[mw.text.trim(col)] = true end
         for col in hide_param:gmatch("[^,]+") do
            hide_set[mw.text.trim(col)] = true
        end
     end
     end


     local compact = mw.text.trim(frame.args["compact"] or "") ~= ""
     local lookup     = build_hero_lookup()
 
    local collapsed = mw.text.trim(frame.args["collapsed"] or ""):lower()
 
     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 {}
     local get_cells  = spec and spec.get_cells or function() return {} end
     local get_cells  = spec and spec.get_cells or function() return {} end
     local stat_notes = Notes[stat]
     local stat_notes = human_notes[stat]


    local abilities = GameData.get_entities(GameData.Dataset.ABILITIES, internal_keys)
    -- Keep only abilities belonging to active, released heroes
     local filtered = {}
     local filtered = {}
     for _, key in ipairs(order) do
     for _, ability in ipairs(abilities) do
         if p.is_member(stat, key) then
        local key = ability["Key"]
             table.insert(filtered, key)
         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 list: " .. stat
         return "No abilities found for stat: " .. stat
     end
     end


     local lists_url = mw.uri.fullUrl("Module:AbilityTable/Lists")
     -- Sort by hero name, then ability slot number
     local notes_url = mw.uri.fullUrl("Module:AbilityTable/Notes", { action = "edit" })
     table.sort(filtered, function(a, b)
    local disclaimer = frame:preprocess(
        local ia = lookup[a["Key"]]
        '<div class="ability-table-disclaimer">'
         local ib = lookup[b["Key"]]
         .. 'This is a dynamic list '
         if ia.heroName ~= ib.heroName then
        .. '<span class="ability-table-list-name">"' .. stat .. '"</span>. '
             return ia.heroName < ib.heroName
        .. '[' .. tostring(lists_url) .. ' Edit contents]'
        .. ' · '
        .. '[' .. tostring(notes_url) .. ' Edit notes].'
         .. '</div>'
    )
 
    if compact then
        local hero_order    = {}
        local hero_abilities = {}
        for _, key in ipairs(filtered) do
            local hero = views[key].heroName
            if not hero_abilities[hero] then
                table.insert(hero_order, hero)
                hero_abilities[hero] = {}
             end
            table.insert(hero_abilities[hero], views[key].view)
         end
         end
        return ia.abilityNum < ib.abilityNum
    end)


        local items = {}
    -- Determine final header list, respecting hide_set.
        for _, hero in ipairs(hero_order) do
    -- "Notes" is included if declared in headers or if human notes exist,
            local hero_cell = frame:expandTemplate{ title = "HeroIcon", args = { hero } }
    -- but only if not hidden. Either way it appears exactly once at the end.
            local ability_parts = {}
            for _, ability in ipairs(hero_abilities[hero]) do
                local name = ability["Name"]
                local ability_cell = frame:expandTemplate{ title = "AbilityIcon", args = { name } }
 
                local extra = get_cells(ability)
                enrich_notes_compact(extra, stat_notes and stat_notes[name])
 
                local note = extra["Notes"]
                if note then
                    table.insert(ability_parts, ability_cell .. " (" .. note .. ")")
                else
                    table.insert(ability_parts, ability_cell)
                end
            end
            table.insert(items, "<li>" .. hero_cell .. ": " .. table.concat(ability_parts, ", ") .. "</li>")
        end
 
        return disclaimer .. "<ul>" .. table.concat(items) .. "</ul>"
    end
 
     local has_notes_header = false
     local has_notes_header = false
     local final_headers = {}
     local final_headers = {}
Line 305: Line 441:
     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 table.insert(final_headers, "Notes") end
     if needs_notes then
        table.insert(final_headers, "Notes")
    end


     local table_class = 'wikitable sortable ability-table'
     -- Disclaimer
     if collapsed ~= "" then
     local edit_url = mw.uri.fullUrl("Module:AbilityTable", { action = "edit" })
         table_class = table_class .. ' mw-collapsible'
    local disclaimer = frame:preprocess(
         if collapsed == "yes" or collapsed == "true" or collapsed == "1" then
         '<div class="ability-table-disclaimer">'
            table_class = table_class .. ' mw-collapsed'
         .. "This is an automatic list. "
         end
        .. "[" .. tostring(edit_url) .. " Click here]"
     end
        .. " to add notes or correct the contents."
         .. "</div>"
     )


    -- Render wikitable
     local out = {}
     local out = {}
     table.insert(out, disclaimer)
     table.insert(out, disclaimer)
     table.insert(out, '{| class="' .. table_class .. '"')
     table.insert(out, '{| class="wikitable sortable ability-table"')


     local header_row = '! class="ability-table-col-hero" | Hero'
     local header_cells = '! class="ability-table-col-hero" | Hero'
                    .. ' !! class="ability-table-col-ability" | Ability'
                      .. ' !! 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-'
         local cls = "ability-table-col-" .. h:lower():gsub("%s+", "-")
            .. h:lower():gsub("%s+", "-") .. '" | ' .. h
        header_cells = header_cells .. ' !! class="' .. cls .. '" | ' .. h
     end
     end
     table.insert(out, header_row)
     table.insert(out, header_cells)


        local hero_counts = {}
     for _, ability in ipairs(filtered) do
     for _, key in ipairs(filtered) do
         local info = lookup[ability["Key"]]
         local hero = views[key].heroName
        hero_counts[hero] = (hero_counts[hero] or 0) + 1
    end
 
    local hero_seen = {}
 
    for _, key in ipairs(filtered) do
        local entry  = views[key]
        local ability = entry.view
        local hero    = entry.heroName


        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 extra = get_cells(ability)
        enrich_notes_full(extra, stat_notes and stat_notes[ability["Name"]])
        local row
        if not hero_seen[hero] then
            local hero_cell = frame:expandTemplate{
                title = "HeroIcon",
                args = { hero }
            }
            if hero_counts[hero] > 1 then
                row = '| rowspan="' .. hero_counts[hero] .. '" class="ability-table-cell-hero" | '
                    .. hero_cell
                    .. ' || class="ability-table-cell-ability" | ' .. ability_cell
            else
                row = '| class="ability-table-cell-hero" | '
                    .. hero_cell
                    .. ' || class="ability-table-cell-ability" | ' .. ability_cell
            end


             hero_seen[hero] = true
        -- Append human note to any renderer-generated note
        else
        if stat_notes and stat_notes[ability["Name"]] then
             row = '| class="ability-table-cell-ability" | ' .. ability_cell
             local human = stat_notes[ability["Name"]]
             extra["Notes"] = extra["Notes"] and (extra["Notes"] .. " " .. human) or human
         end
         end


        local row_cells = '| 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]
             local cls  = "ability-table-cell-" .. header:lower():gsub("%s+", "-")
             local cls  = "ability-table-cell-" .. header:lower():gsub("%s+", "-")
             local content = value ~= nil and tostring(value) or "—"
             local content
             row = row .. ' || class="' .. cls .. '" | ' .. content
            if value ~= nil then
                local suffix = column_suffix[header] or ""
                content = value .. suffix
            else
                content = "—"
             end
            row_cells = row_cells .. ' || class="' .. cls .. '" | ' .. content
         end
         end


         table.insert(out, "|-")
         table.insert(out, "|-")
         table.insert(out, row)
         table.insert(out, row_cells)
     end
     end


Please note that all contributions to The Deadlock Wiki are considered to be released under the Creative Commons Attribution-NonCommercial-ShareAlike (see Deadlock:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!
Cancel Editing help (opens in new window)
Preview page with this template

Page included on this page: