|
|
| (4 intermediate revisions by 2 users not shown) |
| 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 |