Module:HeroDataArrays: Difference between revisions

No edit summary
added alt fire stats to all tables where applicable
 
(37 intermediate revisions by 3 users not shown)
Line 14: Line 14:
         end
         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
end


Line 22: Line 35:
     end
     end
     return value
     return value
end
-- Round to 4 digits
local function roundValueFour(value)
    if type(value) == "number" then
        return util_module.round_to_sig_fig(value, 4)
    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
        -- A row may carry an alternate sort key and a label suffix, used by
        -- secondary fire rows so they stay 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
end


Line 64: Line 163:
         table.insert(rows, {
         table.insert(rows, {
             name = name,
             name = name,
             maxHealth = tonumber(data["MaxHealth"]) or 0,
             maxHealth = data["MaxHealth"] or 0,
             scalingHealth = tonumber(data["LevelScaling>MaxHealth"]) or 0
             scalingHealth = "+" .. data["LevelScaling>MaxHealth"] or 0,
            maxScalingHealth = (data["MaxHealth"] + data["LevelScaling>MaxHealth"] * soul_unlock.get_max("PowerIncrease")) or 0
         })
         })
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     -- Input table will be called a, output wikitable will be called z
     local tableDefs = {
    local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
        {
         "|+ Hero Base Health Stats \n"..
            title = "Base Health Stats",
         "! Hero !! Starting !! Added per Boon !! At Max Boon \n"
            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


     -- Iterate over heroes adding a new row to the wikitable for each
     return table.concat(output, "\n\n")
    for _, r in ipairs(rows) do
        z = z..
            "|- \n"..
            "| style=\"text-align:left;\" | "..
                frame:expandTemplate{title="PageRef", args={r.name}}..
                " || "..r.maxHealth.." || +"..r.scalingHealth.." || "..
                tostring(roundValue(r.maxHealth + r.scalingHealth * soul_unlock.get_max('PowerIncrease'))).." \n"
    end
   
    z = z.."|}"
    return z
end
end


p.heroRegenTable = function(frame)
p.heroRegenTable = function(frame)
     local heroRegenData = heroDataArray({"Name", "BaseHealthRegen"})
     local heroRegenData = heroDataArray({"Name", "BaseHealthRegen", "SpiritScaling>BaseHealthRegen"})
      
      
     local rows = {}
     local rows = {}
Line 96: Line 199:
         table.insert(rows, {
         table.insert(rows, {
             name = name,
             name = name,
             baseHealthRegen = data["BaseHealthRegen"] or 0
             baseHealthRegen = data["BaseHealthRegen"] or 0,
            spiritHealthRegen = "+".. (data["SpiritScaling>BaseHealthRegen"] or 0),
         })
         })
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
     local tableDefs = {
         "|+ Hero Base Health Regen \n"..
         {
        "! Hero !! HP/s \n"
            title = "Base Health Regen Stats",
            columns = {
                { label = "HP/s", field = "baseHealthRegen" },
                { label = "Spirit Scaling", field = "spiritHealthRegen" }
            }
        }
    }


     for _, r in ipairs(rows) do
local output = {}
         z = z..
     for _, def in ipairs(tableDefs) do
            "|- \n"..
         local t = buildTable(frame, rows, def, def.options)
             "| style=\"text-align:left;\" | "..
        if t ~= "" then
                frame:expandTemplate{title="PageRef", args={r.name}}..
             table.insert(output, t)
                " || "..r.baseHealthRegen.." \n"
        end
     end
     end
     z = z.."|}"
 
    return z
     return table.concat(output, "\n\n")
end
end


Line 138: Line 248:
             altBulletsPerShot = data["Weapon>AltFire>BulletsPerShot"] or 0
             altBulletsPerShot = data["Weapon>AltFire>BulletsPerShot"] or 0
         })
         })
    end
    table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
    local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
        "|+ Hero Base Damage \n"..
        "! Hero !! Damage per sec !! Reload Delay (s) !! Reload Time (s) !! Ammo !! Bullets per sec !! Bullet Damage !! Bullets Per Shot \n"
          
          
    for _, r in ipairs(rows) do
        local altBulletDamage = data["Weapon>AltFire>BulletDamage"]
         z = z..
         local altDPS = data["Weapon>AltFire>DPS"]
            "|- \n"..
        local altRoundsPerSecond = data["Weapon>AltFire>RoundsPerSecond"]
            "| style=\"text-align:left;\" | "..
        local altBulletsPerShot = data["Weapon>AltFire>BulletsPerShot"]
                 frame:expandTemplate{title="PageRef", args={r.name}}..
        if altBulletDamage and altBulletDamage ~= 0 then
                 " || "..r.DPS.." || "..r.reloadDelay.." || "..r.reloadTime.." || "..r.clipSize..
            table.insert(rows, {
                 " || "..r.roundsPerSecond.." || "..r.bulletDamage.." || "..r.bulletsPerShot.." \n"
                 name = name,
                sortName = name .. " (alt fire)",
                 nameSuffix = " (alt fire)",
                DPS = altDPS,
                reloadDelay = data["Weapon>ReloadDelay"] or 0,
                reloadTime = data["Weapon>ReloadTime"] or 0,
                 clipSize = data["Weapon>ClipSize"] or 0,
                roundsPerSecond = altRoundsPerSecond,
                bulletDamage = altBulletDamage,
                bulletsPerShot = altBulletsPerShot
            })
        end
     end
     end
    z = z.."|}"
     sortHeroes(rows)
 
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)


     local tableDefs = {
     local tableDefs = {
     {
     {
             title  = "Hero Base Alt Fire Damage",
             title  = "Base Damage Stats",
             columns = {
             columns = {
                 { label = "Damage per sec", field = "altDPS"  },
                 { label = "Damage per sec", field = "DPS"  },
                 { label = "Ammo Per Shot", field = "altAmmoConsumedPerShot"  },
                { label = "Reload Delay (s)", field = "reloadDelay"  },
                 { label = "Bullets per sec", field = "altRoundsPerSecond"  },
                { label = "Reload Time (s)", field = "reloadTime"  },
                 { label = "Bullet Damage", field = "altBulletDamage"  },
                 { label = "Ammo", field = "clipSize"  },
                 { label = "Bullets Per Shot", field = "altBulletsPerShot"  },
                 { label = "Bullets per sec", field = "roundsPerSecond"  },
                 { label = "Bullet Damage", field = "bulletDamage"  },
                 { label = "Bullets Per Shot", field = "bulletsPerShot"  },
             }
             }
         }  
         }  
     }
     }


     local output = ""
     local output = {}
 
     for _, def in ipairs(tableDefs) do
     for _, tableDef in ipairs(tableDefs) do
         local t = buildTable(frame, rows, def, def.options)
         local filteredRows = {}
        if t ~= "" then
        for _, r in ipairs(rows) do
             table.insert(output, t)
            local hasValue = false
            for _, col in ipairs(tableDef.columns) do
                if r[col.field] ~= 0 then
                    hasValue = true
                    break
                end
             end
            if hasValue then
                table.insert(filteredRows, r)
            end
        end
 
        if #filteredRows > 0 then
            local header = ""
            for _, col in ipairs(tableDef.columns) do
                header = header.." !! "..col.label
            end
 
            local t = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
                "|+ "..tableDef.title.." \n"..
                "! Hero"..header.." \n"
 
            for _, r in ipairs(filteredRows) do
                local cells = ""
                for _, col in ipairs(tableDef.columns) do
                    cells = cells.." || "..r[col.field]
                end
                t = t..
                    "|- \n"..
                    "| style=\"text-align:left;\" | "..
                        frame:expandTemplate{title="PageRef", args={r.name}}..
                        cells.." \n"
            end
 
            t = t.."|}"
            if output ~= "" then
                output = output.."\n\n"
            end
            output = output..t
         end
         end
     end
     end


     return z .. "\n\n" .. output
     return table.concat(output, "\n\n")
end
end


p.heroBulletSpeedTable = function(frame)
p.heroBulletSpeedTable = function(frame)
     local heroBulletSpeedData = heroDataArray({"Name", "Weapon>BulletSpeed"})
     local heroBulletSpeedData = heroDataArray({
   
        "Name",
        "Weapon>BulletSpeed",
        "Weapon>AltFire>BulletSpeed"
    })
 
     local rows = {}
     local rows = {}
     for name, data in pairs(heroBulletSpeedData) do
     for name, data in pairs(heroBulletSpeedData) do
         table.insert(rows, {
         table.insert(rows, {
             name = name,
             name = name,
             bulletSpeed = data["Weapon>BulletSpeed"] or 0
             bulletSpeed = data["Weapon>BulletSpeed"] or 0,
            altBulletSpeed = data["Weapon>AltFire>BulletSpeed"] or 0
         })
         })
       
        local altBulletSpeed = data["Weapon>AltFire>BulletSpeed"]
        if altBulletSpeed and altBulletSpeed ~= 0 then
            table.insert(rows, {
                name = name,
                sortName = name .. " (alt fire)",
                nameSuffix = " (alt fire)",
                bulletSpeed = altBulletSpeed
            })
        end
     end
     end
    table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)


     local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
     sortHeroes(rows)
        "|+ Base Bullet Velocity \n"..
        "! Hero !! m/s \n"


     for _, r in ipairs(rows) do
     local tableDefs = {
         z = z..
         {
             "|- \n"..
             title = "Base Bullet Velocity",
             "| style=\"text-align:left;\" | "..
             columns = {
                 frame:expandTemplate{title="PageRef", args={r.name}}..
                 { label = "m/s", field = "bulletSpeed" }
                " || "..r.bulletSpeed.." \n"
            }
        }
    }
 
    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
     end
     z = z.."|}"
 
    return z
     return table.concat(output, "\n\n")
end
end


p.heroClipSizeTable = function(frame)
p.heroClipSizeTable = function(frame)
     local heroClipSizeData = heroDataArray({"Name", "Weapon>ClipSize"})
     local heroClipSizeData = heroDataArray({"Name", "Weapon>ClipSize","Weapon>BulletsPerBurst","Weapon>AltFire>AmmoConsumedPerShot","SpiritScaling>ClipSize"})
      
      
     local rows = {}
     local rows = {}
Line 254: Line 351:
         table.insert(rows, {
         table.insert(rows, {
             name = name,
             name = name,
             clipSize = data["Weapon>ClipSize"] or 0
             clipSize = data["Weapon>ClipSize"] or 0,
            spiritClipSize = "+".. (data["SpiritScaling>ClipSize"] or 0),
            bulletsPerBurst = data["Weapon>BulletsPerBurst"] or 0,
            burstClipSize = data["Weapon>ClipSize"] or 0 / data["Weapon>BulletsPerBurst"] or 0,
            ammoConsumedPerShot = data["Weapon>AltFire>AmmoConsumedPerShot"] or 0
         })
         })
       
        local ammoConsumedPerShot = data["Weapon>AltFire>AmmoConsumedPerShot"]
        if ammoConsumedPerShot and ammoConsumedPerShot ~= 0 then
            table.insert(rows, {
                name = name,
                sortName = name .. " (alt fire)",
                nameSuffix = " (alt fire)",
                bulletsPerBurst = ammoConsumedPerShot,
                clipSize = data["Weapon>ClipSize"] or 0,
                spiritClipSize = "+".. (data["SpiritScaling>ClipSize"] or 0),
            })
        end
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
     local tableDefs = {
        "|+ Base Ammo Count \n"..
    {
        "! Hero !! Ammo \n"
            title  = "Base Ammo Count",
            columns = {
                { label = "Ammo", field = "clipSize" },
                { label = "Ammo Per Shot", field = "bulletsPerBurst" },
                { label = "Spirit Scaling", field = "spiritClipSize" },
            }
        }
    }


     for _, r in ipairs(rows) do
    local output = {}
         z = z..
     for _, def in ipairs(tableDefs) do
            "|- \n"..
         local t = buildTable(frame, rows, def, def.options)
             "| style=\"text-align:left;\" | "..
        if t ~= "" then
                frame:expandTemplate{title="PageRef", args={r.name}}..
             table.insert(output, t)
                " || "..r.clipSize.." \n"
        end
     end
     end
     z = z.."|}"
 
    return z
     return table.concat(output, "\n\n")
end
end


p.heroRoundsPerSecondTable = function(frame)
p.heroRoundsPerSecondTable = function(frame)
     local heroRoundsPerSecondData = heroDataArray({"Name", "Weapon>RoundsPerSecond"})
     local heroRoundsPerSecondData = heroDataArray({"Name", "Weapon>RoundsPerSecond", "Weapon>AltFire>RoundsPerSecond", "SpiritScaling>RoundsPerSecond", "SpiritScaling>FireRate"})
      
      
     local rows = {}
     local rows = {}
Line 281: Line 402:
         table.insert(rows, {
         table.insert(rows, {
             name = name,
             name = name,
             roundsPerSecond = data["Weapon>RoundsPerSecond"] or 0
             roundsPerSecond = data["Weapon>RoundsPerSecond"] or 0,
            altRoundsPerSecond = data["Weapon>AltFire>RoundsPerSecond"] or 0,
            spiritRoundsPerSecond = "+".. (data["SpiritScaling>RoundsPerSecond"] or 0),
            spiritFireRate = "+".. (data["SpiritScaling>FireRate"] or 0),
         })
         })
       
        local altRoundsPerSecond = data["Weapon>AltFire>RoundsPerSecond"]
        if altRoundsPerSecond and altRoundsPerSecond ~= 0 then
            table.insert(rows, {
                name = name,
                sortName = name .. " (alt fire)",
                nameSuffix = " (alt fire)",
                roundsPerSecond = altRoundsPerSecond,
                spiritRoundsPerSecond = "+".. (data["SpiritScaling>RoundsPerSecond"] or 0),
            })
        end
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
     local tableDefs = {
        "|+ Base Fire Rate \n"..
    {
         "! Hero !! Rounds/s \n"
            title  = "Base Fire Rate",
            columns = {
                { label = "Rounds/s", field = "roundsPerSecond" },
                { label = "Spirit Scaling", field = "spiritRoundsPerSecond" },
            }
        
    }


     for _, r in ipairs(rows) do
    local output = {}
         z = z..
     for _, def in ipairs(tableDefs) do
            "|- \n"..
         local t = buildTable(frame, rows, def, def.options)
             "| style=\"text-align:left;\" | "..
        if t ~= "" then
                frame:expandTemplate{title="PageRef", args={r.name}}..
             table.insert(output, t)
                " || "..r.roundsPerSecond.." \n"
        end
     end
     end
     z = z.."|}"
 
    return z
     return table.concat(output, "\n\n")
end
end


p.heroFalloffRangeTable = function(frame)
p.heroFalloffRangeTable = function(frame)
     local heroFalloffRangeData = heroDataArray({"Name", "Weapon>FalloffStartRange", "Weapon>FalloffEndRange"})
     local heroFalloffRangeData = heroDataArray({"Name", "Weapon>FalloffStartRange", "Weapon>FalloffEndRange",  "Weapon>AltFire>FalloffStartRange", "Weapon>AltFire>FalloffEndRange",
    "Weapon>AltFire>ExplosionRadius", "Weapon>FalloffEndScale", "Weapon>AltFire>ExplosionDamageScaleAtMaxRadius",})
      
      
     local rows = {}
     local rows = {}
Line 309: Line 451:
             name = name,
             name = name,
             falloffStartRange = data["Weapon>FalloffStartRange"] or 0,
             falloffStartRange = data["Weapon>FalloffStartRange"] or 0,
             falloffEndRange = data["Weapon>FalloffEndRange"] or 0
             falloffEndRange = data["Weapon>FalloffEndRange"] or 0,
            falloffEndScale = -100 + (100 * (data["Weapon>FalloffEndScale"] or 0)),
            altFalloffStartRange = data["Weapon>AltFire>FalloffStartRange"] or 0,
            altFalloffEndRange = data["Weapon>AltFire>FalloffEndRange"] or 0,
            altExplosionRadius = 0,
            altExplosionEndScale = (100 * (data["Weapon>AltFire>ExplosionDamageScaleAtMaxRadius"] or 0))
         })
         })
   
        local altFalloffStartRange = data["Weapon>AltFire>FalloffStartRange"]
        local altFalloffEndRange = data["Weapon>AltFire>FalloffEndRange"]
        local altExplosionRadius = data["Weapon>AltFire>ExplosionRadius"] or 0
        local altExplosionEndScale = (100 * (data["Weapon>AltFire>ExplosionDamageScaleAtMaxRadius"] or 0))
        if altFalloffStartRange and altFalloffStartRange ~= 0 then
            table.insert(rows, {
                name = name,
                sortName = name .. " (alt fire)",
                nameSuffix = " (alt fire)",
                falloffStartRange = altFalloffStartRange,
                falloffEndRange = altFalloffEndRange,
                altExplosionRadius = data["Weapon>AltFire>ExplosionRadius"] or 0,
                falloffEndScale = -100 + (100 * (data["Weapon>FalloffEndScale"] or 0))
            })
        end
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
     local tableDefs = {
        "|+ Base Falloff Range \n"..
    {
        "! Hero !! Minimum Range (m) !! Maximum Range (m)  \n"
            title  = "Base Falloff Range",
            columns = {
                { label = "Minimum Range (m)", field = "falloffStartRange"  },
                { label = "Maximum Range (m)", field = "falloffEndRange"  },
                { label = "Damage Falloff (%)", field = "falloffEndScale" },
                { label = "Explosion Radius (m)", field = "altExplosionRadius" },
            }
        }   
    }


     for _, r in ipairs(rows) do
    local output = {}
         z = z..
     for _, def in ipairs(tableDefs) do
            "|- \n"..
         local t = buildTable(frame, rows, def, def.options)
             "| style=\"text-align:left;\" | "..
        if t ~= "" then
                frame:expandTemplate{title="PageRef", args={r.name}}..
             table.insert(output, t)
                " || "..r.falloffStartRange.." || "..r.falloffEndRange.." \n"
        end
     end
     end
     z = z.."|}"
 
    return z
     return table.concat(output, "\n\n")
end
end


p.heroBulletDamageTable = function(frame)
p.heroBulletDamageTable = function(frame)
     -- Pull data
     -- Pull data
     local heroBulletDamageData = heroDataArray({"Name", "Weapon>BulletDamage", "LevelScaling>BulletDamage"})
     local heroBulletDamageData = heroDataArray({"Name", "Weapon>BulletDamage", "LevelScaling>BulletDamage", "Weapon>AltFire>BulletDamage", "LevelScaling>BulletDamageAltFire",
    "SpiritScaling>BulletDamage", "Weapon>BulletsPerShot", "Weapon>BulletsPerBurst"})


     local rows = {}
     local rows = {}
Line 337: Line 509:
         table.insert(rows, {
         table.insert(rows, {
             name = name,
             name = name,
             bulletDamage = tonumber(data["Weapon>BulletDamage"]) or 0,
             bulletDamage = data["Weapon>BulletDamage"] or 0,
             scalingDamage = tonumber(data["LevelScaling>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
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
     local tableDefs = {
         "|+ Hero Base Bullet Damage Stats \n"..
    {
         "! Hero !! Starting !! Added per Boon !! At Max Boon \n"
            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


     -- Iterate over heroes adding a new row to the wikitable for each
p.heroMeleeTable = function(frame)
     for _, r in ipairs(rows) do
     -- Pull data
         z = z..
    local heroMeleeData = heroDataArray({"Name", "LightMeleeDamage", "LevelScaling>LightMeleeDamage", "HeavyMeleeDamage", "LevelScaling>HeavyMeleeDamage", "SpiritScaling>HeavyMeleeDamage"})
             "|- \n"..
 
             "| style=\"text-align:left;\" | "..
    local rows = {}
                frame:expandTemplate{title="PageRef", args={r.name}}..
     for name, data in pairs(heroMeleeData) do
                " || "..r.bulletDamage.." || +"..r.scalingDamage.." || "..
         table.insert(rows, {
                tostring(roundValue(r.bulletDamage + r.scalingDamage * soul_unlock.get_max('PowerIncrease'))).." \n"
            name = name,
            lightMeleeDamage = data["LightMeleeDamage"] or 0,
             lightScalingDamage = "+" .. data["LevelScaling>LightMeleeDamage"] or 0,
             lightMaxScalingDamage = (roundValueFour(data["LightMeleeDamage"] + data["LevelScaling>LightMeleeDamage"] * soul_unlock.get_max("PowerIncrease"))) or 0,
            heavyMeleeDamage = data["HeavyMeleeDamage"] or 0,
            heavyScalingDamage = "+" .. data["LevelScaling>HeavyMeleeDamage"] or 0,
            heavyMaxScalingDamage =  (roundValueFour(data["HeavyMeleeDamage"] + data["LevelScaling>HeavyMeleeDamage"] * soul_unlock.get_max("PowerIncrease"))) or 0,
            heavySpiritScalingDamage = "+" .. (data["SpiritScaling>HeavyMeleeDamage"] or 0),
        })
     end
     end
    sortHeroes(rows)
    local tableDefs = {
    {
            title  = "Base Light & Heavy Melee Stats",
            columns = {
                { label = "Starting Light", field = "lightMeleeDamage"  },
                { label = "Added per Boon (L)", field = "lightScalingDamage"  },
                { label = "At Max Boon (L)", field = "lightMaxScalingDamage"  },
                { label = "Starting Heavy", field = "heavyMeleeDamage"  },
                { label = "Added per Boon (H)", field = "heavyScalingDamage"  },
                { label = "At Max Boon (H)", field = "heavyMaxScalingDamage"  },
                { label = "Spirit Scaling (H)", field = "heavySpiritScalingDamage"  },
               
            }
        }
    }
      
      
     z = z.."|}"
     local output = {}
     return z
    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


Line 369: Line 611:
         table.insert(rows, {
         table.insert(rows, {
             name = name,
             name = name,
             lightMeleeDamage = tonumber(data["LightMeleeDamage"]) or 0,
             lightMeleeDamage = data["LightMeleeDamage"] or 0,
             scalingDamage = tonumber(data["LevelScaling>LightMeleeDamage"]) or 0
             scalingDamage = "+" .. data["LevelScaling>LightMeleeDamage"] or 0,
            maxScalingDamage =  (roundValueFour(data["LightMeleeDamage"] + data["LevelScaling>LightMeleeDamage"] * soul_unlock.get_max("PowerIncrease"))) or 0,
         })
         })
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
     local tableDefs = {
        "|+ Hero Base Light Melee Stats \n"..
    {
        "! Hero !! Starting !! Added per Boon !! At Max Boon \n"
            title  = "Base Light Melee Stats",
            columns = {
                { label = "Starting", field = "lightMeleeDamage" },
                { label = "Added per Boon", field = "scalingDamage"  },
                { label = "At Max Boon", field = "maxScalingDamage"  },
            }
        }
    }
   
    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


     -- Iterate over heroes adding a new row to the wikitable for each
     return table.concat(output, "\n\n")
    for _, r in ipairs(rows) do
        z = z..
            "|- \n"..
            "| style=\"text-align:left;\" | "..
                frame:expandTemplate{title="PageRef", args={r.name}}..
                " || "..r.lightMeleeDamage.." || +"..r.scalingDamage.." || "..
                tostring(roundValue(r.lightMeleeDamage + r.scalingDamage * soul_unlock.get_max('PowerIncrease'))).." \n"
    end
   
    z = z.."|}"
    return z
end
end


p.heroHeavyMeleeTable = function(frame)
p.heroHeavyMeleeTable = function(frame)
     -- Pull data
     -- Pull data
     local heroHeavyMeleeData = heroDataArray({"Name", "HeavyMeleeDamage", "LevelScaling>HeavyMeleeDamage"})
     local heroHeavyMeleeData = heroDataArray({"Name", "HeavyMeleeDamage", "LevelScaling>HeavyMeleeDamage", "SpiritScaling>HeavyMeleeDamage"})


     local rows = {}
     local rows = {}
Line 401: Line 648:
         table.insert(rows, {
         table.insert(rows, {
             name = name,
             name = name,
             heavyMeleeDamage = tonumber(data["HeavyMeleeDamage"]) or 0,
             heavyMeleeDamage = data["HeavyMeleeDamage"] or 0,
             scalingDamage = tonumber(data["LevelScaling>HeavyMeleeDamage"]) or 0
             scalingDamage = "+" .. data["LevelScaling>HeavyMeleeDamage"] or 0,
            maxScalingDamage = (roundValueFour(data["HeavyMeleeDamage"] + data["LevelScaling>HeavyMeleeDamage"] * soul_unlock.get_max("PowerIncrease"))) or 0,
            spiritScalingDamage = "+" .. (data["SpiritScaling>HeavyMeleeDamage"] or 0),
         })
         })
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
     local tableDefs = {
        "|+ Hero Base Heavy Melee Stats \n"..
    {
        "! Hero !! Starting !! Added per Boon !! At Max Boon \n"
            title  = "Base Heavy Melee Stats",
            columns = {
                { label = "Starting", field = "heavyMeleeDamage" },
                { 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


     -- Iterate over heroes adding a new row to the wikitable for each
     return table.concat(output, "\n\n")
    for _, r in ipairs(rows) do
        z = z..
            "|- \n"..
            "| style=\"text-align:left;\" | "..
                frame:expandTemplate{title="PageRef", args={r.name}}..
                " || "..r.heavyMeleeDamage.." || +"..r.scalingDamage.." || "..
                tostring(roundValue(r.heavyMeleeDamage + r.scalingDamage * soul_unlock.get_max('PowerIncrease'))).." \n"
    end
   
    z = z.."|}"
    return z
end
end


Line 436: Line 690:
         })
         })
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
     local tableDefs = {
         "|+ Hero Base Stamina Stats \n"..
    {
         "! Hero !! Stamina Bars !! Cooldown (s) \n"
            title  = "Base Stamina Stats",
            columns = {
                { label = "Stamina Bars", field = "stamina" },
                { label = "Cooldown (s)", field = "staminaCooldown" },
            }
        }
    }
   
    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


     for _, r in ipairs(rows) do
     return table.concat(output, "\n\n")
        z = z..
            "|- \n"..
            "| style=\"text-align:left;\" | "..
                frame:expandTemplate{title="PageRef", args={r.name}}..
                " || "..r.stamina.." || "..r.staminaCooldown.." \n"
    end
    z = z.."|}"
    return z
end
end


p.heroDashTable = function(frame)
p.heroDashTable = function(frame)
     local heroDashData = heroDataArray({"Name", "GroundDashSpeed"})
     local heroDashData = heroDataArray({"Name", "GroundDashSpeed", "GroundDashDuration", "AirDashSpeed", "AirDashDuration"})


     local rows = {}
     local rows = {}
Line 460: Line 720:
         table.insert(rows, {
         table.insert(rows, {
             name    = name,
             name    = name,
             duration = tonumber(data.GroundDashSpeed) or 0
             dashSpeed = data["GroundDashSpeed"] or 0,
            dashDuration = data["GroundDashDuration"] or 0,
            airDashSpeed = data["AirDashSpeed"] or 0,
            airDashDuration = data["AirDashDuration"] or 0,
         })
         })
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)
   
    local tableDefs = {
    {
            title  = "Base Dash Speed Stats",
            columns = {
                { label = "Dash Speed (m/s)", field = "dashSpeed"  },
                { label = "Dash Duration (s)", field = "dashDuration"  },
                { label = "Air Dash Speed (m/s)", field = "airDashSpeed"  },
                { label = "Air Dash Duration (s)", field = "airDashDuration"  },
            }
        }
    }
   
    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


     local z = '{| class="wikitable sortable mw-collapsible" style="text-align:center"\n' ..
     return table.concat(output, "\n\n")
              '|+ Hero Base Dash Speed Stats\n' ..
              '! Hero !! Dash Speed (m/s)\n'
    for _, r in ipairs(rows) do
        z = z .. '|-\n| style="text-align:left;" | ' ..
              frame:expandTemplate{title = 'PageRef', args = {r.name}} ..
              ' || ' .. r.duration .. '\n'
    end
    return z .. '|}'
end
end


Line 479: Line 754:
     local heroReloadData = heroDataArray({"Name", "Weapon>ReloadTime"})
     local heroReloadData = heroDataArray({"Name", "Weapon>ReloadTime"})


    -- 1. collect rows
     local rows = {}
     local rows = {}
     for name, data in pairs(heroReloadData) do
     for name, data in pairs(heroReloadData) do
         table.insert(rows, {
         table.insert(rows, {
             name      = name,
             name      = name,
             reloadTime = tonumber(data["Weapon>ReloadTime"]) or 0
             reloadTime = data["Weapon>ReloadTime"] or 0
         })
         })
     end
     end


     -- 2. alphabetical by hero name
     sortHeroes(rows)
    table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)


     -- 3. build table
     local tableDefs = {
    local z = '{| class="wikitable sortable mw-collapsible" style="text-align:center"\n' ..
    {
              '|+ Hero Base Reload Time Stats\n' ..
            title  = "Base Reload Time",
              '! Hero !! Reload Time (s) \n'
            columns = {
                { label = "Reload Time (s)", field = "reloadTime" },
            }
        }
    }
   
    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


     for _, r in ipairs(rows) do
     return table.concat(output, "\n\n")
        z = z ..
            '|-\n' ..
            '| style="text-align:left;" | ' ..
            frame:expandTemplate{title = 'PageRef', args = {r.name}} ..
            ' || '.. r.reloadTime ..' \n'
    end
    return z .. '|}'
end
end


Line 516: Line 794:
         })
         })
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local tableDefs = {
     local tableDefs = {
     {
     {
             title  = "Hero Base Reload Delay Stats",
             title  = "Base Reload Delay",
             columns = {
             columns = {
                 { label = "Reload Delay (s)", field = "reloadDelay"  }
                 { label = "Reload Delay (s)", field = "reloadDelay"  }
Line 527: Line 805:
     }
     }


     local output = ""
     local output = {}
 
     for _, def in ipairs(tableDefs) do
     for _, tableDef in ipairs(tableDefs) do
         local t = buildTable(frame, rows, def, def.options)
         local filteredRows = {}
        if t ~= "" then
        for _, r in ipairs(rows) do
             table.insert(output, t)
            local hasValue = false
            for _, col in ipairs(tableDef.columns) do
                if r[col.field] ~= 0 then
                    hasValue = true
                    break
                end
             end
            if hasValue then
                table.insert(filteredRows, r)
            end
        end
 
        if #filteredRows > 0 then
            local header = ""
            for _, col in ipairs(tableDef.columns) do
                header = header.." !! "..col.label
            end
 
            local t = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
                "|+ "..tableDef.title.." \n"..
                "! Hero"..header.." \n"
 
            for _, r in ipairs(filteredRows) do
                local cells = ""
                for _, col in ipairs(tableDef.columns) do
                    cells = cells.." || "..r[col.field]
                end
                t = t..
                    "|- \n"..
                    "| style=\"text-align:left;\" | "..
                        frame:expandTemplate{title="PageRef", args={r.name}}..
                        cells.." \n"
            end
 
            t = t.."|}"
            if output ~= "" then
                output = output.."\n\n"
            end
            output = output..t
         end
         end
     end
     end


     return output
     return table.concat(output, "\n\n")
end
end


p.heroMoveSpeedTable = function(frame)
p.heroMoveSpeedTable = function(frame)
     local heroMoveSpeedData = heroDataArray({"Name", "MaxMoveSpeed", "SprintSpeed"})
     local heroMoveSpeedData = heroDataArray({"Name", "MaxMoveSpeed", "SprintSpeed", "Weapon>ShootMoveSpeed", "SpiritScaling>MaxMoveSpeed", "SpiritScaling>SprintSpeed"})


     local rows = {}
     local rows = {}
Line 584: Line 823:
         table.insert(rows, {
         table.insert(rows, {
             name = name,
             name = name,
             moveSpeed = tonumber(data["MaxMoveSpeed"]) or 0,
             moveSpeed = data["MaxMoveSpeed"] or 0,
             sprintSpeed = tonumber(data["SprintSpeed"]) or 0
            sprintSpeed = data["SprintSpeed"] or 0,
            totalSpeed = data["MaxMoveSpeed"] + data["SprintSpeed"] or 0,
            shootSpeed = (data["Weapon>ShootMoveSpeed"] or 0) * 100,
            totalShootingSpeed = roundValue((data["MaxMoveSpeed"] + data["SprintSpeed"] or 0) * (data["Weapon>ShootMoveSpeed"] or 0)),
             spiritMoveSpeed = "+" .. (data["SpiritScaling>MaxMoveSpeed"] or 0),
            spiritSprintSpeed = "+" .. (data["SpiritScaling>SprintSpeed"] or 0),
         })
         })
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local z = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
     local tableDefs = {
        "|+ Hero Base Move Speed Stats \n"..
    {
        "! Hero !! Move Speed (m/s) !! Sprint Speed (m/s) !! Total Speed (m/s) \n"
            title  = "Base Move Speed Stats",
            columns = {
                { label = "Move Speed (m/s)", field = "moveSpeed"  },
                { label = "Sprint Speed (m/s)", field = "sprintSpeed"  },
                { label = "Total Speed (m/s)", field = "totalSpeed"  },
                { label = "Shooting Speed (m/s)", field = "totalShootingSpeed"  },
                { label = "Shooting Mult (%)", field = "shootSpeed"  },
                { label = "MS Spirit Scaling", field = "spiritMoveSpeed"  },
                { label = "SS Spirit Scaling", field = "spiritSprintSpeed"  },
            }
        }   
    }


     for _, r in ipairs(rows) do
    local output = {}
         z = z..
     for _, def in ipairs(tableDefs) do
            "|- \n"..
         local t = buildTable(frame, rows, def, def.options)
             "| style=\"text-align:left;\" | "..
        if t ~= "" then
                frame:expandTemplate{title="PageRef", args={r.name}}..
             table.insert(output, t)
                " || "..r.moveSpeed.." || "..r.sprintSpeed.." || "..r.moveSpeed + r.sprintSpeed .." \n"
        end
     end
     end
     z = z.."|}"
 
    return z
     return table.concat(output, "\n\n")
end
end


p.heroDamageResistTable = function(frame)
p.heroDamageResistTable = function(frame)
     local heroDamageResistData = heroDataArray({"Name", "BulletResist", "LevelScaling>BulletResist", "TechResist", "LevelScaling>TechResist", "MeleeResist", "CritDamageReceivedPercent"})
     local heroDamageResistData = heroDataArray({"Name", "BulletResist", "LevelScaling>BulletResist", "TechResist", "LevelScaling>TechResist", "MeleeResist", "CritDamageReceivedPercent",
  "SpiritScaling>BulletResist", "SpiritScaling>TechResist",
    })


     local rows = {}
     local rows = {}
Line 613: Line 870:
             name = name,
             name = name,
             bulletResist  = data["BulletResist"] or 0,
             bulletResist  = data["BulletResist"] or 0,
             bulletScaling = data["LevelScaling>BulletResist"] or 0,
             bulletResistScaling = "+".. (data["LevelScaling>BulletResist"] or 0),
            bulletResistScalingMax = ((roundValue((data["BulletResist"] or 0) + (data["LevelScaling>BulletResist"] or 0) * soul_unlock.get_max("PowerIncrease"))) or 0),
            bulletResistSpiritScaling = "+".. (data["SpiritScaling>BulletResist"] or 0),
             spiritResist  = data["TechResist"] or 0,
             spiritResist  = data["TechResist"] or 0,
             spiritScaling = data["LevelScaling>TechResist"] or 0,
             spiritResistScaling = "+".. (data["LevelScaling>TechResist"] or 0),
            spiritResistScalingMax = ((roundValue((data["TechResist"] or 0) + (data["LevelScaling>TechResist"] or 0) * soul_unlock.get_max("PowerIncrease"))) or 0),
            spiritResistSpiritScaling = "+".. (data["SpiritScaling>TechResist"] or 0),
             meleeResist  = data["MeleeResist"] or 0,
             meleeResist  = data["MeleeResist"] or 0,
             critReduction = data["CritDamageReceivedPercent"] or 0
             critReduction = data["CritDamageReceivedPercent"] or 0
         })
         })
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local tableDefs = {
     local tableDefs = {
         {
         {
             title  = "Hero Base Bullet Resist Stats",
             title  = "Base Bullet Resist Stats",
             columns = {
             columns = {
                 { label = "Bullet Resist (%)", field = "bulletResist"  },
                 { label = "Starting (%)", field = "bulletResist"  },
                 { label = "BR per Boon (%)",  field = "bulletScaling" },
                 { label = "Added per Boon (%)",  field = "bulletResistScaling" },
                { label = "At Max Boon (%)",  field = "bulletResistScalingMax" },
                { label = "Spirit Scaling (%)",  field = "bulletResistSpiritScaling" },
             }
             }
         },
         },
         {
         {
             title  = "Hero Base Spirit Resist Stats",
             title  = "Base Spirit Resist Stats",
             columns = {
             columns = {
                 { label = "Spirit Resist (%)", field = "spiritResist"  },
                 { label = "Starting (%)", field = "spiritResist"  },
                 { label = "SR per Boon (%)",  field = "spiritScaling" },
                 { label = "Added per Boon (%)",  field = "spiritResistScaling" },
                { label = "At Max Boon (%)",  field = "spiritResistScalingMax" },
                { label = "Spirit Scaling (%)",  field = "spiritResistSpiritScaling" },
             }
             }
         },
         },
         {
         {
             title  = "Hero Base Melee Resist Stats",
             title  = "Base Melee Resist Stats",
             columns = {
             columns = {
                 { label = "Melee Resist (%)", field = "meleeResist" },
                 { label = "Melee Resist (%)", field = "meleeResist" },
Line 644: Line 909:
         },
         },
         {
         {
             title  = "Hero Base Crit Reduction Stats",
             title  = "Base Crit Reduction Stats",
             columns = {
             columns = {
                 { label = "Crit Reduction (%)", field = "critReduction" },
                 { label = "Crit Reduction (%)", field = "critReduction" },
Line 651: Line 916:
     }
     }


     local output = ""
     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
 
p.heroCritTable = function(frame)
    local heroCritData = heroDataArray({"Name", "CritDamageBonusPercent"})
 
    local rows = {}
    for name, data in pairs(heroCritData) do
        table.insert(rows, {
            name = name,
            critBonusScale  = data["CritDamageBonusPercent"] or 0
        })
    end
    sortHeroes(rows)
 
    local tableDefs = {
    {
            title  = "Base Crit Bonus Scale Stats",
            columns = {
                { label = "Crit Bonus Scale (%)", field = "critBonusScale"  }
            }
        }   
    }


     for _, tableDef in ipairs(tableDefs) do
     local output = {}
        local filteredRows = {}
    for _, def in ipairs(tableDefs) do
        for _, r in ipairs(rows) do
        local t = buildTable(frame, rows, def, def.options)
            local hasValue = false
        if t ~= "" then
            for _, col in ipairs(tableDef.columns) do
             table.insert(output, t)
                if r[col.field] ~= 0 then
                    hasValue = true
                    break
                end
            end
             if hasValue then
                table.insert(filteredRows, r)
            end
         end
         end
    end


        if #filteredRows > 0 then
    return table.concat(output, "\n\n")
            local header = ""
end
            for _, col in ipairs(tableDef.columns) do
                header = header.." !! "..col.label
            end


            local t = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
p.heroSpiritTable = function(frame)
                "|+ "..tableDef.title.." \n"..
    local heroSpiritData = heroDataArray({"Name", "LevelScaling>TechPower"})
                "! Hero"..header.." \n"


            for _, r in ipairs(filteredRows) do
    local rows = {}
                local cells = ""
    for name, data in pairs(heroSpiritData) do
                for _, col in ipairs(tableDef.columns) do
        table.insert(rows, {
                    cells = cells.." || "..r[col.field]
            name = name,
                end
            spiritScaling  = "+".. data["LevelScaling>TechPower"] or 0,
                t = t..
            maxSpiritScaling = (data["LevelScaling>TechPower"] * soul_unlock.get_max("PowerIncrease")) or 0,
                    "|- \n"..
        })
                    "| style=\"text-align:left;\" | "..
    end
                        frame:expandTemplate{title="PageRef", args={r.name}}..
    sortHeroes(rows)
                        cells.." \n"
 
            end
    local tableDefs = {
    {
            title  = "Spirit Scaling Stats",
            columns = {
                { label = "Spirit Per Boon", field = "spiritScaling" },
                { label = "At Max Boon", field = "maxSpiritScaling"  },
            }
        }   
    }


            t = t.."|}"
    local output = {}
            if output ~= "" then
    for _, def in ipairs(tableDefs) do
                output = output.."\n\n"
        local t = buildTable(frame, rows, def, def.options)
            end
        if t ~= "" then
            output = output..t
            table.insert(output, t)
         end
         end
     end
     end


     return output
     return table.concat(output, "\n\n")
end
end


p.heroCritTable = function(frame)
p.heroGravityTable = function(frame)
     local heroCritData = heroDataArray({"Name", "CritDamageBonusPercent"})
     local heroGravityData = heroDataArray({"Name", "GravityChange"})


     local rows = {}
     local rows = {}
     for name, data in pairs(heroCritData) do
     for name, data in pairs(heroGravityData) do
         table.insert(rows, {
         table.insert(rows, {
             name = name,
             name = name,
             critBonusScale = data["CritDamageBonusPercent"] or 0
             gravityScale = (data["GravityChange"] or 0),
         })
         })
     end
     end
     table.sort(rows, function(a, b) return a.name:lower() < b.name:lower() end)
     sortHeroes(rows)


     local tableDefs = {
     local tableDefs = {
     {
     {
             title  = "Hero Base Crit Bonus Scale Stats",
             title  = "Base Gravity Scale Stats",
             columns = {
             columns = {
                 { label = "Crit Bonus Scale (%)", field = "critBonusScale"  }
                 { label = "Gravity Scale (%)", field = "gravityScale"  },
             }
             }
         }     
         }     
     }
     }


     local output = ""
     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


     for _, tableDef in ipairs(tableDefs) do
     return table.concat(output, "\n\n")
        local filteredRows = {}
end
        for _, r in ipairs(rows) do
 
             local hasValue = false
p.bulletGravityTable = function(frame)
             for _, col in ipairs(tableDef.columns) do
    local heroGravityData = heroDataArray({"Name", "Weapon>BulletGravityScale", "Weapon>AltFire>BulletGravityScale"})
                 if r[col.field] ~= 0 then
 
                    hasValue = true
    local rows = {}
                    break
    for name, data in pairs(heroGravityData) do
                 end
        table.insert(rows, {
             end
            name = name,
            if hasValue then
            gravityScale  = (data["Weapon>BulletGravityScale"] or 0),
                table.insert(filteredRows, r)
             altGravityScale  = (data["Weapon>AltFire>BulletGravityScale"] or 0),
            end
        })
    end
    sortHeroes(rows)
 
    local tableDefs = {
    {
             title  = "Base Bullet Gravity Scale Stats",
            columns = {
                 { label = "Gravity Scale", field = "gravityScale"  },
            }
        },
        {
            title  = "Base Alt-fire Bullet Gravity Scale Stats",
            columns = {
                 { label = "Gravity Scale", field = "altGravityScale"  },
             }
        }
    }
 
    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
    end


        if #filteredRows > 0 then
    return table.concat(output, "\n\n")
            local header = ""
end
            for _, col in ipairs(tableDef.columns) do
                header = header.." !! "..col.label
            end


            local t = "{| class=\"wikitable sortable mw-collapsible\" style=\"text-align:center\" \n"..
p.bulletRadiusTable = function(frame)
                "|+ "..tableDef.title.." \n"..
    local heroBulletRadiusData = heroDataArray({"Name", "Weapon>BulletRadius", "Weapon>AltFire>BulletRadius"})
                "! Hero"..header.." \n"


            for _, r in ipairs(filteredRows) do
    local rows = {}
                local cells = ""
    for name, data in pairs(heroBulletRadiusData) do
                for _, col in ipairs(tableDef.columns) do
        table.insert(rows, {
                    cells = cells.." || "..r[col.field]
            name = name,
                 end
            bulletRadius  = (data["Weapon>BulletRadius"] or 0) * 100,
                 t = t..
            bulletRadiusInches  = roundValueInteger((data["Weapon>BulletRadius"] or 0) * 100 * 0.394),
                    "|- \n"..
            altBulletRadius  = (data["Weapon>AltFire>BulletRadius"] or 0) * 100,
                    "| style=\"text-align:left;\" | "..
            altBulletRadiusInches  = roundValueInteger((data["Weapon>AltFire>BulletRadius"] or 0) * 100 * 0.394),
                        frame:expandTemplate{title="PageRef", args={r.name}}..
        })
                        cells.." \n"
       
             end
        local altBulletRadius = (data["Weapon>AltFire>BulletRadius"] or 0) * 100
        local altBulletRadiusInches  = roundValueInteger((data["Weapon>AltFire>BulletRadius"] or 0) * 100 * 0.394)
        if altBulletRadius and altBulletRadius ~= 0 then
            table.insert(rows, {
                 name = name,
                 sortName = name .. " (alt fire)",
                nameSuffix = " (alt fire)",
                bulletRadius = altBulletRadius,
                bulletRadiusInches = altBulletRadiusInches
            })
        end
    end
    sortHeroes(rows)
 
    local tableDefs = {
    {
            title   = "Base Bullet Radius Stats",
            columns = {
                { label = "Bullet Radius (cm)", field = "bulletRadius"  },
                { label = "Bullet Radius (in)", field = "bulletRadiusInches" },
             }
        }
    }


            t = t.."|}"
    local output = {}
            if output ~= "" then
    for _, def in ipairs(tableDefs) do
                output = output.."\n\n"
        local t = buildTable(frame, rows, def, def.options)
            end
        if t ~= "" then
            output = output..t
            table.insert(output, t)
         end
         end
     end
     end


     return output
     return table.concat(output, "\n\n")
end
end


return p
return p