Editing Module:Sandbox/LVL

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:
local p = {}
local p = {}
local lang = require "Module:Lang"
local util_module = require "Module:Utilities"
local data = mw.loadJsonData("Data:Monster_Domosed/ItemData.json")
local stat_links = mw.loadJsonData("Data:StatLinks.json")


local heroes_data      = mw.loadJsonData("Data:HeroData.json")
local LANG_CODE = lang.get_lang_code()
local attributes_data  = mw.loadJsonData("Data:AttributeData.json")
local IS_EN = (LANG_CODE == "en")
local attribute_orders  = mw.loadJsonData("Data:StatInfoboxOrder.json")
local util_module      = require('Module:Utilities')
local lang_module      = require('Module:Lang')
local dictionary_module = require('Module:Dictionary')
local attribute_module  = require('Module:AttributeData')
local hero_data_module  = require('Module:HeroData')


local function get_nested_stat_value(hero_data, stat_key)
local MOVE_SPEED_RATES = mw.loadJsonData("Data:LocalizedUnits.json")
     if hero_data[stat_key] ~= nil then
 
        return hero_data[stat_key]
local function has_rate_unit(str)
     elseif hero_data.Weapon and hero_data.Weapon[stat_key] ~= nil then
     if type(str) ~= "string" then return false end
        return hero_data.Weapon[stat_key]
     return str:find("/") ~= nil
     elseif hero_data.Weapon and hero_data.Weapon.AltFire and hero_data.Weapon.AltFire[stat_key] ~= nil then
end
        return hero_data.Weapon.AltFire[stat_key]
 
    end
local function get_movespeed_rate_suffix()
    return 0
     return MOVE_SPEED_RATES[LANG_CODE]
end
end


local function localize(key, fallback)
local function get_missing_rate_warning()
     local result = lang_module.get_string(key)
     return '<sup style="white-space:nowrap; font-size: smaller;"><span title="Missing translated m/s. Please add an entry for this language to Data:Lang_MoveSpeedRates.json" style="color:red; cursor:help;">!?</span></sup>'
    if result == "" or result == nil then
        result = util_module.add_space_before_cap(fallback) ..
                mw.getCurrentFrame():expandTemplate{title = "MissingValveTranslationTooltip"}
    end
    return result
end
end


-- =========================================================================
-- Get raw value, CSS class, and LocTokenOverride for a property
--  DPS calculation helpers
local function get_raw_value(item_name, prop)
-- =========================================================================
    local item = data[item_name] or nil
    if not item then
        for k,v in pairs(data) do
            if v.Name == item_name then item = v; break end
        end
    end
    if not item then return nil, nil, nil end


local function calculate_dps(stats, dps_type)
    local raw_prop = item[prop]
    local rps = stats.RoundsPerSecond or 0
    if type(raw_prop) == "table" then
     if rps == 0 then return 0 end
        local loc_override = raw_prop.LocTokenOverride or prop
        return raw_prop.Value, raw_prop.CSSClass, loc_override
     else
        return raw_prop, nil, prop
    end
end


    local bullets_per_shot = (stats.HitOnceAcrossAllBullets and 1) or (stats.BulletsPerShot or 1)
-- Strip embedded unit postfix when not EN (e.g. "3.0m" -> "3.0")
     local burst_count = stats.BulletsPerBurst or 1
local function strip_embedded_postfix(val)
     local cycle_time = 1 / rps
     if IS_EN then return val end
     local total_cycle_time = cycle_time * burst_count
     if type(val) ~= "string" then return val end
     return val:match("^%-?%d+%.?%d*") or val
end


    if total_cycle_time == 0 then return 0 end


     local base_damage = stats.BulletDamage or 0
-- Check if property exists in language data
     if dps_type == 'burst' then
local function is_property_in_lang_data(prop)
         return base_damage * bullets_per_shot * burst_count / total_cycle_time
     local suffixes = {"_label", "_Label", "_prefix", "_Prefix", "_postfix", "_Postfix", "_postvalue_label", "_Postvalue_Label"}
     for _, suffix in ipairs(suffixes) do
        if lang.get_string(prop .. suffix) then
            return true
         end
     end
     end
    return false
end


     local clip_size = stats.ClipSize or 0
-- Get item by name, ignoring disabled if possible
     if clip_size <= 0 then
local function get_json_item(name)
        return base_damage * bullets_per_shot * burst_count / total_cycle_time
     for _, v in pairs(data) do
        if v.Name == name and (v.IsDisabled == false or v.IsDisabled == nil) then
            return v
        end
    end
     for _, v in pairs(data) do
        if v.Name == name then return v end
     end
     end
    return nil
end


    local reload_time
-- Helper function: get first non-nil string from variants
     if stats.ReloadSingle then
local function first_non_nil(...)
         reload_time = (stats.ReloadTime or 0) * clip_size
     for i = 1, select("#", ...) do
    else
         local v = select(i, ...)
         reload_time = stats.ReloadTime or 0
         if v and v ~= "" then return v end
     end
     end
     reload_time = reload_time + (stats.ReloadDelay or 0)
     return ""
end


    local time_to_empty_clip = (clip_size / burst_count) * total_cycle_time
-- Helper: get lang string with case fallback
     local damage_from_clip = base_damage * bullets_per_shot * clip_size
local function get_lang_with_fallback(base_key, suffix1, suffix2)
    local total_time = time_to_empty_clip + reload_time
     return first_non_nil(
    if total_time == 0 then return 0 end
        lang.get_string(base_key .. suffix1),
     return damage_from_clip / total_time
        lang.get_string(base_key .. suffix2)
     )
end
end


-- Compute how much DPS changes when all relevant component scalings are applied.
-- Enhanced stat wrapper - prefix and value get enhanced styling, postfix/label don't
-- Used for alt-fire rows where explicit DPS/SustainedDPS scaling keys are missing.
local function format_stat_enhanced(item_name, prop, skip_label, allow_conditional)
local function compute_dps_scaling(hero_data, base_stats, dps_type, scaling_type)
skip_label = skip_label or false
     local scaling_source = hero_data[scaling_type .. "Scaling"] or {}
if allow_conditional == nil then allow_conditional = true end
local value, css, loc_key = get_raw_value(item_name, prop)
if value == nil or value == 0 or value == "" then
return nil
end
 
local numeric_val = tonumber(tostring(value):match("^-?%d+%.?%d*")) or 0
if numeric_val == 0 then
return nil
end
 
local label_key = loc_key:gsub("^#", "")
 
if not (
lang.get_string(label_key.."_label") or
lang.get_string(label_key.."_Label") or
lang.get_string(label_key.."_prefix") or
lang.get_string(label_key.."_Prefix") or
lang.get_string(label_key.."_postfix") or
lang.get_string(label_key.."_Postfix") or
lang.get_string(label_key.."_postvalue_label") or
lang.get_string(label_key.."_Postvalue_Label")
) then
return nil
end
 
if prop == "BuildUpPerShot" then label_key = "BuildupPerShot"
elseif prop == "DotDuration" then label_key = "DOTDuration"
end
 
local label = skip_label and "" or get_lang_with_fallback(label_key, "_label", "_Label")
local prefix = get_lang_with_fallback(label_key, "_prefix", "_Prefix")
local postfix = get_lang_with_fallback(label_key, "_postfix", "_Postfix")
 
if prefix == "" and postfix == "" and label == "" then
return nil
end
 
--------------------------------------------------
-- VALUE (Enhanced) with PREFIX and POSTFIX/SUFFIX
--------------------------------------------------
local val_str
local suffix = postfix
 
        -- Special spacing rule for HP/s
     if postfix == "HP/s" then
        suffix = "&#32;" .. postfix
    end
   
    if (css == "move_speed" or css == "sprint_speed")
    and postfix ~= "%"
    and not has_rate_unit(postfix)
    then
        local override = get_movespeed_rate_suffix()


    local component_scalings = {}
        if override then
    local possible_keys = {
            suffix = override
         "BulletDamage", "RoundsPerSecond", "ClipSize", "ReloadTime",
         else
        "ReloadDelay", "BulletsPerShot", "BulletsPerBurst"
            suffix = "m/s"
    }
            suffix = suffix .. get_missing_rate_warning()
    for _, key in ipairs(possible_keys) do
        local alt_key = key .. "AltFire"
        if scaling_source[alt_key] then
            component_scalings[key] = scaling_source[alt_key]
         end
         end
     end
     end


    -- ClipSize is shared between fire modes; fall back to primary scaling
if prefix ~= "" then
    if base_stats.ClipSize and not component_scalings.ClipSize then
if prefix == "{s:sign}" then
        local primary_clip_scale = scaling_source["ClipSize"]
local num = tonumber(tostring(value):match("^-?%d+%.?%d*"))
         if primary_clip_scale then
if num and num >= 0 then
             component_scalings.ClipSize = primary_clip_scale
prefix = "+"
else
prefix = ""
end
end
if suffix ~= "" and suffix ~= "%" then
val_str = string.format(
"{{#invoke:Enhanced|render|%s|%s|prefix=%s|suffix=%s|noicon=true}}",
item_name,
prop,
prefix,
suffix
)
else
val_str = string.format(
"{{#invoke:Enhanced|render|%s|%s|prefix=%s|noicon=true}}",
item_name,
prop,
prefix
)
end
else
if suffix ~= "" and suffix ~= "%" then
val_str = string.format(
"{{#invoke:Enhanced|render|%s|%s|suffix=%s|noicon=true}}",
item_name,
prop,
suffix
)
else
val_str = string.format(
"{{#invoke:Enhanced|render|%s|%s|noicon=true}}",
item_name,
prop
)
end
end
 
--------------------------------------------------
-- Postfix (UNSTYLED)
--------------------------------------------------
if postfix ~= "" and postfix ~= "%" and not val_str:find(postfix, 1, true) then
if postfix == "HP/s" then
val_str = val_str .. " " .. postfix
else
val_str = val_str .. postfix
end
end
 
--------------------------------------------------
-- Label (UNSTYLED - only if not skipped)
--------------------------------------------------
if label ~= "" then
    local link_page = stat_links[prop]
    if link_page then
        if link_page == label then
            val_str = val_str .. " [[" .. label .. "]]"
        else
            val_str = val_str .. " [[" .. link_page .. "|" .. label .. "]]"
        end
    else
        val_str = val_str .. " " .. label
    end
end
 
--------------------------------------------------
-- Conditional marker
--------------------------------------------------
    if allow_conditional then
        local item = get_json_item(item_name)
        local raw = item and item[prop]
         if type(raw) == "table"
          and (raw.UsageFlags == "ConditionallyApplied"
            or raw.UsageFlags == "ConditionallyEnemyApplied")
        then
             val_str = val_str .. string.format(
                ' <span style="color:#C0C0C0">(%s)</span>',
                lang.get_string("Citadel_Shop_ConditionalAttribute")
            )
         end
         end
     end
     end


     if next(component_scalings) == nil then
return val_str
         return 0
end
 
-- Special formatting for StatusEffect properties (enhanced variant)
local function format_property_for_infobox_enhanced(item_name, prop)
    if prop:match("^StatusEffect") then
        return string.format(
            "[[{{#invoke:Lang|get_string|Citadel_%s}}]] [[{{#invoke:Lang|get_string|Citadel_StatusEffect}}]]",
            prop
        )
    else
        return format_stat_enhanced(item_name, prop)
    end
end
 
-- Returns items that build from this item, sorted by tier
local function get_component_of_sorted(item_input)
    local target_key = nil
     if data[item_input] then
        target_key = item_input
    else
        for k, v in pairs(data) do
            if v.Name == item_input and (v.IsDisabled == false or v.IsDisabled == nil) then
                target_key = k; break
            end
         end
     end
     end
    if not target_key then return {} end


     local base_dps = calculate_dps(base_stats, dps_type)
     local parents = {}
    local scaled_stats = {}
     for key, item in pairs(data) do
     for k, v in pairs(base_stats) do scaled_stats[k] = v end
        if item.IsDisabled ~= true and item.Components and type(item.Components) == "table" then
    for comp_key, scale_val in pairs(component_scalings) do
            for _, comp in ipairs(item.Components) do
        if scaled_stats[comp_key] ~= nil then
                if comp == target_key then
             scaled_stats[comp_key] = scaled_stats[comp_key] + scale_val
                    table.insert(parents, key)
                    break
                end
             end
         end
         end
     end
     end
     local scaled_dps = calculate_dps(scaled_stats, dps_type)
 
     return scaled_dps - base_dps
     table.sort(parents, function(a, b)
        local tier_a = tonumber(data[a] and data[a]["Tier"]) or 0
        local tier_b = tonumber(data[b] and data[b]["Tier"]) or 0
        return tier_a < tier_b
    end)
 
     return parents
end
end


-- =========================================================================
-- Format stat value for Infobox, handling prefix/postfix, CSS, scaling, and conditional
-- Main module function
-- allow_conditional should be true for ImportantProperties, false for regular Properties
-- =========================================================================
local function format_stat(item_name, prop, skip_label, debug, allow_conditional)
    skip_label = skip_label or false
    if allow_conditional == nil then allow_conditional = true end


p.write_hero_comparison_table = function(frame)
     local value, css, loc_key = get_raw_value(item_name, prop)
     local power_increases = tonumber(frame.args[1]) or 0
     if value == nil or value == 0 or value == "" then return nil end
     local spirit_power    = tonumber(frame.args[2]) or 0
    local max_power      = tonumber(frame.args[3]) or 25
    local max_spirit      = tonumber(frame.args[4]) or 500


     local display_scaling_icons = (power_increases == 0 and spirit_power == 0)
     local numeric_val = tonumber(tostring(value):match("^-?%d+%.?%d*")) or 0
    if numeric_val == 0 then return nil end


     local body_str = ""
     local label_key = loc_key:gsub("^#", "")
    if prop == "BuildUpPerShot" then label_key = "BuildupPerShot"
    elseif prop == "DotDuration" then label_key = "DOTDuration" end


     local stats_to_include = {
     local label = skip_label and "" or get_lang_with_fallback(label_key, "_label", "_Label")
        Weapon = {
    local prefix = get_lang_with_fallback(label_key, "_prefix", "_Prefix")
            "DPS", "SustainedDPS", "BulletDamage", "RoundsPerSecond", "FireRate",
    local postfix = get_lang_with_fallback(label_key, "_postfix", "_Postfix")
            "ClipSize", "ReloadTime", "ReloadDelay", "ReloadSingle", "BulletsPerShot", "BulletsPerBurst",
 
            "BurstInterShotInterval", "LightMeleeDamage", "HeavyMeleeDamage",
    if prefix == "" and postfix == "" and label == "" then return nil end
            "BulletSpeed", "FalloffStartRange", "FalloffEndRange",
            "CritDamageBonusPercent", "BulletRadius", "RoundsPerSecondAtMaxSpin", "SpinAcceleration", "SpinDeceleration"
        },
        Vitality = {
            "MaxHealth", "BaseHealthRegen", "BulletResist", "TechResist", "MeleeResist",
            "CritDamageReceivedPercent", "DebuffResist", "BulletLifesteal", "MaxMoveSpeed",
            "SprintSpeed", "StaminaCooldown", "Stamina", "GroundDashSpeed", "GravityChange"
        },
        Spirit = { "TechPower" }
    }


     -- Collect and sort heroes alphabetically, excluding disabled/in-development
     --------------------------------------------------
     local sorted_heroes = {}
     -- Prefix logic
    for hero_key, hero_data in pairs(heroes_data) do
    --------------------------------------------------
         if not hero_data["InDevelopment"] and not hero_data["IsDisabled"] then
    if prefix == "{s:sign}" then
             table.insert(sorted_heroes, {key = hero_key, data = hero_data})
         if numeric_val >= 0 then
            prefix = "+"
        else
             prefix = ""
         end
         end
     end
     end
     table.sort(sorted_heroes, function(a, b)
 
         local function get_sort_name(name)
    --------------------------------------------------
             return (name:gsub("^The ", ""))
    -- Postfix / move speed handling
    --------------------------------------------------
     local suffix = postfix
    if postfix == "HP/s" then suffix = "&#32;" .. postfix end
    if (css == "move_speed" or css == "sprint_speed") and postfix ~= "%" and not has_rate_unit(postfix) then
         local override = get_movespeed_rate_suffix()
        if override then
             suffix = override
        else
            suffix = "m/s" .. get_missing_rate_warning()
         end
         end
        return get_sort_name(a.data["Name"] or a.key) < get_sort_name(b.data["Name"] or b.key)
    end
    end)
 
    --------------------------------------------------
    -- Compose main value string
    --------------------------------------------------
    local val_str = tostring(numeric_val)
    if prefix ~= "" then val_str = prefix .. val_str end
    if suffix ~= "" and not val_str:find(suffix, 1, true) then val_str = val_str .. suffix end


     -- Build a single stat cell (<td>) with data attributes for JS recalculation
     --------------------------------------------------
     local function buildStatCell(hero_data, hero_key, attr_key, is_alt_fire, category)
    -- Scale handling (Boon / Ss)
         -- Conversion factors for stats that need unit changes (e.g. metres → centimetres)
    --------------------------------------------------
         local unit_conversion = {
    local item = get_json_item(item_name)
            BulletRadius = 100  -- metres to centimetres
     local raw_prop = item and item[prop]
         }
    if type(raw_prop) == "table" and raw_prop.Scale and raw_prop.Scale.Value ~= 0 then
        local conv_factor = unit_conversion[attr_key] or 1
         local scale_val = util_module.round_to_sig_fig(raw_prop.Scale.Value, 2)
         local template_name = (raw_prop.Scale.Type == "power_increase") and "Boon" or "Ss"
         val_str = val_str .. " " .. mw.getCurrentFrame():expandTemplate{ title = template_name, args = { scale_val } }
    end


         local base_value
    --------------------------------------------------
         if is_alt_fire then
    -- Label (unstyled)
             if attr_key == "ClipSize" or attr_key == "ReloadTime" then
    --------------------------------------------------
                 base_value = get_nested_stat_value(hero_data, attr_key)
    if label ~= "" then
            elseif hero_data.Weapon and hero_data.Weapon.AltFire
         local link_page = stat_links[prop]
                and hero_data.Weapon.AltFire[attr_key] ~= nil then
         if link_page then
                base_value = hero_data.Weapon.AltFire[attr_key]
             if link_page == label then
                 val_str = val_str .. " [[" .. label .. "]]"
             else
             else
                 base_value = get_nested_stat_value(hero_data, attr_key)
                 val_str = val_str .. " [[" .. link_page .. "|" .. label .. "]]"
             end
             end
         else
         else
             base_value = get_nested_stat_value(hero_data, attr_key)
             val_str = val_str .. " " .. label
         end
         end
    end
    --------------------------------------------------
    -- Conditional marker
    --------------------------------------------------
    if allow_conditional and type(raw_prop) == "table" and
      (raw_prop.UsageFlags == "ConditionallyApplied" or raw_prop.UsageFlags == "ConditionallyEnemyApplied") then
        val_str = val_str .. string.format(
            ' <span style="color:#C0C0C0">(%s)</span>',
            lang.get_string("Citadel_Shop_ConditionalAttribute")
        )
    end
    return val_str
end
-- Special formatting for StatusEffect properties
local function format_property_for_infobox(item_name, prop, skip_label, debug)
    if prop:match("^StatusEffect") then
        return string.format(
            "[[{{#invoke:Lang|get_string|Citadel_%s}}]] [[{{#invoke:Lang|get_string|Citadel_StatusEffect}}]]",
            prop
        )
    else
        return format_stat(item_name, prop, skip_label, debug, true)
    end
end
-- Build raw wikitext for the default tooltip infobox (no preprocess)
local function build_tooltip(frame)
    local item_name = frame.args[1]
    if not item_name then return "Item name missing", 0 end


         -- Apply unit conversion to the base value
    local debug = tonumber(frame.args.debug) or 0
         if type(base_value) == "number" then
    local item = get_json_item(item_name)
             base_value = base_value * conv_factor
    if not item then return "Item not found", debug end
 
    local ItemData = require("Module:ItemData")
    local lines = {
         "{{Infobox item",
        "| item_name = " .. item_name
    }
 
    if item.StreetBrawl == true then
        table.insert(lines, "| street_brawl = true")
    end
 
    ----------------------------------------------------------------------
    -- Check if AbilityCooldown or AbilityChargeUpTime exists in sections
    ----------------------------------------------------------------------
    local cooldown_found_in_sections = false
    local chargeup_found_in_sections = false
    for _, section in ipairs(item.TooltipSections or {}) do
         for _, entry in ipairs(section.Entries or {}) do
            for _, prop in ipairs(entry.Properties or {}) do
                if prop == "AbilityCooldown" then cooldown_found_in_sections = true end
                if prop == "AbilityChargeUpTime" then chargeup_found_in_sections = true end
            end
            for _, prop in ipairs(entry.ImportantProperties or {}) do
                if prop == "AbilityCooldown" then cooldown_found_in_sections = true end
                if prop == "AbilityChargeUpTime" then chargeup_found_in_sections = true end
             end
         end
         end
    end


         local stat_value = base_value
    ----------------------------------------------------------------------
    -- Innate stats
    ----------------------------------------------------------------------
    local innate_index = 1
    for _, section in ipairs(item.TooltipSections or {}) do
         if section.Type == "EArea_Innate" then
            for _, entry in ipairs(section.Entries or {}) do
                for _, prop in ipairs(entry.Properties or {}) do
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime" then
                        local val = format_stat(item_name, prop, false, debug, false)
                        if val then
                            table.insert(lines, string.format("| item_stat%d = %s", innate_index, val))
                            innate_index = innate_index + 1
                        end
                    end
                end
            end
        end
    end


        -- Scaling lookup
    ----------------------------------------------------------------------
        local scaling_data
    -- Passive stats
        if is_alt_fire then
    ----------------------------------------------------------------------
            if attr_key == "ClipSize" or attr_key == "ReloadTime" then
    local passive_counter = 1
                scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key)
    for _, section in ipairs(item.TooltipSections or {}) do
            elseif (attr_key == "DPS" or attr_key == "SustainedDPS")
        if section.Type ~= "EArea_Innate" and section.Type ~= "EArea_Active" then
                and hero_data.Weapon and hero_data.Weapon.AltFire then
            for _, entry in ipairs(section.Entries or {}) do
                -- Alt-fire DPS/SustainedDPS: use explicit key if present, otherwise compute from components
                 local loc = entry.LocString and entry.LocString:gsub("#","") or nil
                local direct_scaling = hero_data_module.get_hero_scaling_data(hero_data, attr_key .. "AltFire")
                if loc then
                 if direct_scaling and next(direct_scaling) ~= nil then
                     table.insert(lines, string.format(
                    scaling_data = direct_scaling
                        "| passive%d_description = {{#invoke:Lang|get_string|%s|item_name=%s}}",
                else
                        passive_counter, loc, item_name
                    local alt_stats = hero_data.Weapon.AltFire
                     ))
                    local dps_type = attr_key == "DPS" and 'burst' or 'sustained'
                    local level_scale = compute_dps_scaling(hero_data, alt_stats, dps_type, "Level")
                    local spirit_scale = compute_dps_scaling(hero_data, alt_stats, dps_type, "Spirit")
                     level_scale = util_module.round_to_sig_fig(level_scale, 5)
                    spirit_scale = util_module.round_to_sig_fig(spirit_scale, 5)
                    scaling_data = {}
                    if level_scale ~= 0 then scaling_data[level_scale] = "Level" end
                    if spirit_scale ~= 0 then scaling_data[spirit_scale] = "Spirit" end
                     if next(scaling_data) == nil then scaling_data = nil end
                 end
                 end
            elseif hero_data.Weapon and hero_data.Weapon.AltFire
 
                 and hero_data.Weapon.AltFire[attr_key] ~= nil then
                local stat_index = 1
                scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key .. "AltFire")
                local seen_props = {}
            else
 
                scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key)
                for _, prop in ipairs(entry.ImportantProperties or {}) do
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime"
                      and is_property_in_lang_data(prop) and not seen_props[prop] then
                        local val = format_stat(item_name, prop, false, debug, true)
                        if val then
                            table.insert(lines, string.format("| passive%d_stat%d = %s", passive_counter, stat_index, val))
                            stat_index = stat_index + 1
                            seen_props[prop] = true
                        end
                    end
                 end
 
                for _, prop in ipairs(entry.Properties or {}) do
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime"
                      and is_property_in_lang_data(prop) and not seen_props[prop] then
                        local val = format_stat(item_name, prop, false, debug, false)
                        if val then
                            table.insert(lines, string.format("| passive%d_stat%d = %s", passive_counter, stat_index, val))
                            stat_index = stat_index + 1
                            seen_props[prop] = true
                        end
                    end
                end
 
                for _, prop_list in ipairs({entry.Properties or {}, entry.ImportantProperties or {}}) do
                    for _, prop in ipairs(prop_list) do
                        if prop == "AbilityCooldown" then
                            local cd = format_stat(item_name, prop, true, debug)
                            if cd then table.insert(lines, string.format("| passive%d_cooldown = %s", passive_counter, cd)) end
                        end
                        if prop == "AbilityChargeUpTime" then
                            local cu = format_stat(item_name, prop, true, debug)
                            if cu then table.insert(lines, string.format("| passive%d_chargeup = %s", passive_counter, cu)) end
                        end
                    end
                end
 
                passive_counter = passive_counter + 1
             end
             end
        else
            scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key)
         end
         end
    end
    ----------------------------------------------------------------------
    -- Active stats
    ----------------------------------------------------------------------
    local active_index = 1
    for _, section in ipairs(item.TooltipSections or {}) do
        if section.Type == "EArea_Active" then
            for _, entry in ipairs(section.Entries or {}) do
                if entry.LocString then
                    table.insert(lines, string.format(
                        "| active%d_description = {{#invoke:Lang|get_string|%s|item_name=%s}}",
                        active_index, entry.LocString:gsub("#",""), item_name
                    ))
                end
                local stat_index = 1
                local seen_props = {}
                for _, prop in ipairs(entry.ImportantProperties or {}) do
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime"
                      and is_property_in_lang_data(prop) and not seen_props[prop] then
                        local val = format_stat(item_name, prop, false, debug, true)
                        if val then
                            table.insert(lines, string.format("| active%d_stat%d = %s", active_index, stat_index, val))
                            stat_index = stat_index + 1
                            seen_props[prop] = true
                        end
                    end
                end


        local scaling_strs = ""
                for _, prop in ipairs(entry.Properties or {}) do
        local spirit_scale = 0
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime"
        local level_scale  = 0
                      and is_property_in_lang_data(prop) and not seen_props[prop] then
        local spirit_val = 0
                        local val = format_stat(item_name, prop, false, debug, false)
        local level_val  = 0
                        if val then
        if scaling_data ~= nil then
                            table.insert(lines, string.format("| active%d_stat%d = %s", active_index, stat_index, val))
            for scaling_val, scaling_type in pairs(scaling_data) do
                            stat_index = stat_index + 1
                if scaling_type == "Spirit" then
                            seen_props[prop] = true
                    spirit_val = scaling_val * conv_factor  -- apply unit conversion
                        end
                elseif scaling_type == "Level" then
                     end
                     level_val = scaling_val * conv_factor  -- apply unit conversion
                 end
                 end
            end


            -- Output Spirit icon before Level icon
                for _, prop_list in ipairs({entry.Properties or {}, entry.ImportantProperties or {}}) do
            local function append_scaling(val, stype)
                    for _, prop in ipairs(prop_list) do
                if val ~= 0 then
                        if prop == "AbilityCooldown" then
                    local scaling_str = hero_data_module.write_scalar_str(val, stype, true)
                            local cd = format_stat(item_name, prop, true, debug)
                    if scaling_str ~= "" then
                            if cd then table.insert(lines, string.format("| active%d_cooldown = %s", active_index, cd)) end
                        scaling_strs = scaling_strs .. " " .. scaling_str
                        end
                        if prop == "AbilityChargeUpTime" then
                            local cu = format_stat(item_name, prop, true, debug)
                            if cu then table.insert(lines, string.format("| active%d_chargeup = %s", active_index, cu)) end
                        end
                     end
                     end
                 end
                 end
            end
            append_scaling(spirit_val, "Spirit")
            append_scaling(level_val, "Level")


            if type(stat_value) == "number" then
                 active_index = active_index + 1
                stat_value = stat_value + (spirit_power * spirit_val)
                 stat_value = stat_value + (power_increases * level_val)
             end
             end
            spirit_scale = spirit_val
            level_scale  = level_val
         end
         end
    end


         if attr_key == "TechPower" and spirit_scale == 0 then
    ----------------------------------------------------------------------
             spirit_scale = 1.0
    -- Global fallback for AbilityCooldown and AbilityChargeUpTime
             if type(stat_value) == "number" then
    -- Attaches to the active section if one exists, otherwise innate
                 stat_value = stat_value + (spirit_power * spirit_scale)
    ----------------------------------------------------------------------
    if not cooldown_found_in_sections and not chargeup_found_in_sections and item.AbilityCooldown then
         local cd = format_stat(item_name, "AbilityCooldown", true, debug)
        if cd then
             local has_active = false
             for _, section in ipairs(item.TooltipSections or {}) do
                if section.Type == "EArea_Active" then has_active = true; break end
            end
            if has_active then
                 table.insert(lines, "| active1_cooldown = " .. cd)
            else
                table.insert(lines, "| item_stat_cooldown = " .. cd)
             end
             end
         end
         end
    end
    if not chargeup_found_in_sections and item.AbilityChargeUpTime then
        local cu = format_stat(item_name, "AbilityChargeUpTime", true, debug)
        if cu then table.insert(lines, "| item_stat_chargeup = " .. cu) end
    end


         local innate_spirit_scale =
    ----------------------------------------------------------------------
            (hero_data["LevelScaling"] and hero_data["LevelScaling"]["TechPower"]) or 0
    -- Components
    ----------------------------------------------------------------------
    for i = 1, 2 do
         local comp = ItemData.get_component_name { args = { item_name, i } }
        if comp and comp ~= "" then
            table.insert(lines, string.format("| component%d_name = %s", i, comp))
        end
    end


         if not display_scaling_icons then scaling_strs = "" end
    ----------------------------------------------------------------------
    -- Is Component Of (sorted by tier)
    ----------------------------------------------------------------------
    local parents = get_component_of_sorted(item_name)
    for i, key in ipairs(parents) do
        local parent_name = data[key] and data[key]["Name"] or ""
         if parent_name ~= "" then
            table.insert(lines, string.format("| iscomponentof%d_name = %s", i, parent_name))
        end
    end


        if type(stat_value) == "boolean" then
    ----------------------------------------------------------------------
            stat_value = tostring(stat_value)
    -- Sounds
         else
    ----------------------------------------------------------------------
             stat_value = util_module.round_to_sig_fig(stat_value, 5)
    for key, value in pairs(frame.args) do
        local index = tostring(key):match("^sound(%d+)$")
         if index and value ~= "" then
             table.insert(lines, string.format("| sound%s = %s", index, value))
         end
         end
    end
    ----------------------------------------------------------------------
    table.insert(lines, "}}")
    return table.concat(lines, "\n"), debug
end


        local cell_inner = string.format(
-- Build raw wikitext for the enhanced tooltip infobox (no preprocess)
            '<span class="stat-num">%s</span><span class="stat-scaling">%s</span>',
local function build_tooltip_enhanced(frame)
            stat_value,
    local item_name = frame.args[1]
            scaling_strs
    if not item_name then return "Item name missing" end
        )


        local weapon_table = hero_data.Weapon
    local item = get_json_item(item_name)
        if is_alt_fire and weapon_table and weapon_table.AltFire then
    if not item then return "Item not found" end
            weapon_table = weapon_table.AltFire
        end
        local hit_once = "false"
        if (attr_key == "DPS" or attr_key == "SustainedDPS")
            and weapon_table
            and weapon_table.HitOnceAcrossAllBullets
        then
            hit_once = "true"
        end


        local data_attrs = string.format(
    local ItemData = require("Module:ItemData")
            'data-stat-name="%s" data-base="%s" data-spirit-scale="%s" data-level-scale="%s" data-innate-spirit-scale="%s" data-sort-value="%s" data-hit-once="%s"',
    local lines = {
            attr_key,
        "{{Infobox item",
            tostring(type(base_value) == "number" and util_module.round_to_sig_fig(base_value, 3) or base_value),
        "| enhanced = true",
            tonumber(spirit_scale) or 0,
        "| item_name = " .. item_name
            (attr_key == "TechPower") and "0" or (tonumber(level_scale) or 0),
    }
            innate_spirit_scale or 0,
            stat_value,
            hit_once
        )


         return string.format('<td style="white-space: nowrap;" %s>%s</td>', data_attrs, cell_inner)
    if item.StreetBrawl == true then
         table.insert(lines, "| street_brawl = true")
     end
     end


     -- Generate a hero's primary stat row
     ----------------------------------------------------------------------
     local function generatePrimaryRow(hero_data, hero_key, has_alt)
     -- Check if AbilityCooldown or AbilityChargeUpTime exists in sections
        local row_str = ""
    ----------------------------------------------------------------------
        local hero_name_local = localize(hero_key, hero_key)
    local cooldown_found_in_sections = false
        local hero_name_en = hero_data["Name"]
    local chargeup_found_in_sections = false
        local template_args = {[1] = hero_name_en, l1 = hero_name_local}
    for _, section in ipairs(item.TooltipSections or {}) do
        local hero_icon = mw.getCurrentFrame():expandTemplate{ title = "Template:HeroIcon", args = template_args }
         for _, entry in ipairs(section.Entries or {}) do
        local hero_cell_content = hero_icon
             for _, prop in ipairs(entry.Properties or {}) do
        if has_alt then
                if prop == "AbilityCooldown" then cooldown_found_in_sections = true end
            hero_cell_content = '[[#alt-fire-' .. hero_key .. '|+]] ' .. hero_cell_content
                 if prop == "AbilityChargeUpTime" then chargeup_found_in_sections = true end
        end
            end
        local sort_name = (hero_data["Name"] or hero_key):gsub("^The ", "")
            for _, prop in ipairs(entry.ImportantProperties or {}) do
        row_str = row_str .. '<td style="position: sticky; left: 0; z-index: 10; background-color: var(--background-color-base-2); isolation: isolate; overflow: hidden; min-width: 150px;" data-sort-value="' .. sort_name .. '">' .. hero_cell_content .. '</td>'
                if prop == "AbilityCooldown" then cooldown_found_in_sections = true end
         for _, category in ipairs(attribute_orders["category_order"]) do
                 if prop == "AbilityChargeUpTime" then chargeup_found_in_sections = true end
             if stats_to_include[category] ~= nil then
                 for _, attr_key in ipairs(stats_to_include[category]) do
                    row_str = row_str .. buildStatCell(hero_data, hero_key, attr_key, false, category)
                 end
             end
             end
         end
         end
        return "<tr>" .. row_str .. "</tr>"
     end
     end


     -- Generate an expandable alt-fire sub-row (weapon stats only)
     ----------------------------------------------------------------------
     local function generateAltFireSubRow(hero_data, hero_key)
     -- Innate stats (Enhanced)
        local row_str = ""
    ----------------------------------------------------------------------
        local hero_name_local = localize(hero_key, hero_key)
    local innate_index = 1
        local hero_name_en = hero_data["Name"]
    for _, section in ipairs(item.TooltipSections or {}) do
        local template_args = {[1] = hero_name_en, l1 = hero_name_local}
         if section.Type == "EArea_Innate" then
        local hero_icon = mw.getCurrentFrame():expandTemplate{ title = "Template:HeroIcon", args = template_args }
            for _, entry in ipairs(section.Entries or {}) do
        local hero_cell_content = hero_icon .. ' <small style="color:#aaa;">(Alt‑fire)</small>'
                 for _, prop in ipairs(entry.Properties or {}) do
         local sort_name = (hero_data["Name"] or hero_key):gsub("^The ", "")
                     if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime"
        row_str = row_str .. '<td style="position: sticky; left: 0; z-index: 9; background-color: #202122; isolation: isolate; overflow: hidden; min-width: 150px;" data-sort-value="' .. sort_name .. '">' .. hero_cell_content .. '</td>'
                      and is_property_in_lang_data(prop) then
        for _, category in ipairs(attribute_orders["category_order"]) do
                         local val = format_stat_enhanced(item_name, prop, nil, false)
            if stats_to_include[category] ~= nil then
                        if val then
                 for _, attr_key in ipairs(stats_to_include[category]) do
                            table.insert(
                     if category == "Weapon" then
                                lines,
                         row_str = row_str .. buildStatCell(hero_data, hero_key, attr_key, true, category)
                                string.format(
                    else
                                    "| item_stat%d = %s",
                         row_str = row_str .. '<td></td>'
                                    innate_index,
                                    val
                                )
                            )
                            innate_index = innate_index + 1
                         end
                     end
                     end
                 end
                 end
             end
             end
         end
         end
        return '<tr class="alt-fire-sub" data-parent="' .. hero_key .. '">' .. row_str .. '</tr>'
     end
     end


     -- Build all body rows
     ----------------------------------------------------------------------
     for _, hero_entry in ipairs(sorted_heroes) do
    -- Passive stats (Enhanced)
         local hero_key  = hero_entry.key
    ----------------------------------------------------------------------
        local hero_data = hero_entry.data
    local passive_counter = 1
        local has_alt = hero_data.Weapon and hero_data.Weapon.AltFire and true or false
     for _, section in ipairs(item.TooltipSections or {}) do
        body_str = body_str .. generatePrimaryRow(hero_data, hero_key, has_alt)
         if section.Type ~= "EArea_Innate" and section.Type ~= "EArea_Active" then
        if has_alt then
            for _, entry in ipairs(section.Entries or {}) do
            body_str = body_str .. generateAltFireSubRow(hero_data, hero_key)
                if entry.LocString then
        end
                    table.insert(lines, string.format(
    end
                        "| passive%d_description = {{#invoke:Lang|get_string|%s|item_name=%s}}",
                        passive_counter,
                        entry.LocString:gsub("#",""),
                        item_name
                    ))
                end
 
                local stat_index = 1
                local seen = {}
 
                for _, prop in ipairs(entry.ImportantProperties or {}) do
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime"
                      and is_property_in_lang_data(prop) and not seen[prop] then
                        local val = format_property_for_infobox_enhanced(item_name, prop)
                        if val then
                            table.insert(
                                lines,
                                string.format(
                                    "| passive%d_stat%d = %s",
                                    passive_counter,
                                    stat_index,
                                    val
                                )
                            )
                            stat_index = stat_index + 1
                            seen[prop] = true
                        end
                    end
                end


    -- Pre-pass: determine which stats have any scaling, and which have both types.
                for _, prop in ipairs(entry.Properties or {}) do
    -- Used to set column widths in the header.
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime"
    local stats_with_any_scaling  = {}
                      and is_property_in_lang_data(prop) and not seen[prop] then
    local stats_with_both_scaling = {}
                        local val = format_stat_enhanced(item_name, prop, nil, false)
    for _, hero_entry in ipairs(sorted_heroes) do
                        if val then
        local hero_data = hero_entry.data
                            table.insert(
        for _, category in ipairs(attribute_orders["category_order"]) do
                                lines,
            if stats_to_include[category] ~= nil then
                                string.format(
                for _, attr_key in ipairs(stats_to_include[category]) do
                                    "| passive%d_stat%d = %s",
                    -- Primary scaling
                                    passive_counter,
                    local scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key)
                                    stat_index,
                    if scaling_data ~= nil and next(scaling_data) ~= nil then
                                    val
                        stats_with_any_scaling[attr_key] = true
                                )
                        local has_spirit, has_level = false, false
                            )
                        for _, scaling_type in pairs(scaling_data) do
                             stat_index = stat_index + 1
                             if scaling_type == "Spirit" then has_spirit = true
                             seen[prop] = true
                             elseif scaling_type == "Level" then has_level = true end
                         end
                         end
                        if has_spirit and has_level then stats_with_both_scaling[attr_key] = true end
                     end
                     end
                end


                    -- Alt-fire scaling (only for stats present in the AltFire table)
                for _, prop_list in ipairs({entry.Properties or {}, entry.ImportantProperties or {}}) do
                    if hero_data.Weapon
                     for _, prop in ipairs(prop_list) do
                        and hero_data.Weapon.AltFire
                         if prop == "AbilityCooldown" then
                        and hero_data.Weapon.AltFire[attr_key] ~= nil
                             local cd = format_stat_enhanced(item_name, prop, true)
                     then
                             if cd then
                         if attr_key ~= "ClipSize" then
                                 table.insert(lines, string.format("| passive%d_cooldown = %s", passive_counter, cd))
                             local alt_scaling = hero_data_module.get_hero_scaling_data(hero_data, attr_key .. "AltFire")
                            end
                             if alt_scaling and next(alt_scaling) ~= nil then
                        end
                                 stats_with_any_scaling[attr_key] = true
                        if prop == "AbilityChargeUpTime" then
                                local has_spirit_alt, has_level_alt = false, false
                            local cu = format_stat_enhanced(item_name, prop, true)
                                for _, stype in pairs(alt_scaling) do
                            if cu then
                                    if stype == "Spirit" then has_spirit_alt = true
                                table.insert(lines, string.format("| passive%d_chargeup = %s", passive_counter, cu))
                                    elseif stype == "Level" then has_level_alt = true end
                                end
                                if has_spirit_alt and has_level_alt then stats_with_both_scaling[attr_key] = true end
                            else
                                if attr_key == "DPS" or attr_key == "SustainedDPS" then
                                    local alt_stats = hero_data.Weapon.AltFire
                                    local dps_type = attr_key == "DPS" and 'burst' or 'sustained'
                                    local lv = compute_dps_scaling(hero_data, alt_stats, dps_type, "Level")
                                    local sp = compute_dps_scaling(hero_data, alt_stats, dps_type, "Spirit")
                                    lv = util_module.round_to_sig_fig(lv, 5)
                                    sp = util_module.round_to_sig_fig(sp, 5)
                                    if lv ~= 0 or sp ~= 0 then
                                        stats_with_any_scaling[attr_key] = true
                                        if lv ~= 0 and sp ~= 0 then
                                            stats_with_both_scaling[attr_key] = true
                                        end
                                    end
                                end
                             end
                             end
                         end
                         end
                     end
                     end
                 end
                 end
                passive_counter = passive_counter + 1
             end
             end
         end
         end
     end
     end


     -- Build table header row
     ----------------------------------------------------------------------
     local headers_str = '<th style="position: sticky; left: 0; top: -1px; z-index: 12; background-color: var(--background-color-base-5); isolation: isolate; min-width: 150px;">Hero</th>'
    -- Active stats (Enhanced)
     local category_data = attribute_module.get_category_data()
     ----------------------------------------------------------------------
     local postfix_key_map = {
     local active_index = 1
         ["ReloadDelay"] = "StatDesc_ReloadTime_postfix",
     for _, section in ipairs(item.TooltipSections or {}) do
        ["BulletsPerShot"] = "",
         if section.Type == "EArea_Active" then
        ["BulletsPerBurst"] = "",
            for _, entry in ipairs(section.Entries or {}) do
        ["BurstInterShotInterval"] = "StatDesc_ReloadTime_postfix",
                if entry.LocString then
        ["ReloadSingle"] = "",
                    table.insert(lines, string.format(
        ["BonusAttackRange"] = "StatDesc_WeaponRangeFalloffMax_postfix",
                        "| active%d_description = {{#invoke:Lang|get_string|%s|item_name=%s}}",
        ["SustainedDPS"] = "DPS_postfix",
                        active_index,
        ["RoundsPerSecondAtMaxSpin"] = "",
                        entry.LocString:gsub("#",""),
        ["CritDamageBonusPercent"]  = "StatDesc_CritDamageBonusScale_postfix",
                        item_name
["CritDamageReceivedPercent"] = "StatDesc_CritDamageReceivedScale_postfix",
                    ))
        ["BulletLifesteal"] = "BulletLifestealPercentHero_postfix",
                end
        ["GroundDashSpeed"] = "DashSpeed_postfix"
 
    }
                local stat_index = 1
    for _, category in ipairs(attribute_orders["category_order"]) do
                local seen = {}
        local category_attrs = attributes_data[category]
 
        local category_rgb  = category_data[category]["rgb"]
                for _, prop in ipairs(entry.ImportantProperties or {}) do
        if stats_to_include[category] ~= nil then
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime"
            for _, attr_key in ipairs(stats_to_include[category]) do
                      and is_property_in_lang_data(prop) and not seen[prop] then
                local attr_data = category_attrs[attr_key]
                        local val = format_property_for_infobox_enhanced(item_name, prop)
               
                        if val then
                -- Fallback: if the stat is missing from this category, check all other categories
                            table.insert(
                if attr_data == nil then
                                lines,
                    for _, fallback_cat_data in pairs(attributes_data) do
                                string.format(
                        if fallback_cat_data[attr_key] ~= nil then
                                    "| active%d_stat%d = %s",
                             attr_data = fallback_cat_data[attr_key]
                                    active_index,
                            break
                                    stat_index,
                                    val
                                )
                            )
                            stat_index = stat_index + 1
                             seen[prop] = true
                         end
                         end
                     end
                     end
                 end
                 end


                 local attr_localized, postfix
                 for _, prop in ipairs(entry.Properties or {}) do
                if attr_data ~= nil then
                    if prop ~= "AbilityCooldown" and prop ~= "AbilityChargeUpTime"
                    attr_localized = lang_module.get_string(attr_data["label"])
                      and is_property_in_lang_data(prop) and not seen[prop] then
                    if attr_localized == nil or attr_localized == "" then
                        local val = format_stat_enhanced(item_name, prop, nil, false)
                         attr_localized = util_module.add_space_before_cap(attr_key)
                        if val then
                            table.insert(
                                lines,
                                string.format(
                                    "| active%d_stat%d = %s",
                                    active_index,
                                    stat_index,
                                    val
                                )
                            )
                            stat_index = stat_index + 1
                            seen[prop] = true
                         end
                     end
                     end
                    postfix = lang_module.get_string(attr_data["postfix"])
                    if postfix == nil or postfix == "" then postfix = ""
                    else postfix = " (" .. postfix .. ")" end
                    if attr_key == "BulletRadius" then postfix = " (cm)" end
                else
                    attr_localized = dictionary_module.translate(attr_key)
                    postfix = lang_module.get_string(postfix_key_map[attr_key])
                    if postfix == nil then return "attr_key " .. attr_key .. " must be added to postfix_key_map" end
                    if postfix ~= "" then postfix = " (" .. postfix .. ")" end
                 end
                 end
                 local th_style = 'position: sticky; top: -1px; z-index: 3; background-color: rgb(' .. category_rgb .. ');'
 
                if stats_with_both_scaling[attr_key] then
                 for _, prop_list in ipairs({entry.Properties or {}, entry.ImportantProperties or {}}) do
                    th_style = th_style .. ' min-width: 130px;'
                    for _, prop in ipairs(prop_list) do
                elseif stats_with_any_scaling[attr_key] then
                        if prop == "AbilityCooldown" then
                    th_style = th_style .. ' min-width: 75px;'
                            local cd = format_stat_enhanced(item_name, prop, true)
                            if cd then
                                table.insert(lines, string.format("| active%d_cooldown = %s", active_index, cd))
                            end
                        end
                        if prop == "AbilityChargeUpTime" then
                            local cu = format_stat_enhanced(item_name, prop, true)
                            if cu then
                                table.insert(lines, string.format("| active%d_chargeup = %s", active_index, cu))
                            end
                        end
                    end
                 end
                 end
                 headers_str = headers_str .. '<th style="' .. th_style .. '">' .. attr_localized .. postfix .. "</th>"
 
                 active_index = active_index + 1
             end
             end
         end
         end
     end
     end
    headers_str = "<tr>" .. headers_str .. "</tr>"


     return string.format(
     ----------------------------------------------------------------------
         '<div id="hero-comparison-container" data-max-power="%s" data-max-spirit="%s">' ..
    -- Global fallback for AbilityCooldown and AbilityChargeUpTime
         '<div style="overflow: auto; max-height: 70vh; width: 100%%;">' ..
    -- Attaches to the active section if one exists, otherwise innate
         '<table class="wikitable sortable" style="table-layout: auto; width: 100%%;" id="hero-comparison-table">%s%s</table>' ..
    ----------------------------------------------------------------------
         '</div></div>',
    if not cooldown_found_in_sections and not chargeup_found_in_sections and item.AbilityCooldown then
        max_power, max_spirit, headers_str, body_str
        local cd = format_stat_enhanced(item_name, "AbilityCooldown", true)
     )
        if cd then
            local has_active = false
            for _, section in ipairs(item.TooltipSections or {}) do
                if section.Type == "EArea_Active" then has_active = true; break end
            end
            if has_active then
                table.insert(lines, "| active1_cooldown = " .. cd)
            else
                table.insert(lines, "| passive1_cooldown = " .. cd)
            end
        end
    end
 
    if not chargeup_found_in_sections and item.AbilityChargeUpTime then
        local cu = format_stat_enhanced(item_name, "AbilityChargeUpTime", true)
         if cu then table.insert(lines, "| passive1_chargeup = " .. cu) end
    end
 
    ----------------------------------------------------------------------
    -- Components
    ----------------------------------------------------------------------
    for i = 1, 2 do
        local comp = ItemData.get_component_name { args = { item_name, i } }
        if comp and comp ~= "" then
            table.insert(lines, string.format("| component%d_name = %s", i, comp))
        end
    end
 
    ----------------------------------------------------------------------
    -- Is Component Of (sorted by tier)
    ----------------------------------------------------------------------
    local parents = get_component_of_sorted(item_name)
    for i, key in ipairs(parents) do
        local parent_name = data[key] and data[key]["Name"] or ""
         if parent_name ~= "" then
            table.insert(lines, string.format("| iscomponentof%d_name = %s", i, parent_name))
        end
    end
 
    ----------------------------------------------------------------------
    -- Sounds
    ----------------------------------------------------------------------
    for key, value in pairs(frame.args) do
         local index = tostring(key):match("^sound(%d+)$")
        if index and value ~= "" then
            table.insert(lines, string.format("| sound%s = %s", index, value))
        end
    end
 
    ----------------------------------------------------------------------
    table.insert(lines, "}}")
    return table.concat(lines, "\n")
end
 
function p.fill_tooltip(frame)
    local output, debug = build_tooltip(frame)
    if debug == 1 or debug == 2 then
         return output
    end
    return mw.getCurrentFrame():preprocess(output)
end
 
function p.fill_tooltip_enhanced(frame)
    return mw.getCurrentFrame():preprocess(build_tooltip_enhanced(frame))
end
 
function p.render(frame)
    local output_lines = { "<tabber>" }
    table.insert(output_lines, "Default=")
    local tooltip, _ = build_tooltip(frame)
    table.insert(output_lines, tooltip)
    table.insert(output_lines, "|-|")
    table.insert(output_lines, "Enhanced=")
    table.insert(output_lines, build_tooltip_enhanced(frame))
    table.insert(output_lines, "</tabber>")
     return mw.getCurrentFrame():preprocess(table.concat(output_lines, "\n"))
end
end


return p
return p
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: