Editing Module:LevelTables

Warning: You are not logged in. Once you make an edit, a temporary account will be created for you. Learn more. Log in or create an account to continue receiving notifications after this account expires, and to access other features.
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 Icon = require('Module:Icon')
local Souls = require('Module:Souls')
-- Loads the necessary data from the wiki's JSON pages.
local heroes_data = mw.loadJsonData("Data:HeroData.json")
local heroes_data = mw.loadJsonData("Data:HeroData.json")
local soul_unlocks_data = mw.loadJsonData("Data:SoulUnlockData.json")
local soul_unlocks_data = mw.loadJsonData("Data:SoulUnlockData.json")


-- Helper to safely get stats that may be nested under Weapon
local function get_stat_value(data, key)
    if data[key] ~= nil then return data[key] end
    if data.Weapon and data.Weapon[key] ~= nil then return data.Weapon[key] end
    return 0
end
-- Defines human-readable names for the internal property keys used in headers.
local level_scaling_key_to_name = {
local level_scaling_key_to_name = {
BulletDamage = "Bullet Damage",
BulletDamage = "Bullet Damage",
Line 24: Line 12:
}
}


-- A helper function that returns a sort-friendly version of a name by removing leading articles like "The ".
-- Not in HeroData.json although Shiv is just (bullet damage * 12)
local function get_sortable_name(name)
local alt_fires = {
if string.sub(name, 1, 4) == "The " then
Shiv = {base = 55.2, level = 2.64, suffix = "alt fire 12 pellets"},
return string.sub(name, 5)
Viscous = {base = 63, level = 0.9, suffix = "alt fire AOE"},
end
Yamato = {base = 63.36, level = 0.35, suffix = "alt fire AOE"}
return name
}
end


--[[
-- Outputs a wikitable of the boon scaling of the inputted property by hero
-- @function p.write_power_increase_table
-- @function   p.write_power_increase_table
-- @description Generates a responsive and readable table showing hero stat scaling based on boons.
-- @param     {string}
--              Includes a compact summary view and an expandable, detailed view for all levels.
-- @return     {string}
--              Can optionally wrap the entire output in a theme-aware collapsible section.
-- @param {Frame} frame The MediaWiki frame object.
-- @param {string} frame.args[1] The internal property name to display (e.g., 'BulletDamage').
-- @param {string} [frame.args.title] An optional title. If provided, the table is wrapped in a collapsible section.
-- @return {string} The complete HTML for the table or collapsible section.
--]]
p.write_power_increase_table = function(frame)
p.write_power_increase_table = function(frame)
    -- Use the same smart sorting for this list.
local function hero_sort(h1, h2)
return get_sortable_name(h1.Name) < get_sortable_name(h2.Name)
end
-- Read and validate the arguments passed from the #invoke call.
local property = frame.args[1]
local title = frame.args.title
if not property or property == '' then
return '<strong class="error">Error: Missing required property argument in #invoke call. Example: {{#invoke:ModuleName|write_power_increase_table|BulletDamage}}</strong>'
end
local name = level_scaling_key_to_name[property]
if not name then
return '<strong class="error">Error: Invalid property argument "' .. tostring(property) .. '". Must be one of: BulletDamage, MaxHealth, TechPower, LightMeleeDamage, HeavyMeleeDamage.</strong>'
end
-- Create an ordered list of soul requirements for each power increase.
local pi_souls_list = {}
local pi_souls_list = {}
for _, v in ipairs(soul_unlocks_data) do
for _, v in ipairs(soul_unlocks_data) do
Line 68: Line 30:
end
end
end
end
local level_count = 0
for _ in ipairs(pi_souls_list) do level_count = level_count + 1 end
local max_boons = #pi_souls_list
-- Define the milestones to display in the summary table.
local milestones = {10, 20, max_boons}
-- Filter out disabled heroes from the data.
local filtered_heroes = {}
local filtered_heroes = {}
for _, hero in pairs(heroes_data) do
for _, hero in pairs(heroes_data) do
if (not hero["IsDisabled"] and not hero["InDevelopment"] and hero["Name"]) then
if (not hero["IsDisabled"]) then
table.insert(filtered_heroes, hero)
table.insert(filtered_heroes, hero)
end
end
end
end
-- Sort the hero list alphabetically, ignoring "The " for proper sorting.
local property = frame.args[1] --frame:getParent().args[1]
table.sort(filtered_heroes, hero_sort)
local name = level_scaling_key_to_name[property]
local suffix = property == "BulletDamage" and " (Full Shot)" or ""
local create_table = mw.html.create("table"):addClass("wikitable sortable mw-collapsible mw-collapsed"):done()
local title_header = mw.html.create("tr")
title_header:tag("th"):attr("colspan", (level_count + 3)):wikitext("Base " .. name .. " based on Boons"):done()
create_table:node(title_header)
-- Begin building the main data table.
local label_header = mw.html.create("tr")
local create_table = mw.html.create("table"):addClass("wikitable sortable")
label_header
:tag("th"):attr("rowspan", 2):wikitext("Hero"):done()
:tag("th"):attr("rowspan", 2):wikitext("Base " .. name .. suffix):done()
:tag("th"):attr("rowspan", 2):wikitext("Boons" .. suffix):done()
:tag("th"):attr("colspan", level_count):wikitext("Souls Collected"):done()
create_table:node(label_header)
-- Create the header row for the summary table.
local souls_header = mw.html.create("tr")
local header_row = mw.html.create("tr")
for _, v in ipairs(pi_souls_list) do
header_row
local souls_formatted = string.format("%.1fK", (v + 400) / 1000)
:tag("th"):wikitext("Hero"):done()
souls_formatted = frame:expandTemplate{ title = "Souls", args = {souls_formatted}}
:tag("th"):wikitext("Base " .. name):done()
souls_header:tag("th"):wikitext(souls_formatted):done()
:tag("th"):wikitext("Increase per Boon"):done()
 
-- Dynamically create milestone headers with both boon count and soul amount.
for _, count in ipairs(milestones) do
local header_text = count .. " Boons"
if count == max_boons then header_text = header_text .. " (Max)" end
local required_souls = pi_souls_list[count]
local souls_formatted = string.format("%.1fK", (required_souls + 400) / 1000)
local soul_display = Souls._render(souls_formatted)
local full_header = header_text .. "<br /><small>(" .. soul_display .. ")</small>"
header_row:tag("th"):wikitext(full_header):done()
end
end
create_table:node(souls_header)
header_row:tag("th"):wikitext("Full Details"):done()
for _, hero in ipairs(filtered_heroes) do
create_table:node(header_row)
local base = math.floor((hero[property] or 0) * 100) / 100
local level = math.floor(hero["LevelScaling"][property] * 100) / 100
-- A helper function that generates the HTML for a single hero's row and its collapsible details.
local shots = hero["BulletsPerShot"]
local function addHeroRows(hero_data, is_alt_fire)
local burst = hero["BulletsPerBurst"]
local base, level, suffix
local bullet_mult = shots * burst
local hero_icon_wikitext = Icon._render(hero_data['Name'])
local hero_icon = frame:expandTemplate{ title = "HeroIcon", args = {hero["Name"]}}
local hero_suffix = ""
-- Determine if this is a main weapon or an alt-fire and set the correct data.
if (property == "BulletDamage" and bullet_mult > 1) then
if is_alt_fire then
base = base * bullet_mult
base = hero_data.alt_fire.base
level = level * bullet_mult
level = hero_data.alt_fire.level
if (shots > 1) then hero_suffix = hero_suffix .. " (" .. shots .. " pellets)" end
suffix = " (" .. hero_data.alt_fire.suffix .. ")"
if (burst > 1) then hero_suffix = hero_suffix .. " (" .. burst .. " bullets)" end
else
base = get_stat_value(hero_data, property)
level = (hero_data["LevelScaling"] and hero_data["LevelScaling"][property] or 0)
suffix = ""
local shots = get_stat_value(hero_data, "BulletsPerShot")
if shots == 0 then shots = 1 end
local burst = get_stat_value(hero_data, "BulletsPerBurst")
if burst == 0 then burst = 1 end
local hit_once = get_stat_value(hero_data, "HitOnceAcrossAllBullets")
if (property == "BulletDamage" and (shots * burst) > 1) then
if (not hit_once) then
base = base * shots * burst
level = level * shots * burst
end
if (shots > 1) then suffix = suffix .. " (" .. shots .. " pellets)" end
if (burst > 1) then suffix = suffix .. " (" .. burst .. " bullets)" end
end
end
end
 
local hero_row = mw.html.create("tr")
-- Build the main summary row with the hero's info and milestone values.
hero_row
local summary_row = mw.html.create("tr")
:tag("td"):wikitext(hero_icon .. hero_suffix):done()
:tag("td"):wikitext(base):done()
local hero_cell_content = hero_icon_wikitext .. suffix
:tag("td"):wikitext("+" .. level):done()
summary_row:tag("td"):wikitext(hero_cell_content):done()
for i, v in ipairs(pi_souls_list) do
summary_row
local total = (tonumber(base) ~= nil and base or 0) + (tonumber(level) ~= nil and level or 0)  * i
:tag("td"):wikitext(string.format("%.2f", base)):done()
hero_row
:tag("td"):wikitext("+" .. string.format("%.2f", level)):done()
:tag("td"):wikitext(total):done()
for _, count in ipairs(milestones) do
local total = base + level * count
summary_row:tag("td"):wikitext(string.format("%.2f", total)):done()
end
end
-- Create the main container for the collapsible "Full Details" section.
local details_id = "boon-details-" .. hero_data.Name .. "-" .. property .. (is_alt_fire and "-alt" or "")
local collapsible_container = mw.html.create("div")
:addClass("mw-collapsible mw-collapsed")
:attr("id", details_id)
:attr("style", "display: flex; flex-direction: column; align-items: flex-end; gap: 5px;")
-- Style the "Show All" toggle to look like a theme-aware button.
create_table:node(hero_row)
collapsible_container:tag("div")
    :addClass("mw-collapsible-toggle")
    :attr("style", "display: inline-block; padding: 2px 8px; border: 1px solid currentColor; border-radius: 1px; text-decoration: none;")
    :wikitext("Show All")
-- Create the container for the expanded content.
local content_div = mw.html.create("div"):addClass("mw-collapsible-content")
-- Generate the three-column layout by distributing rows into mini-tables.
if (property == "BulletDamage" and alt_fires[hero["Name"]] ~= nil) then
local column1_rows, column2_rows, column3_rows = {}, {}, {}
local alt_fire = alt_fires[hero["Name"]]
local rows_per_column = math.ceil(max_boons / 3)
local alt_base = alt_fire["base"]
local alt_level = alt_fire["level"]
local alt_suffix = " (" .. alt_fire["suffix"] .. ")"
local alt_fire_row = mw.html.create("tr")
alt_fire_row
:tag("td"):wikitext(hero_icon .. alt_suffix):done()
:tag("td"):wikitext(alt_base):done()
:tag("td"):wikitext("+" .. alt_level):done()
for i = 1, max_boons do
for i, v in ipairs(pi_souls_list) do
local total = base + level * i
local total = alt_base + alt_level * i
local required_souls = pi_souls_list[i]
alt_fire_row:tag("td"):wikitext(total):done()
local souls_formatted = string.format("%.1fK", (required_souls + 400) / 1000)
end
local soul_display = Souls._render(souls_formatted)
local row_html = string.format("<tr><td>%s</td><td style='text-align:right;'>%.2f</td></tr>", soul_display, total)
create_table:node(alt_fire_row)
if i <= rows_per_column then
table.insert(column1_rows, row_html)
elseif i <= rows_per_column * 2 then
table.insert(column2_rows, row_html)
else
table.insert(column3_rows, row_html)
end
end
end
local table_style = "border:none; background:transparent; margin:0 10px; min-width:150px;"
local table1 = "<table style='" .. table_style .. "'>" .. table.concat(column1_rows) .. "</table>"
local table2 = "<table style='" .. table_style .. "'>" .. table.concat(column2_rows) .. "</table>"
local table3 = "<table style='" .. table_style .. "'>" .. table.concat(column3_rows) .. "</table>"
-- Use a flexbox container to display the mini-tables side-by-side.
local flex_container_style = "display:flex; justify-content:space-around; padding:10px 0;"
content_div:wikitext("<div style='" .. flex_container_style .. "'>" .. table1 .. table2 .. table3 .. "</div>")
collapsible_container:node(content_div)
summary_row:tag("td"):node(collapsible_container)
create_table:node(summary_row)
end
end
-- Iterate through each hero to generate their main row and an alt-fire row if applicable.
return tostring(create_table)
for _, hero in ipairs(filtered_heroes) do
addHeroRows(hero, false)
-- Dynamically add alt-fire rows if the data exists
if (property == "BulletDamage" and hero.Weapon and hero.Weapon.AltFire) then
    local alt_fire_data = hero.Weapon.AltFire
    local alt_fire_base = alt_fire_data.BulletDamage or 0
   
    if alt_fire_base > 0 then
        -- Get scaling: prefer alt-fire specific key, fall back to main weapon scaling
        local alt_fire_level = (hero.LevelScaling and
            (hero.LevelScaling.BulletDamageAltFire or hero.LevelScaling[property])) or 0
       
        -- Apply same pellet/burst logic as main weapon
        local shots = alt_fire_data.BulletsPerShot or 1
        local burst = alt_fire_data.BulletsPerBurst or 1
        local hit_once = alt_fire_data.HitOnceAcrossAllBullets or false
       
        local suffix = "alt fire"
       
        if (shots * burst) > 1 and not hit_once then
            alt_fire_base = alt_fire_base * shots * burst
            alt_fire_level = alt_fire_level * shots * burst
            if shots > 1 then suffix = suffix .. " " .. shots .. " pellets" end
            if burst > 1 then suffix = suffix .. " " .. burst .. " bullets" end
        end
       
        -- Add AOE indicator (without radius)
        if alt_fire_data.ExplosionRadius then
            suffix = suffix .. " AOE"
        end
       
        local alt_fire_hero_data = {
            Name = hero.Name,
            alt_fire = {
                base = alt_fire_base,
                level = alt_fire_level,
                suffix = suffix
            }
        }
        addHeroRows(alt_fire_hero_data, true)
    end
end
end
-- If a title was provided, wrap the completed table in a theme-aware collapsible container.
if title and title ~= '' then
local wrapper = mw.html.create('table')
:addClass('mw-collapsible mw-collapsed wikitable')
:css('width', '100%')
:css('margin-bottom', '1em')
 
local header_row_wrapper = wrapper:tag('tr')
 
header_row_wrapper:tag('th')
:css('text-align', 'center')
:wikitext('<span style="font-size: larger; font-weight: bold;">' .. title .. '</span><span class="mw-collapsible-toggle mw-collapsible-toggle-right">[Show]</span>')
 
local content_row_wrapper = wrapper:tag('tr')
 
content_row_wrapper:tag('td')
:addClass('mw-collapsible-content')
:node(create_table)
return tostring(wrapper)
else
-- If no title is provided, return the table as before.
return tostring(create_table)
end
end
end


--[[
-- Outputs a string list of weapon stats from heroes with either spreadshot or burst fire
-- @function p.write_multishot_stats
-- @function  p.write_multishot_stats
-- @description Outputs a wikitext bulleted list of heroes with spreadshot or burst fire weapons.
-- @param     {}
-- @param {Frame} frame The MediaWiki frame object.
-- @return     {string}
-- @return {string} A formatted wikitext string.
--]]
p.write_multishot_stats = function(frame)
p.write_multishot_stats = function(frame)
-- Use the same smart sorting for this list.
local function hero_sort(h1, h2)  
local function hero_sort(h1, h2)  
return get_sortable_name(h1.Name) < get_sortable_name(h2.Name)
return h1["Name"] < h2["Name"]
end
end
local spreadshot_heroes = {}
local spreadshot_heroes = {}
local burst_heroes = {}
local burst_heroes = {}
for _, hero in pairs(heroes_data) do
for _, hero in pairs(heroes_data) do
    local bullets_per_shot = get_stat_value(hero, "BulletsPerShot")
if (hero["BulletsPerShot"] > 1 and not hero["IsDisabled"]) then
    local bullets_per_burst = get_stat_value(hero, "BulletsPerBurst")
   
if (bullets_per_shot > 1 and not hero["IsDisabled"] and not hero["InDevelopment"]) then
table.insert(spreadshot_heroes, hero)
table.insert(spreadshot_heroes, hero)
elseif (bullets_per_burst > 1 and not hero["IsDisabled"] and not hero["InDevelopment"]) then
elseif (hero["BulletsPerBurst"] > 1 and not hero["IsDisabled"]) then
table.insert(burst_heroes, hero)
table.insert(burst_heroes, hero)
end
end
Line 307: Line 139:
local multishot_str = "Weapons with multiple projectiles (Spreadshot):\n"
local multishot_str = "Weapons with multiple projectiles (Spreadshot):\n"
for _, hero in ipairs(spreadshot_heroes) do
for _, hero in ipairs(spreadshot_heroes) do
local base = math.floor(get_stat_value(hero, "BulletDamage") * 100) / 100
local base = math.floor(hero["BulletDamage"] * 100) / 100
local level = math.floor(hero["LevelScaling"]["BulletDamage"] * 100) / 100
local level = math.floor(hero["LevelScaling"]["BulletDamage"] * 100) / 100
local shots = get_stat_value(hero, "BulletsPerShot")
local shots = hero["BulletsPerShot"]
local hit_once = get_stat_value(hero, "HitOnceAcrossAllBullets")
local hero_icon = frame:expandTemplate{ title = "HeroIcon", args = {hero["Name"]}}
local hero_icon = Icon._render(hero["Name"])
local stat_str = "* " .. hero_icon .. ": " .. shots .. " pellets, "  
.. base .. "/pellet, +" .. level .. "/pellet\n"
-- Checks for the special property and changes the output text accordingly.
local stat_str
if hit_once then
stat_str = "* " .. hero_icon .. ": " .. shots .. " pellets, "  
.. base .. " total damage (hits only once), +" .. level .. " total\n"
else
stat_str = "* " .. hero_icon .. ": " .. shots .. " pellets, "
.. base .. "/pellet, +" .. level .. "/pellet\n"
end
multishot_str = multishot_str .. stat_str
multishot_str = multishot_str .. stat_str
end
end
Line 327: Line 150:
multishot_str = multishot_str .. "Weapons with burst fire (Burst Fire):\n"
multishot_str = multishot_str .. "Weapons with burst fire (Burst Fire):\n"
for _, hero in ipairs(burst_heroes) do
for _, hero in ipairs(burst_heroes) do
local base = math.floor(get_stat_value(hero, "BulletDamage") * 100) / 100
local base = math.floor(hero["BulletDamage"] * 100) / 100
local level = math.floor(hero["LevelScaling"]["BulletDamage"] * 100) / 100
local level = math.floor(hero["LevelScaling"]["BulletDamage"] * 100) / 100
local shots = get_stat_value(hero, "BulletsPerBurst")
local shots = hero["BulletsPerBurst"]
local hero_icon = Icon._render(hero["Name"])
local hero_icon = frame:expandTemplate{ title = "HeroIcon", args = {hero["Name"]}}
local stat_str = "* " .. hero_icon .. ": " .. shots .. " rounds, "  
local stat_str = "* " .. hero_icon .. ": " .. shots .. " rounds, "  
.. base .. "/bullet, +" .. level .. "/bullet\n"
.. base .. "/bullet, +" .. level .. "/bullet\n"
Please note that all contributions to The Deadlock Wiki are considered to be released under the Creative Commons Attribution-NonCommercial-ShareAlike (see Deadlock:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!
Cancel Editing help (opens in new window)
Preview page with this template