Editing Module:Sandbox/LVLGive feedback
Jump to navigation
Jump to search
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 30: | Line 30: | ||
end | end | ||
-- | -- {{#invoke:HeroComparisonTable|write_hero_comparison_table|POWER_INCREASES|SPIRIT_POWER|MAX_POWER|MAX_SPIRIT}} | ||
p.write_hero_comparison_table = function(frame) | p.write_hero_comparison_table = function(frame) | ||
local power_increases = tonumber(frame.args[1]) or 0 | local power_increases = tonumber(frame.args[1]) or 0 | ||
| Line 123: | Line 39: | ||
local display_scaling_icons = (power_increases == 0 and spirit_power == 0) | local display_scaling_icons = (power_increases == 0 and spirit_power == 0) | ||
local body_str = "" | local body_str = "" | ||
local stats_to_include = { | local stats_to_include = { | ||
Weapon = { | Weapon = { | ||
"DPS", "SustainedDPS", "BulletDamage", "RoundsPerSecond", "FireRate", | "DPS", | ||
"ClipSize", "ReloadTime", "ReloadDelay", | "SustainedDPS", | ||
"BurstInterShotInterval", "LightMeleeDamage", "HeavyMeleeDamage", | "BulletDamage", | ||
"BulletSpeed", "FalloffStartRange", "FalloffEndRange", | "RoundsPerSecond", | ||
"CritDamageBonusPercent", | "FireRate", | ||
"ClipSize", | |||
"ReloadTime", | |||
"ReloadDelay", | |||
"BulletsPerShot", | |||
"BulletsPerBurst", | |||
"BurstInterShotInterval", | |||
"LightMeleeDamage", | |||
"HeavyMeleeDamage", | |||
"ReloadSingle", | |||
"BulletSpeed", | |||
"FalloffStartRange", | |||
"FalloffEndRange", | |||
"CritDamageBonusPercent", | |||
"RoundsPerSecondAtMaxSpin", | |||
"SpinAcceleration", | |||
"SpinDeceleration" | |||
}, | }, | ||
Vitality = { | Vitality = { | ||
"MaxHealth", "BaseHealthRegen", "BulletResist", "TechResist", "MeleeResist", | "MaxHealth", | ||
"CritDamageReceivedPercent", "DebuffResist", "BulletLifesteal", "MaxMoveSpeed", | "BaseHealthRegen", | ||
"SprintSpeed", "StaminaCooldown", "Stamina", "GroundDashSpeed", "GravityChange" | "BulletResist", | ||
"TechResist", | |||
"MeleeResist", | |||
"CritDamageReceivedPercent", | |||
"DebuffResist", | |||
"BulletLifesteal", | |||
"MaxMoveSpeed", | |||
"SprintSpeed", | |||
"StaminaCooldown", | |||
"Stamina", | |||
"GroundDashSpeed", | |||
"GravityChange" | |||
}, | }, | ||
Spirit = { "TechPower" } | Spirit = { | ||
"TechPower" | |||
} | |||
} | } | ||
-- Collect and sort heroes alphabetically, | -- Collect and sort heroes alphabetically, skipping disabled/in-development | ||
local sorted_heroes = {} | local sorted_heroes = {} | ||
for hero_key, hero_data in pairs(heroes_data) do | for hero_key, hero_data in pairs(heroes_data) do | ||
| Line 155: | Line 100: | ||
end) | end) | ||
-- | -- Shared helper: build a single stat cell (returns the <td> string) | ||
local function buildStatCell(hero_data, hero_key, attr_key, is_alt_fire, category) | local function buildStatCell(hero_data, hero_key, attr_key, is_alt_fire, category) | ||
local base_value | local base_value | ||
if is_alt_fire then | if is_alt_fire then | ||
if attr_key == "ClipSize | -- ClipSize always uses primary | ||
if attr_key == "ClipSize" then | |||
base_value = get_nested_stat_value(hero_data, attr_key) | base_value = get_nested_stat_value(hero_data, attr_key) | ||
elseif hero_data.Weapon and hero_data.Weapon.AltFire | elseif hero_data.Weapon and hero_data.Weapon.AltFire | ||
| Line 175: | Line 115: | ||
else | else | ||
base_value = get_nested_stat_value(hero_data, attr_key) | base_value = get_nested_stat_value(hero_data, attr_key) | ||
end | end | ||
| Line 187: | Line 122: | ||
local scaling_data | local scaling_data | ||
if is_alt_fire then | if is_alt_fire then | ||
if attr_key == "ClipSize" | 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) | scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key) | ||
elseif hero_data.Weapon and hero_data.Weapon.AltFire | elseif hero_data.Weapon and hero_data.Weapon.AltFire | ||
and hero_data.Weapon.AltFire[attr_key] ~= nil then | and hero_data.Weapon.AltFire[attr_key] ~= nil then | ||
| Line 220: | Line 139: | ||
local spirit_scale = 0 | local spirit_scale = 0 | ||
local level_scale = 0 | local level_scale = 0 | ||
if scaling_data ~= nil then | if scaling_data ~= nil then | ||
for scaling_val, scaling_type in pairs(scaling_data) do | 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 | if scaling_type == "Spirit" then | ||
spirit_scale = scaling_val | |||
stat_value = stat_value + (spirit_power * scaling_val) | |||
elseif scaling_type == "Level" then | elseif scaling_type == "Level" then | ||
level_scale = scaling_val | |||
stat_value = stat_value + (power_increases * scaling_val) | |||
end | end | ||
end | end | ||
end | end | ||
if attr_key == "TechPower" and spirit_scale == 0 then | if attr_key == "TechPower" and spirit_scale == 0 then | ||
spirit_scale = 1.0 | spirit_scale = 1.0 | ||
stat_value = stat_value + (spirit_power * spirit_scale) | |||
end | end | ||
| Line 267: | Line 171: | ||
stat_value = tostring(stat_value) | stat_value = tostring(stat_value) | ||
else | else | ||
stat_value = util_module.round_to_sig_fig(stat_value, | stat_value = util_module.round_to_sig_fig(stat_value, 3) | ||
end | end | ||
| Line 302: | Line 206: | ||
end | end | ||
-- | -- Build primary row (with optional rowspan on hero cell) | ||
local function generatePrimaryRow(hero_data, hero_key, has_alt) | local function generatePrimaryRow(hero_data, hero_key, has_alt) | ||
local row_str = "" | local row_str = "" | ||
| Line 309: | Line 213: | ||
local template_args = {[1] = hero_name_en, l1 = hero_name_local} | 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_icon = mw.getCurrentFrame():expandTemplate{ title = "Template:HeroIcon", args = template_args } | ||
local hero_cell_content = hero_icon | local hero_cell_content = hero_icon | ||
if has_alt then | if has_alt then | ||
hero_cell_content = ' | hero_cell_content = '<button class="toggle-alt-fire" data-hero-key="' .. hero_key .. '">+</button> ' .. hero_cell_content | ||
end | end | ||
local | local rowspan_attr = has_alt and ' rowspan="2"' or '' | ||
row_str = row_str .. '<td style="position: sticky; left: 0; z-index: 10; background-color: | row_str = row_str .. '<td style="position: sticky; left: 0; z-index: 10; background-color: #202122; isolation: isolate;"' .. rowspan_attr .. '>' .. hero_cell_content .. '</td>' | ||
for _, category in ipairs(attribute_orders["category_order"]) do | for _, category in ipairs(attribute_orders["category_order"]) do | ||
if stats_to_include[category] ~= nil then | if stats_to_include[category] ~= nil then | ||
| Line 322: | Line 228: | ||
end | end | ||
end | end | ||
return "<tr>" .. row_str .. "</tr>" | return "<tr>" .. row_str .. "</tr>" | ||
end | end | ||
-- | -- Build alt-fire sub-row (only weapon cells, other cells empty) | ||
local function generateAltFireSubRow(hero_data, hero_key) | local function generateAltFireSubRow(hero_data, hero_key) | ||
local row_str = "" | local row_str = "" | ||
-- No hero cell (covered by rowspan) | |||
for _, category in ipairs(attribute_orders["category_order"]) do | for _, category in ipairs(attribute_orders["category_order"]) do | ||
if stats_to_include[category] ~= nil then | if stats_to_include[category] ~= nil then | ||
| Line 341: | Line 243: | ||
row_str = row_str .. buildStatCell(hero_data, hero_key, attr_key, true, category) | row_str = row_str .. buildStatCell(hero_data, hero_key, attr_key, true, category) | ||
else | else | ||
-- Empty cell to preserve column layout | |||
row_str = row_str .. '<td></td>' | row_str = row_str .. '<td></td>' | ||
end | end | ||
| Line 346: | Line 249: | ||
end | end | ||
end | end | ||
return '<tr class="alt-fire-sub" data-parent="' .. hero_key .. '">' .. row_str .. '</tr>' | return '<tr class="alt-fire-sub" data-parent="' .. hero_key .. '">' .. row_str .. '</tr>' | ||
end | 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 | local has_alt = hero_data.Weapon and hero_data.Weapon.AltFire and true or false | ||
body_str = body_str .. generatePrimaryRow(hero_data, hero_key, has_alt) | body_str = body_str .. generatePrimaryRow(hero_data, hero_key, has_alt) | ||
if has_alt then | if has_alt then | ||
| Line 360: | Line 265: | ||
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 373: | Line 277: | ||
if scaling_data ~= nil and next(scaling_data) ~= nil then | if scaling_data ~= nil and next(scaling_data) ~= nil then | ||
stats_with_any_scaling[attr_key] = true | stats_with_any_scaling[attr_key] = true | ||
local has_spirit | local has_spirit = false | ||
local has_level = false | |||
for _, scaling_type in pairs(scaling_data) do | for _, scaling_type in pairs(scaling_data) do | ||
if scaling_type == "Spirit" then has_spirit = true | if scaling_type == "Spirit" then has_spirit = true end | ||
if scaling_type == "Level" then has_level = true end | |||
end | |||
if has_spirit and has_level then | |||
stats_with_both_scaling[attr_key] = true | |||
end | end | ||
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 ~= "ClipSize" then | 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 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 | ||
| Line 419: | Line 318: | ||
end | end | ||
-- Build | -- Build header row (unchanged) | ||
local headers_str = '<th style="position: sticky; left: 0; top: -1px; z-index: 12; background-color: | 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() | ||
local postfix_key_map = { | local postfix_key_map = { | ||
["ReloadDelay"] = "StatDesc_ReloadTime_postfix", | ["ReloadDelay"] = "StatDesc_ReloadTime_postfix", | ||
["BulletsPerShot"] = "", | ["BulletsPerShot"] = "", | ||
["BulletsPerBurst"] = "", | ["BulletsPerBurst"] = "", | ||
["BurstInterShotInterval"] = "StatDesc_ReloadTime_postfix", | ["BurstInterShotInterval"] = "StatDesc_ReloadTime_postfix", | ||
["ReloadSingle"] = "", | ["ReloadSingle"] = "", | ||
["BonusAttackRange"] = "StatDesc_WeaponRangeFalloffMax_postfix", | ["BonusAttackRange"] = "StatDesc_WeaponRangeFalloffMax_postfix", | ||
["SustainedDPS"] = "DPS_postfix", | ["SustainedDPS"] = "DPS_postfix", | ||
["RoundsPerSecondAtMaxSpin"] = "", | ["RoundsPerSecondAtMaxSpin"] = "", | ||
[" | ["CritDamageBonusScale"] = "StatDesc_CritDamageBonusScale_postfix", | ||
["BonusCritDamagePercent"] = "BonusCritDamagePercent_postfix", | |||
["BulletLifesteal"] = "BulletLifestealPercentHero_postfix", | ["BulletLifesteal"] = "BulletLifestealPercentHero_postfix", | ||
["GroundDashSpeed"] = "DashSpeed_postfix" | ["GroundDashSpeed"] = "DashSpeed_postfix" | ||
} | } | ||
for _, category in ipairs(attribute_orders["category_order"]) do | for _, category in ipairs(attribute_orders["category_order"]) do | ||
local category_attrs = attributes_data[category] | local category_attrs = attributes_data[category] | ||
local category_rgb = category_data[category]["rgb"] | local category_rgb = category_data[category]["rgb"] | ||
if stats_to_include[category] ~= nil then | if stats_to_include[category] ~= nil then | ||
for _, attr_key in ipairs(stats_to_include[category]) do | for _, attr_key in ipairs(stats_to_include[category]) do | ||
local attr_data = category_attrs[attr_key] | local attr_data = category_attrs[attr_key] | ||
local attr_localized | |||
local postfix | |||
if attr_data ~= nil then | if attr_data ~= nil then | ||
attr_localized = lang_module.get_string(attr_data["label"]) | attr_localized = lang_module.get_string(attr_data["label"]) | ||
| Line 460: | Line 352: | ||
end | end | ||
postfix = lang_module.get_string(attr_data["postfix"]) | postfix = lang_module.get_string(attr_data["postfix"]) | ||
if postfix == nil or postfix == "" then postfix = "" | if postfix == nil or postfix == "" then | ||
else postfix = " (" .. postfix .. ")" | postfix = "" | ||
else | |||
postfix = " (" .. postfix .. ")" | |||
end | |||
else | else | ||
attr_localized = dictionary_module.translate(attr_key) | attr_localized = dictionary_module.translate(attr_key) | ||
postfix = lang_module.get_string(postfix_key_map[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 == nil then | ||
if postfix ~= "" then postfix = " (" .. postfix .. ")" end | 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 .. ');' | local th_style = 'position: sticky; top: -1px; z-index: 3; background-color: rgb(' .. category_rgb .. ');' | ||
if stats_with_both_scaling[attr_key] then | if stats_with_both_scaling[attr_key] then | ||
| Line 475: | Line 374: | ||
th_style = th_style .. ' min-width: 75px;' | th_style = th_style .. ' min-width: 75px;' | ||
end | end | ||
headers_str = headers_str .. '<th style="' .. th_style .. '">' .. attr_localized .. postfix .. "</th>" | |||
headers_str = headers_str .. | |||
'<th style="' .. th_style .. '">' .. attr_localized .. postfix .. "</th>" | |||
end | end | ||
end | end | ||
| Line 483: | 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>' .. | ||
'</div></div>', | '</div></div>', | ||
max_power, max_spirit, headers_str, body_str | max_power, | ||
max_spirit, | |||
headers_str, | |||
body_str | |||
) | ) | ||
end | end | ||
return p | return p | ||