Module:Sandbox/LVL: Difference between revisionsGive feedback
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
| Line 40: | Line 40: | ||
local body_str = "" | local body_str = "" | ||
local stats_to_include = { | local stats_to_include = { | ||
| Line 104: | Line 100: | ||
end) | end) | ||
-- | -- Shared helper: build a single stat cell (returns the <td> string) | ||
local function | local function buildStatCell(hero_data, hero_key, attr_key, is_alt_fire, category) | ||
local | local base_value | ||
if is_alt_fire then | |||
-- ClipSize always uses primary | |||
if attr_key == "ClipSize" then | |||
base_value = get_nested_stat_value(hero_data, attr_key) | |||
local | elseif hero_data.Weapon and hero_data.Weapon.AltFire | ||
and hero_data.Weapon.AltFire[attr_key] ~= nil then | |||
base_value = hero_data.Weapon.AltFire[attr_key] | |||
else | |||
base_value = get_nested_stat_value(hero_data, attr_key) | |||
end | |||
else | |||
base_value = get_nested_stat_value(hero_data, attr_key) | |||
end | |||
local stat_value = base_value | |||
-- Scaling lookup | |||
local scaling_data | |||
if is_alt_fire then | if is_alt_fire then | ||
if attr_key == "ClipSize" then | |||
scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key) | |||
elseif hero_key == "hero_shiv" and attr_key == "BulletDamage" then | |||
scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key) | |||
elseif hero_data.Weapon and hero_data.Weapon.AltFire | |||
and hero_data.Weapon.AltFire[attr_key] ~= nil then | |||
scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key .. "AltFire") | |||
else | |||
scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key) | |||
end | |||
else | |||
scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key) | |||
end | end | ||
local scaling_strs = "" | |||
local spirit_scale = 0 | |||
local level_scale = 0 | |||
if scaling_data ~= nil then | |||
for scaling_val, scaling_type in pairs(scaling_data) do | |||
local scaling_str = hero_data_module.write_scalar_str(scaling_val, scaling_type, true) | |||
if scaling_str ~= "" then scaling_str = " " .. scaling_str end | |||
if scaling_str ~= nil then | |||
scaling_strs = scaling_strs .. scaling_str | |||
end | |||
if scaling_type == "Spirit" then | |||
spirit_scale = scaling_val | |||
stat_value = stat_value + (spirit_power * scaling_val) | |||
elseif scaling_type == "Level" then | |||
level_scale = scaling_val | |||
stat_value = stat_value + (power_increases * scaling_val) | |||
end | |||
end | |||
end | |||
if attr_key == "TechPower" and spirit_scale == 0 then | |||
spirit_scale = 1.0 | |||
stat_value = stat_value + (spirit_power * spirit_scale) | |||
end | |||
local innate_spirit_scale = | |||
(hero_data["LevelScaling"] and hero_data["LevelScaling"]["TechPower"]) or 0 | |||
if not display_scaling_icons then scaling_strs = "" end | |||
if type(stat_value) == "boolean" then | |||
stat_value = tostring(stat_value) | |||
else | |||
stat_value = util_module.round_to_sig_fig(stat_value, 3) | |||
end | |||
local cell_inner = string.format( | |||
'<span class="stat-num">%s</span><span class="stat-scaling">%s</span>', | |||
stat_value, | |||
scaling_strs | |||
) | |||
local weapon_table = hero_data.Weapon | |||
if is_alt_fire and weapon_table and weapon_table.AltFire then | |||
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( | |||
'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"', | |||
attr_key, | |||
tostring(type(base_value) == "number" and util_module.round_to_sig_fig(base_value, 3) or base_value), | |||
tonumber(spirit_scale) or 0, | |||
(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) | |||
end | |||
-- Build primary row (with optional rowspan on hero cell) | |||
local function generatePrimaryRow(hero_data, hero_key, has_alt) | |||
local row_str = "" | |||
local hero_name_local = localize(hero_key, hero_key) | |||
local hero_name_en = hero_data["Name"] | |||
local template_args = {[1] = hero_name_en, l1 = hero_name_local} | |||
local hero_icon = mw.getCurrentFrame():expandTemplate{ title = "Template:HeroIcon", args = template_args } | |||
local hero_cell_content = hero_icon | |||
if has_alt then | |||
hero_cell_content = '<button class="toggle-alt-fire" data-hero-key="' .. hero_key .. '">+</button> ' .. hero_cell_content | |||
end | |||
local rowspan_attr = has_alt and ' rowspan="2"' or '' | |||
row_str = row_str .. '<td style="position: sticky; left: 0; z-index: 10; background-color: #202122; isolation: isolate;"' .. rowspan_attr .. '>' .. hero_cell_content .. '</td>' | |||
row_str = row_str .. | for _, category in ipairs(attribute_orders["category_order"]) do | ||
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 | ||
| Line 235: | Line 232: | ||
end | end | ||
-- Build | -- Build alt-fire sub-row (only weapon cells, other cells empty) | ||
local function generateAltFireSubRow(hero_data, hero_key) | |||
local row_str = "" | |||
-- No hero cell (covered by rowspan) | |||
for _, category in ipairs(attribute_orders["category_order"]) do | |||
if stats_to_include[category] ~= nil then | |||
for _, attr_key in ipairs(stats_to_include[category]) do | |||
if category == "Weapon" then | |||
row_str = row_str .. buildStatCell(hero_data, hero_key, attr_key, true, category) | |||
else | |||
-- Empty cell to preserve column layout | |||
row_str = row_str .. '<td></td>' | |||
end | |||
end | |||
end | |||
end | |||
return '<tr class="alt-fire-sub" data-parent="' .. hero_key .. '">' .. row_str .. '</tr>' | |||
end | |||
-- Generate all rows | |||
for _, hero_entry in ipairs(sorted_heroes) do | for _, hero_entry in ipairs(sorted_heroes) do | ||
local hero_key = hero_entry.key | local hero_key = hero_entry.key | ||
local hero_data = hero_entry.data | local hero_data = hero_entry.data | ||
local has_alt = hero_data.Weapon and hero_data.Weapon.AltFire and true or false | |||
body_str = body_str .. | body_str = body_str .. generatePrimaryRow(hero_data, hero_key, has_alt) | ||
if | if has_alt then | ||
body_str = body_str .. | body_str = body_str .. generateAltFireSubRow(hero_data, hero_key) | ||
end | end | ||
end | end | ||
-- Pre-pass: | -- Pre-pass: scaling info (unchanged, but now includes alt-fire where appropriate) | ||
local stats_with_any_scaling = {} | local stats_with_any_scaling = {} | ||
local stats_with_both_scaling = {} | local stats_with_both_scaling = {} | ||
| Line 270: | Line 288: | ||
end | end | ||
-- Alt-fire scaling (only | -- Alt-fire scaling (only if the stat exists in AltFire and we actually use alt scaling) | ||
if hero_data.Weapon | if hero_data.Weapon | ||
and hero_data.Weapon.AltFire | and hero_data.Weapon.AltFire | ||
and hero_data.Weapon.AltFire[attr_key] ~= nil | and hero_data.Weapon.AltFire[attr_key] ~= nil | ||
then | then | ||
-- | -- ClipSize always uses primary, so skip alt check | ||
if attr_key | if attr_key ~= "ClipSize" then | ||
local alt_scaling = hero_data_module.get_hero_scaling_data(hero_data, attr_key .. "AltFire") | local alt_scaling = hero_data_module.get_hero_scaling_data(hero_data, attr_key .. "AltFire") | ||
if alt_scaling ~= nil and next(alt_scaling) ~= nil then | if alt_scaling ~= nil and next(alt_scaling) ~= nil then | ||
stats_with_any_scaling[attr_key] = true | -- Shiv BulletDamage uses primary scaling, already counted | ||
if not (hero_entry.key == "hero_shiv" and attr_key == "BulletDamage") then | |||
stats_with_any_scaling[attr_key] = true | |||
local has_spirit_alt = false | |||
local has_level_alt = false | |||
for _, scaling_type in pairs(alt_scaling) do | |||
if scaling_type == "Spirit" then has_spirit_alt = true end | |||
if scaling_type == "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 | |||
end | end | ||
end | end | ||
| Line 301: | Line 318: | ||
end | end | ||
-- Build header row | -- Build header row (unchanged) | ||
local headers_str = '<th style="position: sticky; left: 0; top: -1px; z-index: 12; background-color: #27292d; isolation: isolate;">Hero</th>' | local headers_str = '<th style="position: sticky; left: 0; top: -1px; z-index: 12; background-color: #27292d; isolation: isolate;">Hero</th>' | ||
local category_data = attribute_module.get_category_data() | local category_data = attribute_module.get_category_data() | ||
| Line 367: | Line 384: | ||
return string.format( | return string.format( | ||
'<div id="hero-comparison-container" data-max-power="%s" data-max-spirit="%s">' .. | '<div id="hero-comparison-container" data-max-power="%s" data-max-spirit="%s">' .. | ||
'<style>.alt-fire-sub { display: none; } .alt-fire-sub.visible { display: table-row; } button.toggle-alt-fire { background: none; border: none; color: inherit; cursor: pointer; font-weight: bold; width: 1.8em; text-align: center; padding: 0; }</style>' .. | |||
'<div style="overflow: auto; max-height: 70vh; width: 100%%;">' .. | '<div style="overflow: auto; max-height: 70vh; width: 100%%;">' .. | ||
'<table class="wikitable sortable" style="table-layout: auto; width: 100%%;" id="hero-comparison-table">%s%s</table>' .. | '<table class="wikitable sortable" style="table-layout: auto; width: 100%%;" id="hero-comparison-table">%s%s</table>' .. | ||