Module:Sandbox/LVL: Difference between revisionsGive feedback
No edit summary |
No edit summary |
||
| Line 31: | Line 31: | ||
-- ========================================================================= | -- ========================================================================= | ||
-- DPS calculation helpers | -- DPS calculation helpers | ||
-- ========================================================================= | -- ========================================================================= | ||
local function calculate_dps(stats, dps_type) | local function calculate_dps(stats, dps_type) | ||
local rps = stats.RoundsPerSecond or 0 | local rps = stats.RoundsPerSecond or 0 | ||
if rps == 0 then return 0 end | if rps == 0 then return 0 end | ||
| Line 50: | Line 50: | ||
end | end | ||
local clip_size = stats.ClipSize or 0 | local clip_size = stats.ClipSize or 0 | ||
if clip_size <= 0 then | if clip_size <= 0 then | ||
return base_damage * bullets_per_shot * burst_count / total_cycle_time | return base_damage * bullets_per_shot * burst_count / total_cycle_time | ||
end | end | ||
| Line 72: | Line 70: | ||
end | end | ||
-- Compute DPS | -- Compute how much DPS changes when all relevant component scalings are applied. | ||
-- | -- Used for alt-fire rows where explicit DPS/SustainedDPS scaling keys are missing. | ||
local function compute_dps_scaling(hero_data, base_stats, dps_type, scaling_type) | local function compute_dps_scaling(hero_data, base_stats, dps_type, scaling_type) | ||
local scaling_source = hero_data[scaling_type .. "Scaling"] or {} | local scaling_source = hero_data[scaling_type .. "Scaling"] or {} | ||
local component_scalings = {} | local component_scalings = {} | ||
local possible_keys = { | local possible_keys = { | ||
| Line 92: | Line 87: | ||
end | end | ||
-- ClipSize is shared between | -- ClipSize is shared between fire modes; fall back to primary scaling | ||
if base_stats.ClipSize and not component_scalings.ClipSize then | if base_stats.ClipSize and not component_scalings.ClipSize then | ||
local primary_clip_scale = scaling_source["ClipSize"] | local primary_clip_scale = scaling_source["ClipSize"] | ||
| Line 117: | Line 111: | ||
end | end | ||
-- Compute DPS scaling delta from explicit component scalings. | |||
-- Used for Shiv, whose alt-fire BulletDamage uses primary scaling. | |||
local function compute_dps_scaling_from_components(base_stats, dps_type, component_scalings) | local function compute_dps_scaling_from_components(base_stats, dps_type, component_scalings) | ||
local scaled_stats = {} | local scaled_stats = {} | ||
for k, v in pairs(base_stats) do scaled_stats[k] = v end | for k, v in pairs(base_stats) do scaled_stats[k] = v end | ||
| Line 134: | Line 129: | ||
-- Main module function | -- Main module function | ||
-- ========================================================================= | -- ========================================================================= | ||
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 142: | Line 138: | ||
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 = { | ||
| Line 160: | Line 156: | ||
} | } | ||
-- Collect and sort heroes | -- Collect and sort heroes alphabetically, excluding 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 174: | Line 170: | ||
end) | 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) | 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" or attr_key == "ReloadTime" then | if attr_key == "ClipSize" or attr_key == "ReloadTime" then | ||
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 | ||
and hero_data.Weapon.AltFire[attr_key] ~= nil then | and hero_data.Weapon.AltFire[attr_key] ~= nil then | ||
| Line 196: | Line 192: | ||
if is_alt_fire then | if is_alt_fire then | ||
if attr_key == "ClipSize" or attr_key == "ReloadTime" then | if attr_key == "ClipSize" or attr_key == "ReloadTime" then | ||
scaling_data = hero_data_module.get_hero_scaling_data(hero_data, attr_key) | |||
elseif hero_key == "hero_shiv" and attr_key == "BulletDamage" then | elseif hero_key == "hero_shiv" and attr_key == "BulletDamage" then | ||
-- Shiv's alt-fire BulletDamage uses the primary fire's scaling | |||
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_key == "hero_shiv" and (attr_key == "DPS" or attr_key == "SustainedDPS") | elseif hero_key == "hero_shiv" and (attr_key == "DPS" or attr_key == "SustainedDPS") | ||
and hero_data.Weapon and hero_data.Weapon.AltFire then | and hero_data.Weapon and hero_data.Weapon.AltFire then | ||
-- Shiv's alt-fire DPS scales from primary BulletDamage scaling | |||
local alt_stats = hero_data.Weapon.AltFire | local alt_stats = hero_data.Weapon.AltFire | ||
local dps_type = attr_key == "DPS" and 'burst' or 'sustained' | local dps_type = attr_key == "DPS" and 'burst' or 'sustained' | ||
local bd_scaling = hero_data_module.get_hero_scaling_data(hero_data, "BulletDamage") | local bd_scaling = hero_data_module.get_hero_scaling_data(hero_data, "BulletDamage") | ||
local level_scale = 0 | local level_scale = 0 | ||
| Line 225: | Line 221: | ||
if spirit_scale ~= 0 then scaling_data[spirit_scale] = "Spirit" end | if spirit_scale ~= 0 then scaling_data[spirit_scale] = "Spirit" end | ||
if next(scaling_data) == nil then scaling_data = nil end | if next(scaling_data) == nil then scaling_data = nil end | ||
elseif (attr_key == "DPS" or attr_key == "SustainedDPS") | elseif (attr_key == "DPS" or attr_key == "SustainedDPS") | ||
and hero_data.Weapon and hero_data.Weapon.AltFire then | and hero_data.Weapon and hero_data.Weapon.AltFire then | ||
-- Alt-fire DPS/SustainedDPS: use explicit key if present, otherwise compute from components | |||
local direct_scaling = hero_data_module.get_hero_scaling_data(hero_data, attr_key .. "AltFire") | local direct_scaling = hero_data_module.get_hero_scaling_data(hero_data, attr_key .. "AltFire") | ||
if direct_scaling and next(direct_scaling) ~= nil then | if direct_scaling and next(direct_scaling) ~= nil then | ||
| Line 259: | Line 255: | ||
local level_val = 0 | local level_val = 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 | ||
if scaling_type == "Spirit" then | if scaling_type == "Spirit" then | ||
| Line 268: | Line 263: | ||
end | end | ||
-- | -- Output Spirit icon before Level icon | ||
local function append_scaling(val, stype) | local function append_scaling(val, stype) | ||
if val ~= 0 then | if val ~= 0 then | ||
| Line 280: | Line 275: | ||
append_scaling(level_val, "Level") | append_scaling(level_val, "Level") | ||
if type(stat_value) == "number" then | if type(stat_value) == "number" then | ||
stat_value = stat_value + (spirit_power * spirit_val) | stat_value = stat_value + (spirit_power * spirit_val) | ||
| Line 340: | Line 334: | ||
end | end | ||
-- | -- Generate a hero's primary stat row | ||
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 351: | Line 345: | ||
hero_cell_content = '[[#alt-fire-' .. hero_key .. '|+]] ' .. hero_cell_content | hero_cell_content = '[[#alt-fire-' .. hero_key .. '|+]] ' .. hero_cell_content | ||
end | end | ||
local sort_name = (hero_data["Name"] or hero_key):gsub("^The ", "") row_str = row_str .. '<td style="position: sticky; left: 0; z-index: | local sort_name = (hero_data["Name"] or hero_key):gsub("^The ", "") | ||
row_str = row_str .. '<td style="position: sticky; left: 0; z-index: 10; background-color: #202122; isolation: isolate; overflow: hidden; min-width: 150px;" data-sort-value="' .. sort_name .. '">' .. 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 362: | Line 357: | ||
end | end | ||
-- | -- Generate an expandable alt-fire sub-row (weapon stats only) | ||
local function generateAltFireSubRow(hero_data, hero_key) | local function generateAltFireSubRow(hero_data, hero_key) | ||
local row_str = "" | local row_str = "" | ||
| Line 370: | Line 365: | ||
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 .. ' <small style="color:#aaa;">(Alt‑fire)</small>' | local hero_cell_content = hero_icon .. ' <small style="color:#aaa;">(Alt‑fire)</small>' | ||
local sort_name = (hero_data["Name"] or hero_key):gsub("^The ", "") row_str = row_str .. '<td style="position: sticky; left: 0; z-index: | local sort_name = (hero_data["Name"] or hero_key):gsub("^The ", "") | ||
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>' | |||
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 385: | Line 381: | ||
end | end | ||
-- Build body | -- Build all body 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 | ||
| Line 396: | Line 392: | ||
end | end | ||
-- Pre-pass | -- Pre-pass: determine which stats have any scaling, and which have both types. | ||
-- Used to set column widths in the header. | |||
local stats_with_any_scaling = {} | local stats_with_any_scaling = {} | ||
local stats_with_both_scaling = {} | local stats_with_both_scaling = {} | ||
| Line 404: | Line 401: | ||
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 | ||
-- Primary scaling | |||
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 | ||
| Line 414: | Line 412: | ||
if has_spirit and has_level then stats_with_both_scaling[attr_key] = true end | if has_spirit and has_level then stats_with_both_scaling[attr_key] = true end | ||
end | end | ||
-- Alt-fire scaling (only | -- Alt-fire scaling (only for stats present in the AltFire table) | ||
if hero_data.Weapon | if hero_data.Weapon | ||
and hero_data.Weapon.AltFire | and hero_data.Weapon.AltFire | ||
| Line 433: | Line 431: | ||
end | end | ||
else | else | ||
-- | -- Shiv's alt-fire DPS/SustainedDPS uses primary BulletDamage scaling | ||
if hero_entry.key == "hero_shiv" and (attr_key == "DPS" or attr_key == "SustainedDPS") then | if hero_entry.key == "hero_shiv" and (attr_key == "DPS" or attr_key == "SustainedDPS") then | ||
local bd_scaling = hero_data_module.get_hero_scaling_data(hero_data, "BulletDamage") | local bd_scaling = hero_data_module.get_hero_scaling_data(hero_data, "BulletDamage") | ||
| Line 444: | Line 442: | ||
if delta ~= 0 then | if delta ~= 0 then | ||
stats_with_any_scaling[attr_key] = true | stats_with_any_scaling[attr_key] = true | ||
end | end | ||
end | end | ||
end | end | ||
-- | -- Alt-fire DPS/SustainedDPS for other heroes: compute from component scalings | ||
elseif attr_key == "DPS" or attr_key == "SustainedDPS" then | elseif attr_key == "DPS" or attr_key == "SustainedDPS" then | ||
local alt_stats = hero_data.Weapon.AltFire | local alt_stats = hero_data.Weapon.AltFire | ||
| Line 474: | Line 468: | ||
end | end | ||
-- | -- Build table header row | ||
local headers_str = '<th style="position: sticky; left: 0; top: -1px; z-index: 12; background-color: #444; isolation: isolate; min-width: 150px;">Hero</th>' | local headers_str = '<th style="position: sticky; left: 0; top: -1px; z-index: 12; background-color: #444; isolation: isolate; min-width: 150px;">Hero</th>' | ||
local category_data = attribute_module.get_category_data() | local category_data = attribute_module.get_category_data() | ||