Module:Sandbox/LVL: Difference between revisionsGive feedback
No edit summary |
No edit summary |
||
| Line 30: | Line 30: | ||
end | end | ||
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 43: | Line 42: | ||
local stats_to_include = { | local stats_to_include = { | ||
Weapon = { | Weapon = { | ||
"DPS", | "DPS", "SustainedDPS", "BulletDamage", "RoundsPerSecond", "FireRate", | ||
"ClipSize", "ReloadTime", "ReloadDelay", "BulletsPerShot", "BulletsPerBurst", | |||
"BurstInterShotInterval", "LightMeleeDamage", "HeavyMeleeDamage", | |||
"ReloadSingle", "BulletSpeed", "FalloffStartRange", "FalloffEndRange", | |||
"CritDamageBonusPercent", "RoundsPerSecondAtMaxSpin", "SpinAcceleration", "SpinDeceleration" | |||
"ClipSize", | |||
"BurstInterShotInterval", | |||
"ReloadSingle", | |||
"CritDamageBonusPercent", | |||
}, | }, | ||
Vitality = { | Vitality = { | ||
"MaxHealth", | "MaxHealth", "BaseHealthRegen", "BulletResist", "TechResist", "MeleeResist", | ||
"CritDamageReceivedPercent", "DebuffResist", "BulletLifesteal", "MaxMoveSpeed", | |||
"SprintSpeed", "StaminaCooldown", "Stamina", "GroundDashSpeed", "GravityChange" | |||
"CritDamageReceivedPercent", | |||
"SprintSpeed", | |||
}, | }, | ||
Spirit = { | Spirit = { "TechPower" } | ||
} | } | ||
-- Collect and sort heroes | -- Collect and sort heroes | ||
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 100: | Line 70: | ||
end) | end) | ||
-- Shared | -- Shared stat cell builder | ||
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" then | 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) | ||
| Line 118: | Line 87: | ||
local stat_value = base_value | local stat_value = base_value | ||
local scaling_data | local scaling_data | ||
if is_alt_fire then | if is_alt_fire then | ||
| Line 139: | Line 106: | ||
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 | ||
| Line 147: | Line 113: | ||
scaling_strs = scaling_strs .. scaling_str | scaling_strs = scaling_strs .. scaling_str | ||
end | end | ||
if scaling_type == "Spirit" then | if scaling_type == "Spirit" then | ||
spirit_scale = scaling_val | spirit_scale = scaling_val | ||
| Line 163: | Line 128: | ||
end | end | ||
local innate_spirit_scale = | local innate_spirit_scale = (hero_data["LevelScaling"] and hero_data["LevelScaling"]["TechPower"]) or 0 | ||
if not display_scaling_icons then scaling_strs = "" end | if not display_scaling_icons then scaling_strs = "" end | ||
if type(stat_value) == "boolean" then | if type(stat_value) == "boolean" then stat_value = tostring(stat_value) | ||
else stat_value = util_module.round_to_sig_fig(stat_value, 3) end | |||
else | |||
local cell_inner = string.format( | local cell_inner = string.format( | ||
'<span class="stat-num">%s</span><span class="stat-scaling">%s</span>', | '<span class="stat-num">%s</span><span class="stat-scaling">%s</span>', | ||
stat_value, | stat_value, scaling_strs | ||
) | ) | ||
| Line 185: | Line 144: | ||
end | end | ||
local hit_once = "false" | local hit_once = "false" | ||
if (attr_key == "DPS" or attr_key == "SustainedDPS") | if (attr_key == "DPS" or attr_key == "SustainedDPS") and weapon_table and weapon_table.HitOnceAcrossAllBullets then | ||
hit_once = "true" | hit_once = "true" | ||
end | end | ||
| Line 202: | Line 158: | ||
hit_once | hit_once | ||
) | ) | ||
return string.format('<td style="white-space: nowrap;" %s>%s</td>', data_attrs, cell_inner) | return string.format('<td style="white-space: nowrap;" %s>%s</td>', data_attrs, cell_inner) | ||
end | end | ||
-- | -- Primary row generator | ||
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 216: | Line 171: | ||
local hero_cell_content = hero_icon | local hero_cell_content = hero_icon | ||
if has_alt then | if has_alt then | ||
hero_cell_content = ' | -- Wikilink toggle (JS will prevent default and handle click) | ||
hero_cell_content = '[[#alt-fire-' .. hero_key .. '|+]] ' .. hero_cell_content | |||
end | end | ||
local rowspan_attr = has_alt and ' rowspan="2"' or '' | local rowspan_attr = has_alt and ' rowspan="2"' or '' | ||
| Line 228: | Line 184: | ||
end | end | ||
end | end | ||
return "<tr>" .. row_str .. "</tr>" | return "<tr>" .. row_str .. "</tr>" | ||
end | end | ||
-- | -- Alt-fire sub-row generator | ||
local function generateAltFireSubRow(hero_data, hero_key) | local function generateAltFireSubRow(hero_data, hero_key) | ||
local row_str = "" | local row_str = "" | ||
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 243: | Line 196: | ||
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 | ||
row_str = row_str .. '<td></td>' | row_str = row_str .. '<td></td>' | ||
end | end | ||
| Line 249: | Line 201: | ||
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 | ||
-- | -- Build body | ||
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 265: | Line 215: | ||
end | end | ||
-- Pre-pass | -- Pre-pass (unchanged) | ||
local stats_with_any_scaling = {} | local stats_with_any_scaling = {} | ||
local stats_with_both_scaling = {} | local stats_with_both_scaling = {} | ||
| Line 273: | Line 223: | ||
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 scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key) | local scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key) | ||
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 = false | local has_spirit, has_level = false, 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 | ||
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 | ||
if hero_data.Weapon and hero_data.Weapon.AltFire and hero_data.Weapon.AltFire[attr_key] ~= nil then | |||
if hero_data.Weapon | |||
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 ~= nil and next(alt_scaling) ~= nil then | if alt_scaling ~= nil and next(alt_scaling) ~= nil then | ||
if not (hero_entry.key == "hero_shiv" and attr_key == "BulletDamage") then | if not (hero_entry.key == "hero_shiv" and attr_key == "BulletDamage") then | ||
stats_with_any_scaling[attr_key] = true | stats_with_any_scaling[attr_key] = true | ||
local has_spirit_alt = false | local has_spirit_alt, has_level_alt = false, false | ||
for _, scaling_type in pairs(alt_scaling) do | for _, scaling_type in pairs(alt_scaling) do | ||
if scaling_type == "Spirit" then has_spirit_alt = true | if scaling_type == "Spirit" then has_spirit_alt = true | ||
elseif scaling_type == "Level" then has_level_alt = true end | |||
end | end | ||
if has_spirit_alt and has_level_alt then stats_with_both_scaling[attr_key] = true end | |||
end | end | ||
end | end | ||
| Line 318: | Line 254: | ||
end | end | ||
-- | -- Header (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() | ||
local postfix_key_map = { | local postfix_key_map = { | ||
["ReloadDelay"] | ["ReloadDelay"] = "StatDesc_ReloadTime_postfix", | ||
["BulletsPerShot"] | ["BulletsPerShot"] = "", | ||
["BulletsPerBurst"] | ["BulletsPerBurst"] = "", | ||
["BurstInterShotInterval"] | ["BurstInterShotInterval"] = "StatDesc_ReloadTime_postfix", | ||
["ReloadSingle"] | ["ReloadSingle"] = "", | ||
["BonusAttackRange"] | ["BonusAttackRange"] = "StatDesc_WeaponRangeFalloffMax_postfix", | ||
["SustainedDPS"] | ["SustainedDPS"] = "DPS_postfix", | ||
["RoundsPerSecondAtMaxSpin"] = "", | ["RoundsPerSecondAtMaxSpin"] = "", | ||
["CritDamageBonusScale"] | ["CritDamageBonusScale"] = "StatDesc_CritDamageBonusScale_postfix", | ||
["BonusCritDamagePercent"] | ["BonusCritDamagePercent"] = "BonusCritDamagePercent_postfix", | ||
["BulletLifesteal"] | ["BulletLifesteal"] = "BulletLifestealPercentHero_postfix", | ||
["GroundDashSpeed"] | ["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 attr_localized, 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 352: | Line 284: | ||
end | end | ||
postfix = lang_module.get_string(attr_data["postfix"]) | postfix = lang_module.get_string(attr_data["postfix"]) | ||
if postfix == nil or postfix == "" then | if postfix == nil or postfix == "" then postfix = "" | ||
else postfix = " (" .. postfix .. ")" end | |||
else | |||
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 | if postfix == nil then return "attr_key " .. attr_key .. " must be added to postfix_key_map" end | ||
if postfix ~= "" then postfix = " (" .. postfix .. ")" end | |||
if postfix ~= "" then | |||
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 374: | Line 298: | ||
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 .. | |||
end | end | ||
end | end | ||
| Line 384: | Line 306: | ||
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">' .. | ||
'<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_power, max_spirit, headers_str, body_str | ||
) | ) | ||
end | end | ||
return p | return p | ||