Module:AbilitySection: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
Vergir (talk | contribs)
Add Interactions tab/parameter alongside Notes and Tips (with help from vergir-bot LLM)
Use Data:Dictionary keys for ability card tab labels instead of hardcoded English (with help from vergir-bot LLM)
 
(6 intermediate revisions by 3 users not shown)
Line 1: Line 1:
-- see /doc for docs
-- See /doc for docs


local p = {}
local p = {}
local utils          = require "Module:Abilities/utils"
local utils          = require "Module:Abilities/utils"
local lang            = require "Module:Lang"
local lang            = require "Module:Lang"
local icon_module    = require "Module:Icon"
local AbilityHints    = require "Module:AbilityHints"
local AbilityHints    = require "Module:AbilityHints"
local resource_lookup = mw.loadJsonData("Data:ResourceLookup.json")
local resource_lookup = mw.loadJsonData("Data:ResourceLookup.json")
local dictionary_data = mw.loadJsonData("Data:Dictionary")


-- Tab definitions — order here determines display order.
-- Tab definitions — order here determines display order.
-- label_dict_key names the [[Data:Dictionary]] entry holding the tab label; the Notes
-- tab reuses the existing 'Notes' entry rather than adding a duplicate.
local TABS = {
local TABS = {
{ key = 'notes',        label = 'Notes'       },
    { key = 'notes',        label_dict_key = 'Notes'                     },
{ key = 'tips',        label = 'Tips'        },
    { key = 'tips',        label_dict_key = 'AbilityCardTabTips'        },
{ key = 'interactions', label = 'Interactions' },
    { key = 'interactions', label_dict_key = 'AbilityCardTabInteractions' },
}
}
local SHOW_ALL_DICT_KEY = 'AbilityCardTabShowAll'
-- Tab labels are read from the dictionary directly instead of going through
-- Module:Dictionary, which appends Template:NeedsTranslationTooltip markup to
-- anything untranslated. mw.ext.tabber.render HTML-escapes a label and reuses it as a
-- DOM id, so that markup would show up as literal text in the tab and mangle the
-- anchor. Untranslated labels therefore fall back to English silently.
local function tab_label(dict_key)
    local entry = dictionary_data[dict_key]
    if entry == nil then return dict_key end
    return entry[lang.get_lang_code()] or entry.en or dict_key
end


local function get_hero_key(hero_name)
local function get_hero_key(hero_name)
if hero_name == nil or hero_name == '' then return nil end
    if hero_name == nil or hero_name == '' then return nil end
if string.sub(hero_name, 1, 5) == "hero_" then return hero_name end
    if string.sub(hero_name, 1, 5) == "hero_" then return hero_name end
local entry = resource_lookup[hero_name:lower()]
    local entry = resource_lookup[hero_name:lower()]
if entry == nil then return nil end
    if entry == nil then return nil end
return entry.key
    return entry.key
end
end


local function trim(s)
local function trim(s)
return s:match('^%s*(.-)%s*$')
    return s:match('^%s*(.-)%s*$')
end
end


local function build_tabber(wikitext_map)
-- Process content in the Interactions tab to build list of icons to draw in ability hints beside ability card
local filled = {}
local function process_interactions(html, ability_num)
for _, tab in ipairs(TABS) do
    local manual, idx, seen = {}, 0, {}
if wikitext_map[tab.key] ~= '' then
    local out = html:gsub('(<div)([^>]*data%-iconbox%-icon="[^"]*"[^>]*)(>)', function(pre, attrs, post)
table.insert(filled, { key = tab.key, label = tab.label, wikitext = wikitext_map[tab.key] })
        local file = attrs:match('data%-iconbox%-icon="([^"]*)"')
end
        if file and file ~= '' then
end
            local base  = mw.ustring.lower((file:gsub('%.%w+$', '')))
            local entry = resource_lookup[base]
            if entry ~= nil and entry.type == 'item' and not seen[base] then
                seen[base] = true
                idx = idx + 1
                local box_id = 'ai-int-' .. ability_num .. '-' .. idx
                table.insert(manual, {
                    file  = file,
                    title  = attrs:match('data%-iconbox%-title="([^"]*)"') or '',
                    state  = attrs:match('data%-iconbox%-state="([^"]*)"') or 'normal',
                    anchor = box_id,
                })
                return pre .. attrs .. ' id="' .. box_id .. '"' .. post
            end
        end
        return pre .. attrs .. post
    end)
    return out, manual
end


if #filled == 0 then
local function build_tabber(wikitext_map, tab_overrides)
return ''
    tab_overrides = tab_overrides or {}
end
    local filled = {}
    for _, tab in ipairs(TABS) do
        if wikitext_map[tab.key] ~= '' then
            local tab_wt = tab_overrides[tab.key]
            if tab_wt == nil or tab_wt == '' then tab_wt = wikitext_map[tab.key] end
            table.insert(filled, {
                key    = tab.key,
                label  = tab_label(tab.label_dict_key),
                tab_wt = tab_wt,
                all_wt = wikitext_map[tab.key],
            })
        end
    end


local tabs = {}
    if #filled == 0 then
for _, section in ipairs(filled) do
        return ''
table.insert(tabs, {
    end
label  = section.label,
content = '<div class="ability-details-panel">\n' .. section.wikitext .. '\n</div>',
})
end


-- "Show All" tab wraps each section's wikitext in a <div> to give the parser
    local tabs = {}
-- a fresh block context per section, preventing html mangling.
    for _, section in ipairs(filled) do
if #filled >= 2 then
        table.insert(tabs, {
local all_parts = {}
            label  = section.label,
for _, section in ipairs(filled) do
            content = '<div class="ability-details-panel">\n' .. section.tab_wt .. '\n</div>',
table.insert(all_parts,
        })
'<div class="ability-details-section-header">' .. section.label .. '</div>'
    end
.. '<div class="ability-details-section-content">\n' .. section.wikitext .. '\n</div>'
)
end
table.insert(tabs, {
label  = 'Show All',
content = '<div class="ability-details-panel">' .. table.concat(all_parts) .. '</div>',
})
end


local rendered = mw.ext.tabber.render(tabs)
    if #filled >= 2 then
if #filled == 1 then
        local all_parts = {}
return '<div class="single-tabbed">' .. rendered .. '</div>'
        for _, section in ipairs(filled) do
end
            table.insert(all_parts,
return rendered
                '<div class="ability-details-section-header">' .. section.label .. '</div>'
                .. '<div class="ability-details-section-content">\n' .. section.all_wt .. '\n</div>'
            )
        end
        table.insert(tabs, {
            label  = tab_label(SHOW_ALL_DICT_KEY),
            content = '<div class="ability-details-panel">' .. table.concat(all_parts) .. '</div>',
        })
    end
 
    local rendered = mw.ext.tabber.render(tabs)
    if #filled == 1 then
        return '<div class="single-tabbed">' .. rendered .. '</div>'
    end
    return rendered
end
end


-- Entry point. Called via {{#invoke:AbilitySection|render}}.
-- Entry point. Called via {{#invoke:AbilitySection|render}}.
function p.render(frame)
function p.render(frame)
local args        = frame.args
    local args        = frame.args
local hero_name  = trim(args.hero_name or '')
    local hero_name  = trim(args.hero_name or '')
local ability_num = trim(args.ability_num or '')
    local ability_num = trim(args.ability_num or '')
local notes        = trim(args.notes or '')
    local notes        = trim(args.notes or '')
local tips        = trim(args.tips or '')
    local tips        = trim(args.tips or '')
local interactions = trim(args.interactions or '')
    local interactions = trim(args.interactions or '')
 
    local hero_key    = get_hero_key(hero_name)
    local heading    = ''
    local ability_key = nil
    if hero_key ~= nil then
        local ability = utils.get_ability_card_data(hero_key, ability_num)
        if ability ~= nil then
            ability_key = ability.Key
            local ability_name = lang.get_string(ability.Key)
            -- Create a visually hidden H3 heading so the Table of Contents can parse
            -- the ability name without displaying a redundant title above the card.
            heading = '<h3 class="ability-name-toc">' .. ability_name .. '</h3>'
        end
    end
 
    local card = frame:expandTemplate{
        title = 'Ability card v2',
        args  = { hero_name = hero_name, ability_num = ability_num, link_name = 'false' }
    }


local hero_key    = get_hero_key(hero_name)
     local interactions_ided, manual = interactions, {}
local heading     = ''
    if interactions ~= '' then
local ability_key = nil
        interactions_ided, manual = process_interactions(interactions, ability_num)
if hero_key ~= nil then
    end
local ability = utils.get_ability_card_data(hero_key, ability_num)
if ability ~= nil then
ability_key = ability.Key
local ability_name = lang.get_string(ability.Key)
-- Use the English ability key for icon render; icon module indexes by English name
local ability_name_en = lang.get_string(ability.Key, 'en', ability.Key)
local icon_html = icon_module._render(ability_name_en, { size = '30px', ['icon-only'] = 'true', ['no-link'] = 'true' })
heading = '<h3 class="ability-name">' .. icon_html .. ' ' .. ability_name .. '</h3>'
end
end


local card = frame:expandTemplate{
    local wikitext_map = {
title = 'Ability card v2',
        notes        = notes,
args  = { hero_name = hero_name, ability_num = ability_num, link_name = 'false' }
        tips        = tips,
}
        interactions = interactions,
    }


local hints_col = ''
    local tabber = build_tabber(wikitext_map, { interactions = interactions_ided })
if ability_key ~= nil then
hints_col = AbilityHints.get_icons(ability_key, ability_num)
end


local wikitext_map = {
    local hints_col = ''
notes        = notes,
    if ability_key ~= nil or #manual > 0 then
tips         = tips,
         hints_col = AbilityHints.get_icons(ability_key, ability_num, manual)
interactions = interactions,
    end
}


local tabber = build_tabber(wikitext_map)
    local details_col = ''
local details_col = ''
    if tabber ~= '' then
if tabber ~= '' then
        details_col = '<div class="ability-section-details">' .. tabber .. '</div>'
details_col = '<div class="ability-section-details">' .. tabber .. '</div>'
    end
end


-- Group the card and the hint rail together so the rail wraps with the card
    local card_block = '<div class="ability-section-card">' .. card .. '</div>'
-- (never onto the notes' line). Abilities with no interactions have no rail,
    local left_col
-- so they keep the flat single-card structure.
    if hints_col ~= '' then
local card_block = '<div class="ability-section-card">' .. card .. '</div>'
        left_col = '<div class="ability-section-main">' .. card_block .. hints_col .. '</div>'
local left_col
    else
if hints_col ~= '' then
        left_col = card_block
left_col = '<div class="ability-section-main">' .. card_block .. hints_col .. '</div>'
    end
else
left_col = card_block
end


local ability_section = '<div class="ability-section">'
    local ability_section = '<div class="ability-section">'
.. left_col
        .. left_col
.. details_col
        .. details_col
.. '</div>'
        .. '</div>'


return '<div class="ability-section-wrapper">'
    return '<div class="ability-section-wrapper">'
.. heading
        .. heading
.. ability_section
        .. ability_section
.. '</div>'
        .. '</div>'
end
end


return p
return p

Latest revision as of 21:07, 1 August 2026

Overview

[edit source]

Lua backend for Template:AbilitySection. Renders a two-column layout with the ability card on the left and an optional tabbed details panel on the right.

Between the card and the details panel, an interaction-hint rail of small item icons is rendered via Module:AbilityHints (showing how the ability interacts with items — dispellable, reduced, blocked, etc.). When a rail is present, the card and rail are grouped in a .ability-section-main wrapper so the rail always wraps together with the card rather than onto the details' line; abilities with no interactions keep the flat single-card structure.

Only tabs with non-empty content are shown. If two or more tabs have content, a Show All tab is automatically appended that stacks all content in sequence with section headers between them.

The Notes tab is height-constrained with a scrollbar when content overflows (see Template:AbilitySection/styles.css). Other tabs are unconstrained.

If no content params are provided, no details panel is rendered at all — only the ability card (and its hint rail, if any).

Entry point

[edit source]

p.render(frame)

[edit source]

Called via {{#invoke:AbilitySection|render}}.

Argument Required Description
hero_name Yes Passed through to Template:Ability card v2.
ability_num Yes Passed through to Template:Ability card v2.
notes No Wikitext for the Notes tab.
tips No Wikitext for the Tips tab.

Constants

[edit source]

Defines list of possible tabs. Order here determines display order in the tabber.

local TABS = {
    { key = 'notes',        label = 'Notes'        },
    { key = 'tips',         label = 'Tips'         },
}

Example

[edit source]
{{AbilitySection
| hero_name   = Abrams
| ability_num = 1
| notes =
* This is an example of a note.
| tips =
* This is an example of a tip.
}}

Siphon Life

Siphon Life



Radius 8m




Duration 4s



Number of Charges 0


Cooldown 42s



Drain health from nearby enemies, dealing spirit damage over time and healing for a portion of the damage dealt.

x0.6


22

Damage Per Second



4s

Duration
On Hit:



70%

Lifesteal

35%  Lifesteal vs Non-Heroes 

Ability Point1
-20s Cooldown
Ability Point2
2s Duration
Ability Point5
+2m Radius and +18 DPS with improved Spirit scaling
Healing amplified by Healing Booster and affected by healing reductionHealing triggers Healing Tempo's unique effect
  • This is an example of a note.

  • This is an example of a tip.

Notes
  • This is an example of a note.
Tips
  • This is an example of a tip.
[edit source]

-- See /doc for docs

local p = {}
local utils           = require "Module:Abilities/utils"
local lang            = require "Module:Lang"
local AbilityHints    = require "Module:AbilityHints"
local resource_lookup = mw.loadJsonData("Data:ResourceLookup.json")
local dictionary_data = mw.loadJsonData("Data:Dictionary")

-- Tab definitions — order here determines display order.
-- label_dict_key names the [[Data:Dictionary]] entry holding the tab label; the Notes
-- tab reuses the existing 'Notes' entry rather than adding a duplicate.
local TABS = {
    { key = 'notes',        label_dict_key = 'Notes'                      },
    { key = 'tips',         label_dict_key = 'AbilityCardTabTips'         },
    { key = 'interactions', label_dict_key = 'AbilityCardTabInteractions' },
}

local SHOW_ALL_DICT_KEY = 'AbilityCardTabShowAll'

-- Tab labels are read from the dictionary directly instead of going through
-- Module:Dictionary, which appends Template:NeedsTranslationTooltip markup to
-- anything untranslated. mw.ext.tabber.render HTML-escapes a label and reuses it as a
-- DOM id, so that markup would show up as literal text in the tab and mangle the
-- anchor. Untranslated labels therefore fall back to English silently.
local function tab_label(dict_key)
    local entry = dictionary_data[dict_key]
    if entry == nil then return dict_key end
    return entry[lang.get_lang_code()] or entry.en or dict_key
end

local function get_hero_key(hero_name)
    if hero_name == nil or hero_name == '' then return nil end
    if string.sub(hero_name, 1, 5) == "hero_" then return hero_name end
    local entry = resource_lookup[hero_name:lower()]
    if entry == nil then return nil end
    return entry.key
end

local function trim(s)
    return s:match('^%s*(.-)%s*$')
end

-- Process content in the Interactions tab to build list of icons to draw in ability hints beside ability card
local function process_interactions(html, ability_num)
    local manual, idx, seen = {}, 0, {}
    local out = html:gsub('(<div)([^>]*data%-iconbox%-icon="[^"]*"[^>]*)(>)', function(pre, attrs, post)
        local file = attrs:match('data%-iconbox%-icon="([^"]*)"')
        if file and file ~= '' then
            local base  = mw.ustring.lower((file:gsub('%.%w+$', '')))
            local entry = resource_lookup[base]
            if entry ~= nil and entry.type == 'item' and not seen[base] then
                seen[base] = true
                idx = idx + 1
                local box_id = 'ai-int-' .. ability_num .. '-' .. idx
                table.insert(manual, {
                    file   = file,
                    title  = attrs:match('data%-iconbox%-title="([^"]*)"') or '',
                    state  = attrs:match('data%-iconbox%-state="([^"]*)"') or 'normal',
                    anchor = box_id,
                })
                return pre .. attrs .. ' id="' .. box_id .. '"' .. post
            end
        end
        return pre .. attrs .. post
    end)
    return out, manual
end

local function build_tabber(wikitext_map, tab_overrides)
    tab_overrides = tab_overrides or {}
    local filled = {}
    for _, tab in ipairs(TABS) do
        if wikitext_map[tab.key] ~= '' then
            local tab_wt = tab_overrides[tab.key]
            if tab_wt == nil or tab_wt == '' then tab_wt = wikitext_map[tab.key] end
            table.insert(filled, {
                key    = tab.key,
                label  = tab_label(tab.label_dict_key),
                tab_wt = tab_wt,
                all_wt = wikitext_map[tab.key],
            })
        end
    end

    if #filled == 0 then
        return ''
    end

    local tabs = {}
    for _, section in ipairs(filled) do
        table.insert(tabs, {
            label   = section.label,
            content = '<div class="ability-details-panel">\n' .. section.tab_wt .. '\n</div>',
        })
    end

    if #filled >= 2 then
        local all_parts = {}
        for _, section in ipairs(filled) do
            table.insert(all_parts,
                '<div class="ability-details-section-header">' .. section.label .. '</div>'
                .. '<div class="ability-details-section-content">\n' .. section.all_wt .. '\n</div>'
            )
        end
        table.insert(tabs, {
            label   = tab_label(SHOW_ALL_DICT_KEY),
            content = '<div class="ability-details-panel">' .. table.concat(all_parts) .. '</div>',
        })
    end

    local rendered = mw.ext.tabber.render(tabs)
    if #filled == 1 then
        return '<div class="single-tabbed">' .. rendered .. '</div>'
    end
    return rendered
end

-- Entry point. Called via {{#invoke:AbilitySection|render}}.
function p.render(frame)
    local args        = frame.args
    local hero_name   = trim(args.hero_name or '')
    local ability_num = trim(args.ability_num or '')
    local notes        = trim(args.notes or '')
    local tips         = trim(args.tips or '')
    local interactions = trim(args.interactions or '')

    local hero_key    = get_hero_key(hero_name)
    local heading     = ''
    local ability_key = nil
    if hero_key ~= nil then
        local ability = utils.get_ability_card_data(hero_key, ability_num)
        if ability ~= nil then
            ability_key = ability.Key
            local ability_name = lang.get_string(ability.Key)
            -- Create a visually hidden H3 heading so the Table of Contents can parse 
            -- the ability name without displaying a redundant title above the card.
            heading = '<h3 class="ability-name-toc">' .. ability_name .. '</h3>'
        end
    end

    local card = frame:expandTemplate{
        title = 'Ability card v2',
        args  = { hero_name = hero_name, ability_num = ability_num, link_name = 'false' }
    }

    local interactions_ided, manual = interactions, {}
    if interactions ~= '' then
        interactions_ided, manual = process_interactions(interactions, ability_num)
    end

    local wikitext_map = {
        notes        = notes,
        tips         = tips,
        interactions = interactions,
    }

    local tabber = build_tabber(wikitext_map, { interactions = interactions_ided })

    local hints_col = ''
    if ability_key ~= nil or #manual > 0 then
        hints_col = AbilityHints.get_icons(ability_key, ability_num, manual)
    end

    local details_col = ''
    if tabber ~= '' then
        details_col = '<div class="ability-section-details">' .. tabber .. '</div>'
    end

    local card_block = '<div class="ability-section-card">' .. card .. '</div>'
    local left_col
    if hints_col ~= '' then
        left_col = '<div class="ability-section-main">' .. card_block .. hints_col .. '</div>'
    else
        left_col = card_block
    end

    local ability_section = '<div class="ability-section">'
        .. left_col
        .. details_col
        .. '</div>'

    return '<div class="ability-section-wrapper">'
        .. heading
        .. ability_section
        .. '</div>'
end

return p