Module:LevelTables

Revision as of 17:34, 29 May 2025 by LVL (talk | contribs) (Updated Shiv and yamato's base alt fire damage.)

This module is used to create tables that display a stat of the heroes against the souls of each level powerup. The data comes from Data:HeroData.json and Data:SoulUnlockData.json.

The primary function creates a theme-aware, collapsible section containing a data table. This is invoked with: {{#invoke:LevelTables|write_power_increase_table|(stat)|title=Section Title}} where (stat) is replaced with one of the power level increase stats, and Section Title is the text that appears in the header of the collapsible box.

The generated table provides a summary of key milestones and includes an expandable '[Show All]' button for each hero to view the complete scaling data.

Currently, the valid stats are BulletDamage, MaxHealth, TechPower, LightMeleeDamage, and HeavyMeleeDamage

The stat keys and names are stored in the private variable level_scaling_key_to_name, so if a new stat gets added to level scaling in HeroData.json, you can add it to that variable.

Since the alternate fires of some heroes' guns are not stored in HeroData.json, there is a variable to store the damage and scaling called alt_fires.

Here is an example of the Bullet Damage Table: Lua error at line 72: attempt to perform arithmetic on local 'shots' (a nil value).

This module also has a function to write a list of heroes with multi-shot weapons, how many shots their weapon has, how much base damage a shot does, and how much damage a shot increases by per boon.

The list is automatically sorted alphabetically (ignoring "The" for names like The Doorman) and provides accurate descriptions for special cases, such as heroes whose multi-pellet attacks only deal damage once.

This list can be invoked with:
{{#invoke:LevelTables|write_multishot_stats}}

This is what the generated list looks like:

Lua error at line 129: attempt to compare number with nil.


local p = {};

local heroes_data = mw.loadJsonData("Data:HeroData.json")
local soul_unlocks_data = mw.loadJsonData("Data:SoulUnlockData.json")

local level_scaling_key_to_name = {
	BulletDamage = "Bullet Damage",
	MaxHealth = "Health",
	TechPower = "Spirit Power",
	LightMeleeDamage = "Light Melee Damage",
	HeavyMeleeDamage = "Heavy Melee Damage"
}

-- Not in HeroData.json although Shiv is just (bullet damage * 12)
local alt_fires = {
	Shiv = {base = 55.2, level = 2.64, suffix = "alt fire 12 pellets"},
	Viscous = {base = 63, level = 0.9, suffix = "alt fire AOE"},
	Yamato = {base = 63.36, level = 0.35, suffix = "alt fire AOE"}
}

-- Outputs a wikitable of the boon scaling of the inputted property by hero
-- @function   p.write_power_increase_table
-- @param      {string}
-- @return     {string}
p.write_power_increase_table = function(frame)
	local pi_souls_list = {}
	for _, v in ipairs(soul_unlocks_data) do
		if (v["PowerIncrease"]) then
			table.insert(pi_souls_list, v["RequiredSouls"])
		end
	end
	local level_count = 0
	for _ in ipairs(pi_souls_list) do level_count = level_count + 1 end
	
	local filtered_heroes = {}
	for _, hero in pairs(heroes_data) do
		if (not hero["IsDisabled"]) then
			table.insert(filtered_heroes, hero)
		end
	end
	
	local property = frame.args[1] --frame:getParent().args[1]
	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)
	
	local label_header = mw.html.create("tr")
	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)
	
	local souls_header = mw.html.create("tr")
	for _, v in ipairs(pi_souls_list) do
		local souls_formatted = string.format("%.1fK", (v + 400) / 1000)
		souls_formatted = frame:expandTemplate{ title = "Souls", args = {souls_formatted}}
		souls_header:tag("th"):wikitext(souls_formatted):done()
	end
	create_table:node(souls_header)
	
	for _, hero in ipairs(filtered_heroes) do
		local base = math.floor((hero[property] or 0) * 100) / 100
		local level = math.floor(hero["LevelScaling"][property] * 100) / 100
		local shots = hero["BulletsPerShot"]
		local burst = hero["BulletsPerBurst"]
		local bullet_mult = shots * burst
		local hero_icon = frame:expandTemplate{ title = "HeroIcon", args = {hero["Name"]}}
		local hero_suffix = ""
		if (property == "BulletDamage" and bullet_mult > 1) then
			base = base * bullet_mult
			level = level * bullet_mult
			if (shots > 1) then hero_suffix = hero_suffix .. " (" .. shots .. " pellets)" end
			if (burst > 1) then hero_suffix = hero_suffix .. " (" .. burst .. " bullets)" end
		end
		local hero_row = mw.html.create("tr")
		hero_row
			:tag("td"):wikitext(hero_icon .. hero_suffix):done()
			:tag("td"):wikitext(base):done()
			:tag("td"):wikitext("+" .. level):done()
		
		for i, v in ipairs(pi_souls_list) do
			local total = (tonumber(base) ~= nil and base or 0) + (tonumber(level) ~= nil and level or 0)  * i
			hero_row
				:tag("td"):wikitext(total):done()
		end
		
		create_table:node(hero_row)
		
		if (property == "BulletDamage" and alt_fires[hero["Name"]] ~= nil) then
			local alt_fire = alt_fires[hero["Name"]]
			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, v in ipairs(pi_souls_list) do
				local total = alt_base + alt_level * i
				alt_fire_row:tag("td"):wikitext(total):done()
			end
			
			create_table:node(alt_fire_row)
		end
	end
	
	return tostring(create_table)
end

-- Outputs a string list of weapon stats from heroes with either spreadshot or burst fire
-- @function   p.write_multishot_stats
-- @param      {}
-- @return     {string}
p.write_multishot_stats = function(frame)
	local function hero_sort(h1, h2) 
		return h1["Name"] < h2["Name"]
	end
	local spreadshot_heroes = {}
	local burst_heroes = {}
	for _, hero in pairs(heroes_data) do
		if (hero["BulletsPerShot"] > 1 and not hero["IsDisabled"]) then
			table.insert(spreadshot_heroes, hero)
		elseif (hero["BulletsPerBurst"] > 1 and not hero["IsDisabled"]) then
			table.insert(burst_heroes, hero)
		end
	end
	
	table.sort(spreadshot_heroes, hero_sort)
	table.sort(burst_heroes, hero_sort)
	
	local multishot_str = "Weapons with multiple projectiles (Spreadshot):\n"
	for _, hero in ipairs(spreadshot_heroes) do
		local base = math.floor(hero["BulletDamage"] * 100) / 100
		local level = math.floor(hero["LevelScaling"]["BulletDamage"] * 100) / 100
		local shots = hero["BulletsPerShot"]
		local hero_icon = frame:expandTemplate{ title = "HeroIcon", args = {hero["Name"]}}
		local stat_str = "* " .. hero_icon .. ": " .. shots .. " pellets, " 
			.. base .. "/pellet, +" .. level .. "/pellet\n"
		multishot_str = multishot_str .. stat_str
	end
	
	multishot_str = multishot_str .. "Weapons with burst fire (Burst Fire):\n"
	for _, hero in ipairs(burst_heroes) do
		local base = math.floor(hero["BulletDamage"] * 100) / 100
		local level = math.floor(hero["LevelScaling"]["BulletDamage"] * 100) / 100
		local shots = hero["BulletsPerBurst"]
		local hero_icon = frame:expandTemplate{ title = "HeroIcon", args = {hero["Name"]}}
		local stat_str = "* " .. hero_icon .. ": " .. shots .. " rounds, " 
			.. base .. "/bullet, +" .. level .. "/bullet\n"
		multishot_str = multishot_str .. stat_str
	end
	
	return multishot_str
end

return p