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" },
    ["healreduce"] = { "HealAmpReceivePenaltyPercent", "HealAmpRegenPenaltyPercent", "DisableHealing" },
    ["debuffresist"]    = {
        -- Do not insert: AbilityDuration, TickRate, SlowDuration
        -- they do more harm than good and include a lot of excessive abilities to the list
        "StunDuration", "SleepDuration", "SilenceDuration", "SilenceDebuff",
        "PetrifyDuration", "HexDuration", "ImmobilizeDuration", "LiftDuration",
        "DebuffDuration", "HoldInPlaceDuration", "ExplodeDebuffDuration", "SlowPercent",
        "DebuffAccuracy", "TimeSlowDuration", "LateCheckoutStun", "BulletDamageAmpDuration",
        "BulletResistReduction", "BurnDuration", "EnemySlowPct", "MoveSlowPercent",
        "GroundDashReductionPercent", "StealDuration", "MaxSlowDuration", "EffectDuration",
        "BleedDuration", "VenomDuration",
        -- can also just list abilities if they don't have a reusable property
        "Hotel Guest", "Life Drain", "Combo", "Light Eater", "Spirit Lasso", "Card Trick"
    },
}


local p = {}
-- Editor-authored notes appended to the Notes column (normal mode) or
 
-- inline after the ability name in parentheses (compact mode).
local extra_columns = {
local human_notes = {
    ["barrier"] = {
        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"] = {
     ["debuffresist"] = {
         headers   = { "Base Damage", "Heavy Melee Scaling", "Notes" },
         ["Aura of Suffering"]   = "T1 slow, T3 extra damage. The effect is negligible.",
         get_cells = Renderers.RenderHeavyMelee,
        ["Lightning Ball"]      = "T2 Slow. The effect is negligible.",
        ["Static Charge"]      = "Stun duration. Delay before stun is not affected.",
        ["Enchanter's Satchel"] = "T3 Slow.",
        ["Scorn"]              = "T3 extra damage from Mo & Krill.",
        ["Hotel Guest"]        = "Only T2 late check-out stun can be reduced.",
        ["Jar of Dead"]        = "T2 slow.",
        ["Grapple Arm"]        = "T1 extra damage from Bebop.",
        ["Binding Word"]        = "Immobilize Duration. Escape time is not affected.",
        ["Power Slash"]        = "T1 slow.",
        ["Kinetic Pulse"]      = "T2 bullet resist reduction and slow.",
        ["Pain Battery"]        = "T1 slow.",
        ["Power Surge"]        = "T3 Spirit Resist reduction.",
        ["Rain of Arrows"]      = "T2 slow.",
        ["Soul Exchange"]      = "T2 silence.",
        ["Ground Strike"]      = "T2 slow.",
         ["Flog"]                = "T2 Fire rate slow.",
     },
     },
    ["scalingspirit"] = {
}
        headers  = { "Spirit Scaling", "Notes" },
 
        get_cells = Renderers.RenderSpiritScaling,
-- Listed by ability name. If adding please add a comment.
    },
local exclude_list = {
    ["healreduce"] = {
     ["debuffresist"] = {
        headers  = { "Heal Reduction", "Notes" },
         "Bookwyrm", -- has debuffduration for some reason
        get_cells = Renderers.RenderHealReduce,
         "Itani Lo Sahn", -- has debufffduration but does not get reduced
    },
         "Slam Fire", -- has debuffduration for some reason
     ["moveslow"] = {
         "Splatter", -- has SlowPercent but no duration to shave off.
         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 _views = nil  -- card key -> { view = record, heroName = ..., abilityNum = ... }
    local cells = {}
local _order = nil  -- array of card keys, sorted by hero then slot


local function build_views()
    if has_base then
    if _views then return _views, _order end
        cells["Charges"] = tostring(base_charges)
     _views, _order = {}, {}
        if cooldown then
            cells["Time Between Charges"] = tostring(cooldown)
        end
     end


     local hero_data = mw.loadJsonData("Data:HeroData.json")
     local note_parts = {}
    local active_heroes = {}
    if upgrade_info then
    for _, hero in pairs(hero_data) do
        local n = upgrade_info.value or "?"
         if type(hero) == "table"
        local charge_str = n .. " charge" .. (n ~= 1 and "s" or "")
            and hero["IsDisabled"] == false
         if has_base then
             and type(hero["Name"]) == "string"
            table.insert(note_parts, "+" .. charge_str .. " on T" .. upgrade_info.tier .. " upgrade.")
             and (hero["IsSelectable"] == nil or hero["IsSelectable"] ~= false)
        else
        then
             local cd_str = upgrade_cooldown and (upgrade_cooldown .. "s") or "unknown"
            active_heroes[hero["Name"]] = true
             table.insert(note_parts, "Becomes charged on T" .. upgrade_info.tier
                .. " upgrade with " .. charge_str
                .. " and " .. cd_str .. " time between charges.")
         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
     end


     local ability_data = mw.loadJsonData(GameData.Dataset.ABILITIES)
     return cells
    local cards        = mw.loadJsonData("Data:AbilityCards.json")
end


    local card_of = {}
local function find_base_melee_scale(ability)
     for _, hero_entry in pairs(cards) do
     for _, v in pairs(ability) do
         if type(hero_entry) == "table" then
         if type(v) == "table" then
             local hero_name = hero_entry["Name"]
             local scale = v["Scale"]
             if hero_name and active_heroes[hero_name] then
             if type(scale) == "table"
                 for slot_str, ability in pairs(hero_entry) do
                and scale["Type"] == "melee"
                    local slot_num = tonumber(slot_str)
                and type(scale["Value"]) == "number"
                     local key = type(ability) == "table" and ability["Key"] or nil
                and scale["Value"] > 0
                    if slot_num and key and ability_data[key] then
            then
                         card_of[key] = { heroName = hero_name, abilityNum = slot_num }
                 return scale["Value"]
            end
        end
    end
    return nil
end
 
local function build_melee_upgrade_notes(ability, base_melee_scale)
    local upgrades = ability["Upgrades"]
    if type(upgrades) ~= "table" then return {} end
    local notes = {}
    for i, tier in ipairs(upgrades) do
        if type(tier) == "table" then
            for _, v in pairs(tier) do
                if type(v) == "table" then
                     local scale = v["Scale"]
                    if type(scale) == "table" and type(scale["Value"]) == "number" then
                        local stype = scale["Type"]
                         local delta = scale["Value"]
                        if stype == "heavy_melee" and delta > 0 then
                            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
                            local final = math.floor((base_melee_scale + delta) * 1000 + 0.5) / 1000
                            table.insert(notes, "T" .. i .. " upgrade adds ×"
                                .. delta .. " scaling (total ×" .. final .. ").")
                        end
                     end
                     end
                 end
                 end
Line 123: Line 195:
         end
         end
     end
     end
    return notes
end


     -- Attach each non-card record to the longest card key it prefixes.
local function melee_cells(ability)
     local subs = {}
    local cells = {}
     for key, record in pairs(ability_data) do
    local base_scale = find_base_melee_scale(ability)
         if not card_of[key] and record["IsDisabled"] ~= true then
     if base_scale then cells["Light Melee Scaling"] = "×" .. base_scale end
            local parent = nil
     local upgrade_notes = build_melee_upgrade_notes(ability, base_scale)
             for card_key in pairs(card_of) do
    if #upgrade_notes > 0 then cells["Notes"] = table.concat(upgrade_notes, " ") end
                 if key:sub(1, #card_key + 1) == card_key .. "_"
    return cells
                     and (parent == nil or #card_key > #parent)
end
                then
 
                     parent = card_key
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)
 
    local first_pct, first_tier, first_is_disable = nil, nil, 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)
    elseif ability["DisableHealing"] and ability["DisableHealing"] ~= 0 then
        first_is_disable = true
    else
        local upgrades = ability["Upgrades"]
        if type(upgrades) == "table" then
             for i, tier in ipairs(upgrades) do
                 if type(tier) == "table" then
                    local up_raw = num_val(tier["HealAmpReceivePenaltyPercent"])
                                or num_val(tier["HealAmpRegenPenaltyPercent"])
                     if up_raw and up_raw ~= 0 then
                        first_pct, first_tier = math.abs(up_raw), i
                        break
                    elseif tier["DisableHealing"] and tier["DisableHealing"] ~= 0 then
                        first_is_disable, first_tier = true, 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
     if first_is_disable then
         local view = {}
         cells["Heal Reduction"] = "100%" .. (first_tier and " (T" .. first_tier .. ")" or "")
        for k, v in pairs(ability_data[key]) do view[k] = v end
    elseif first_pct then
         view["_subabilities"] = subs[key]
         cells["Heal Reduction"] = tostring(first_pct) .. "%" .. (first_tier and " (T" .. first_tier .. ")" or "")
        _views[key] = { view = view, heroName = info.heroName, abilityNum = info.abilityNum }
        table.insert(_order, key)
     end
     end
    if base_dur then cells["Duration"] = tostring(base_dur) end


     table.sort(_order, function(a, b)
     local upgrades = ability["Upgrades"]
        local ia, ib = _views[a], _views[b]
    if type(upgrades) == "table" then
        if ia.heroName ~= ib.heroName then return ia.heroName < ib.heroName end
        local running_pct, running_dur = first_pct, base_dur
         return ia.abilityNum < ib.abilityNum
        local first_pct_seen = (first_tier == nil)
     end)
        for i, tier in ipairs(upgrades) do
            if type(tier) == "table" then
                local tier_notes = {}
                local pct_delta = num_val(tier["HealAmpReceivePenaltyPercent"])
                              or num_val(tier["HealAmpRegenPenaltyPercent"])
                if first_tier == i then
                    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
                    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 by "
                        .. delta_abs .. "% (total " .. total .. "%).")
                    running_pct = total
                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 table.insert(notes, n) end
            end
         end
     end


     return _views, _order
    if #notes > 0 then cells["Notes"] = table.concat(notes, " ") end
     return cells
end
end
local extra_columns = {
    ["charges"] = {
        headers  = { "Charges", "Time Between Charges", "Notes" },
        get_cells = charges_cells,
    },
    ["melee"] = {
        headers  = { "Light Melee Scaling", "Notes" },
        get_cells = melee_cells,
    },
    ["healreduce"] = {
        headers  = { "Heal Reduction", "Duration", "Notes" },
        get_cells = healreduce_cells,
    },
}


-- ============================================================
-- ============================================================
-- Membership
-- Hero lookup
-- Pieces of code that help in telling "is ability X a member of list Y".
--   _key_lookup : ability key  -> { heroName, abilityNum }
-- main function is: p.is_member()
--   _name_to_key : ability name -> ability key
-- Both built together from HeroData + AbilityCards.
-- ============================================================
-- ============================================================


-- Memoized name-exclusion set per list (from Lists.exclude_abilities).
local _key_lookup  = nil
local _excluded = {}
local _name_to_key = nil
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
    _excluded[stat] = set
    return set
end


-- Per-list rules that can't be expressed as prop lists or exclusions.
local function build_lookup()
-- Returns true to keep the ability, false to drop it.
     if _key_lookup then return _key_lookup, _name_to_key end
local function special_ok(stat, record, ability_num)
     _key_lookup  = {}
     if stat == "dispelmagic" and ability_num == 4 then return false end
    _name_to_key = {}
     return true
end


-- Public: does ability `ability_key` belong to list `stat`?
    local hero_data = mw.loadJsonData("Data:HeroData.json")
function p.is_member(stat, ability_key, ability_num)
     local active_heroes = {}
     local props = Lists.lists[stat]
    for _, hero in pairs(hero_data) do
     if not props then return false end
        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


     local views = build_views()
     local cards = mw.loadJsonData("Data:AbilityCards.json")
     local entry = views[ability_key]
     for _, hero_entry in pairs(cards) do
    local view  = entry and entry.view
        if type(hero_entry) == "table" then
 
            local hero_name = hero_entry["Name"]
    if not view then
            if hero_name and active_heroes[hero_name] then
        view = mw.loadJsonData(GameData.Dataset.ABILITIES)[ability_key]
                for slot_str, ability in pairs(hero_entry) do
         if not view then return false end
                    local slot_num = tonumber(slot_str)
                    if slot_num and type(ability) == "table" then
                        local info = { heroName = hero_name, abilityNum = slot_num }
                        if ability["Key"] then
                            _key_lookup[ability["Key"]] = info
                        end
                        if ability["Name"] and ability["Key"] then
                            _name_to_key[ability["Name"]] = ability["Key"]
                        end
                    end
                end
            end
         end
     end
     end


     local name = view["Name"]
     return _key_lookup, _name_to_key
    if name and excluded_set(stat)[name] then return false end
 
    ability_num = ability_num or (entry and entry.abilityNum)
    if not special_ok(stat, view, ability_num) then return false end
 
    return GameData.entity_matches(view, props)
end
end


Line 215: Line 366:
     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


Line 228: Line 379:
     local compact = mw.text.trim(frame.args["compact"] or "") ~= ""
     local compact = mw.text.trim(frame.args["compact"] or "") ~= ""


     local collapsed = mw.text.trim(frame.args["collapsed"] or ""):lower()
     local key_lookup, _ = build_lookup()
 
    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]


    -- Build excluded-name set for this stat
    local excluded = {}
    if exclude_list[stat] then
        for _, name in ipairs(exclude_list[stat]) do
            excluded[name] = true
        end
    end
    local abilities = GameData.get_entities(GameData.Dataset.ABILITIES, internal_keys)
    -- Keep only abilities belonging to active, released heroes and not excluded
     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 key_lookup[key] and not excluded[ability["Name"]] 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 ia = key_lookup[a["Key"]]
        local ib = key_lookup[b["Key"]]
        if not ia and not ib then return (a["Name"] or "") < (b["Name"] or "") end
        if not ia then return false end
        if not ib then return true end
        if ia.heroName ~= ib.heroName then return ia.heroName < ib.heroName end
        return ia.abilityNum < ib.abilityNum
    end)
 
    -- Disclaimer
     local edit_url = mw.uri.fullUrl("Module:AbilityTable", { action = "edit" })
     local disclaimer = frame:preprocess(
     local disclaimer = frame:preprocess(
         '<div class="ability-table-disclaimer">'
         '<div class="ability-table-disclaimer">'
         .. 'This is a dynamic list '
         .. "This is an automatic list. "
        .. '<span class="ability-table-list-name">"' .. stat .. '"</span>. '
         .. "[" .. tostring(edit_url) .. " Click here]"
         .. '[' .. tostring(lists_url) .. ' Edit contents]'
         .. " to add notes or correct the contents."
         .. ' · '
         .. "</div>"
        .. '[' .. tostring(notes_url) .. ' Edit notes].'
         .. '</div>'
     )
     )


     if compact then
     if compact then
        -- Compact mode: <ul> with one <li> per hero.
        -- Each item: "{HeroIcon}: {AbilityIcon} (note), {AbilityIcon}, ..."
        -- Group abilities by hero, preserving sort order.
         local hero_order    = {}
         local hero_order    = {}
         local hero_abilities = {}
         local hero_abilities = {} -- heroName -> list of ability names
         for _, key in ipairs(filtered) do
         for _, ability in ipairs(filtered) do
             local hero = views[key].heroName
             local info = key_lookup[ability["Key"]]
            local hero = info and info.heroName or "—"
             if not hero_abilities[hero] then
             if not hero_abilities[hero] then
                 table.insert(hero_order, hero)
                 table.insert(hero_order, hero)
                 hero_abilities[hero] = {}
                 hero_abilities[hero] = {}
             end
             end
             table.insert(hero_abilities[hero], views[key].view)
             table.insert(hero_abilities[hero], ability["Name"])
         end
         end


Line 275: Line 450:
             local hero_cell = frame:expandTemplate{ title = "HeroIcon", args = { hero } }
             local hero_cell = frame:expandTemplate{ title = "HeroIcon", args = { hero } }
             local ability_parts = {}
             local ability_parts = {}
             for _, ability in ipairs(hero_abilities[hero]) do
             for _, name in ipairs(hero_abilities[hero]) do
                local name = ability["Name"]
                 local ability_cell = frame:expandTemplate{ title = "AbilityIcon", args = { name } }
                 local ability_cell = frame:expandTemplate{ title = "AbilityIcon", args = { name } }
 
                 local note = stat_notes and stat_notes[name]
                 local extra = get_cells(ability)
                enrich_notes_compact(extra, stat_notes and stat_notes[name])
 
                local note = extra["Notes"]
                 if note then
                 if note then
                     table.insert(ability_parts, ability_cell .. " (" .. note .. ")")
                     table.insert(ability_parts, ability_cell .. " (" .. note .. ")")
Line 295: Line 465:
     end
     end


    -- Normal mode: wikitable with one row per ability and stat-specific columns.
     local has_notes_header = false
     local has_notes_header = false
     local final_headers = {}
     local final_headers = {}
Line 306: Line 477:
     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'
    if collapsed ~= "" then
        table_class = table_class .. ' mw-collapsible'
        if collapsed == "yes" or collapsed == "true" or collapsed == "1" then
            table_class = table_class .. ' mw-collapsed'
        end
    end


     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_row = '! class="ability-table-col-hero" | Hero'
Line 327: Line 490:
     table.insert(out, header_row)
     table.insert(out, header_row)


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


    local hero_seen = {}
        local hero_cell
 
        if info then
    for _, key in ipairs(filtered) do
            hero_cell = frame:expandTemplate{ title = "HeroIcon", args = { info.heroName } }
         local entry  = views[key]
         else
        local ability = entry.view
            hero_cell = "—"
         local hero    = entry.heroName
         end
 
         local ability_cell = frame:expandTemplate{ title = "AbilityIcon", args = { ability["Name"] } }
         local ability_cell = frame:expandTemplate{
            title = "AbilityIcon",
            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 stat_notes and stat_notes[ability["Name"]] then
 
             local human = stat_notes[ability["Name"]]
         if not hero_seen[hero] then
             extra["Notes"] = extra["Notes"] and (extra["Notes"] .. " " .. human) or human
             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
        else
            row = '| class="ability-table-cell-ability" | ' .. ability_cell
         end
         end


        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]
             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
            if value ~= nil then
                local suffix = column_suffix[header] or ""
                content = (type(value) == "string" and value:sub(1,1) == "(")
                    and value or (value .. suffix)
            else
                content = "—"
            end
             row = row .. ' || class="' .. cls .. '" | ' .. content
             row = row .. ' || class="' .. cls .. '" | ' .. content
         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: