Editing Module:Sandbox/LVLGive feedback
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 hero_name | |||
local hero_name_en | |||
local hero_icon | |||
local template_args = {} | |||
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 104: | ||
end) | end) | ||
-- | -- Helper to generate a single row (primary or alt-fire) | ||
local function | local function generateRow(hero_data, hero_key, is_alt_fire) | ||
local row_str = "" | |||
local | 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 | local hero_icon = mw.getCurrentFrame():expandTemplate{ title = "Template:HeroIcon", args = template_args } | ||
local hero_cell_content = hero_icon | |||
local | |||
if is_alt_fire then | if is_alt_fire then | ||
hero_cell_content = hero_icon .. ' <small>(Alt-fire)</small>' | |||
end | end | ||
row_str = row_str .. | |||
'<td style="position: sticky; left: 0; z-index: 10; background-color: #202122; isolation: isolate; overflow: hidden;">' .. | |||
hero_cell_content .. "</td>" | |||
-- | 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 | |||
-- Base value: alt-fire weapon stats from AltFire table, else normal lookup | |||
local base_value | |||
if is_alt_fire and category == "Weapon" then | |||
base_value = (hero_data.Weapon.AltFire and hero_data.Weapon.AltFire[attr_key]) or 0 | |||
else | |||
base_value = get_nested_stat_value(hero_data, attr_key) | |||
end | |||
local stat_value = base_value | |||
-- FIX 1: Scale using AltFire key only if the stat actually exists in AltFire | |||
local scaling_data | |||
if is_alt_fire and 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) | |||
local | |||
if | |||
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 | |||
) | |||
row_str = row_str .. | |||
string.format('<td style="white-space: nowrap;" %s>%s</td>', data_attrs, cell_inner) | |||
end | end | ||
end | end | ||
end | end | ||
return "<tr>" .. row_str .. "</tr>" | return "<tr>" .. row_str .. "</tr>" | ||
end | end | ||
-- | -- Build table body rows (primary + optional alt-fire) | ||
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 | ||
body_str = body_str .. | body_str = body_str .. generateRow(hero_data, hero_key, false) | ||
if | if hero_data.Weapon and hero_data.Weapon.AltFire then | ||
body_str = body_str .. | body_str = body_str .. generateRow(hero_data, hero_key, true) | ||
end | end | ||
end | end | ||
-- Pre-pass: determine which stats have any scaling, and which have both types | -- Pre-pass: determine which stats have any scaling, and which have both types | ||
-- | -- (now includes alt-fire scaling for weapon columns) | ||
local stats_with_any_scaling = {} | local stats_with_any_scaling = {} | ||
local stats_with_both_scaling = {} | local stats_with_both_scaling = {} | ||
| Line 373: | Line 240: | ||
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 | -- FIX 2: Alt-fire scaling only for stats that actually appear in AltFire | ||
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 | ||
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 | |||
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 275: | ||
end | end | ||
-- Build | -- Build header row | ||
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 309: | ||
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 331: | ||
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 486: | Line 344: | ||
'<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 | ||