Editing Module:Sandbox/VergirGive 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 1: | Line 1: | ||
local p = {} | local p = {}; | ||
local util_module = require('Module:Utilities') | |||
local allHeroData = mw.loadJsonData("Data:HeroData.json") | |||
local soul_unlock = require('Module:SoulUnlock') | |||
local inGameHeroData = {} | |||
-- 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 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 "") | |||
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) | |||
if type(value) == "number" then | |||
return util_module.round_to_sig_fig(value, 2) | |||
end | |||
return value | |||
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 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 | |||
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 | |||
t = t .. "|}" | |||
return t | |||
end | |||
-- heroDataArray takes as argument a table of any length containing keys or | |||
-- 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 | |||
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 | |||
-- 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 | |||
return p | return p | ||