Module:Sandbox/Vergir: Difference between revisionsGive feedback
Final test draft of StatScaling before publishing (with help from vergir-bot LLM) |
Testing merged alt-fire rows for heroBulletDamageTable (with help from vergir-bot LLM) |
||
| Line 1: | Line 1: | ||
local p = {}; | |||
local util_module = require('Module:Utilities') | |||
local allHeroData = mw.loadJsonData("Data:HeroData.json") | |||
local soul_unlock = require('Module:SoulUnlock') | |||
local inGameHeroData = {} | |||
local | |||
-- Just get heroes that are playable and have a "name" in HeroData | |||
for i, heroData in pairs(allHeroData) do | |||
if heroData["InDevelopment"] == false then | |||
if heroData["IsDisabled"] == false then | |||
if heroData["Name"] ~= nil then | |||
table.insert(inGameHeroData, heroData) | |||
end | |||
end | |||
end | |||
end | |||
-- | -- Use sort name for edge cases like "The Doorman" | ||
local function sortHeroes(rows) | |||
local | local function getSortName(name) | ||
return (name:gsub("^The ", "")) | |||
end | |||
table.sort(rows, function(a, b) | |||
local nameA = getSortName(a.sortName or a.name or "") | |||
local nameB = getSortName(b.sortName or b.name or "") | |||
local | return nameA:lower() < nameB:lower() | ||
end) | |||
end | |||
-- | -- Helper function to round numeric values | ||
local function roundValue(value) | |||
if type(value) == "number" then | |||
return util_module.round_to_sig_fig(value, 3) | |||
end | |||
return value | |||
end | |||
-- | -- Round to an integer (up to 2 digits) | ||
local function roundValueInteger(value) | |||
local function | if type(value) == "number" then | ||
return util_module.round_to_sig_fig(value, 2) | |||
end | |||
return value | |||
end | end | ||
-- | -- Generate tables from given properties | ||
-- | local function buildTable(frame, rows, tableDef, options) | ||
options = options or {} | |||
-- option to ignore heroes who don't have any of the given properties. true by default | |||
local | local filterZero = options.filterZero or true | ||
local filteredRows = {} | |||
for _, r in ipairs(rows) do | |||
local hasValue = not filterZero | |||
if filterZero then | |||
for _, col in ipairs(tableDef.columns) do | |||
if r[col.field] ~= 0 and r[col.field] ~= nil then | |||
if r[col.field] ~= "+0" then | |||
hasValue = true | |||
break | |||
end | |||
end | |||
end | |||
end | |||
if hasValue then | |||
table.insert(filteredRows, r) | |||
end | |||
end | |||
if #filteredRows == 0 then | |||
return "" | |||
end | |||
-- Build header | |||
local header = "" | |||
for _, col in ipairs(tableDef.columns) do | |||
header = header .. " !! " .. col.label | |||
end | |||
-- Start table | |||
local t = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n" .. | |||
"|+ " .. tableDef.title .. " \n" .. | |||
"! Hero" .. header .. " \n" | |||
-- Rows | |||
for _, r in ipairs(filteredRows) do | |||
end | local cells = "" | ||
for _, col in ipairs(tableDef.columns) do | |||
cells = cells .. " || " .. (r[col.field] or 0) | |||
end | |||
-- | -- Rows may carry an alternate sort key and a label suffix, used for | ||
-- secondary fire rows listed underneath their hero's main row | |||
local nameCell = "| style=\"text-align:left;\"" | |||
if r.sortName then | |||
nameCell = nameCell .. " data-sort-value=\"" .. r.sortName .. "\"" | |||
end | |||
nameCell = nameCell .. " | " .. | |||
frame:expandTemplate{ title = "PageRef", args = { r.name } } .. | |||
(r.nameSuffix or "") | |||
t = t .. | |||
"|- \n" .. | |||
nameCell .. | |||
cells .. " \n" | |||
end | |||
end | |||
t = t .. "|}" | |||
return t | |||
end | end | ||
-- | -- heroDataArray takes as argument a table of any length containing keys or | ||
local function | -- paths of keys in the HeroData JSON, e.g. {Name, MaxHealth, LevelScaling>Health}. | ||
-- A path of keys should be specified as such, with the ">" character separating | |||
-- subsequent keys. | |||
local heroDataArray = function(args) | |||
local outData = {} | |||
for i, heroData in ipairs(inGameHeroData) do -- Iterate over each hero | |||
local out = {} -- First creating a table to hold data | |||
for j, key in pairs(args) do -- Iterate over each key: | |||
if string.find(key, ">") ~= nil then -- If the key is actually a path of keys, | |||
local node = heroData -- start with the biggest node heroData, and | |||
for k in string.gmatch(key, "([^>]+)") do -- iterate over each key, splitting on ">", | |||
if node and node[k] ~= nil then -- trying the key as a string e.g. "1", (Added 'node and' for safety) | |||
node = node[k] | |||
elseif node and node[tonumber(k)] ~= nil then -- else using the key as numeric, (Added 'node and' for safety) | |||
node = node[tonumber(k)] | |||
else -- If at any point the path breaks, the result is nil | |||
node = nil | |||
break | |||
end -- drilling down the JSON node after node | |||
end | |||
out[key] = roundValue(node) -- until outputting the final node (rounded) | |||
else | |||
out[key] = roundValue(heroData[key]) -- or just grab the data if not a path (rounded) | |||
end | |||
end | |||
outData[heroData["Name"]] = out -- Save data in the big table of heroes | |||
end | |||
return(outData) | |||
end | end | ||
p.heroBulletDamageTable = function(frame) | |||
-- Pull data | |||
local heroBulletDamageData = heroDataArray({"Name", "Weapon>BulletDamage", "LevelScaling>BulletDamage", "Weapon>AltFire>BulletDamage", "LevelScaling>BulletDamageAltFire", | |||
"SpiritScaling>BulletDamage", "Weapon>BulletsPerShot", "Weapon>BulletsPerBurst"}) | |||
local rows = {} | |||
for name, data in pairs(heroBulletDamageData) do | |||
table.insert(rows, { | |||
name = name, | |||
bulletDamage = data["Weapon>BulletDamage"] or 0, | |||
bulletDamagePerShot = ((data["Weapon>BulletDamage"] or 0) * (data["Weapon>BulletsPerShot"] or 0) * (data["Weapon>BulletsPerBurst"] or 0)), | |||
scalingDamage = "+" .. data["LevelScaling>BulletDamage"] or 0, | |||
maxScalingDamage = (roundValue(data["Weapon>BulletDamage"] + data["LevelScaling>BulletDamage"] * soul_unlock.get_max("PowerIncrease"))) or 0, | |||
altBulletDamage = data["Weapon>AltFire>BulletDamage"] or 0, | |||
spiritScalingDamage = "+" .. (data["SpiritScaling>BulletDamage"] or 0), | |||
}) | |||
-- | -- Secondary fire is listed as its own row, right under the hero's main row | ||
local altDamage = data["Weapon>AltFire>BulletDamage"] | |||
if altDamage and altDamage ~= 0 then | |||
local altScaling = data["LevelScaling>BulletDamageAltFire"] or 0 | |||
table.insert(rows, { | |||
name = name, | |||
sortName = name .. " (alt fire)", | |||
nameSuffix = " (alt fire)", | |||
bulletDamage = altDamage, | |||
scalingDamage = "+" .. altScaling, | |||
maxScalingDamage = roundValue(altDamage + altScaling * soul_unlock.get_max("PowerIncrease")), | |||
spiritScalingDamage = "+0", | |||
}) | |||
end | |||
end | |||
sortHeroes(rows) | |||
local tableDefs = { | |||
{ | |||
title = "Base Bullet Damage Stats", | |||
columns = { | |||
{ label = "Starting", field = "bulletDamage" }, | |||
{ label = "Added per Boon", field = "scalingDamage" }, | |||
{ label = "At Max Boon", field = "maxScalingDamage" }, | |||
{ label = "Spirit Scaling", field = "spiritScalingDamage" }, | |||
} | |||
}, | |||
} | |||
local output = {} | |||
for _, def in ipairs(tableDefs) do | |||
local t = buildTable(frame, rows, def, def.options) | |||
if t ~= "" then | |||
table.insert(output, t) | |||
end | |||
end | |||
return table.concat(output, "\n\n") | |||
end | end | ||
------ | -- Kept for regression testing: an untouched table that also goes through | ||
-- buildTable() and sortHeroes() | |||
p.heroHealthTable = function(frame) | |||
-- Pull data | |||
local heroHealthData = heroDataArray({"Name", "MaxHealth", "LevelScaling>MaxHealth"}) | |||
local rows = {} | |||
for name, data in pairs(heroHealthData) do | |||
table.insert(rows, { | |||
name = name, | |||
maxHealth = data["MaxHealth"] or 0, | |||
scalingHealth = "+" .. data["LevelScaling>MaxHealth"] or 0, | |||
maxScalingHealth = roundValue(data["MaxHealth"] + data["LevelScaling>MaxHealth"] * soul_unlock.get_max("PowerIncrease")) or 0 | |||
}) | |||
end | |||
sortHeroes(rows) | |||
local tableDefs = { | |||
{ | |||
title = "Base Health Stats", | |||
columns = { | |||
{ label = "Starting", field = "maxHealth" }, | |||
{ label = "Added per Boon", field = "scalingHealth" }, | |||
{ label = "At Max Boon", field = "maxScalingHealth" }, | |||
} | |||
} | |||
} | |||
local output = {} | |||
for _, def in ipairs(tableDefs) do | |||
local t = buildTable(frame, rows, def, def.options) | |||
if t ~= "" then | |||
table.insert(output, t) | |||
end | |||
end | |||
return table.concat(output, "\n\n") | |||
end | end | ||
return p | return p | ||