Module:BTD6 stats: Difference between revisions

From Blooncyclopedia, the independent Bloons knowledge base
Jump to navigation Jump to search
big refactor, might break a few things
Tag: Reverted
Undo revision 66471 by Polavux (talk) ok this broke a lot of things and im too tired to fix it right now
Tag: Undo
Line 1: Line 1:
--[[This code is copyrighted and licensed under the Creative Commons Attribution-NonCommercial-ShareAlike International license, version 4.0. You may reuse and adapt this code for other purposes if:
-- This code is copyrighted and licensed under the Creative Commons Attribution-NonCommercial-ShareAlike International license, version 4.0. You may reuse and adapt this code for other purposes if:
* You give appropriate credit
-- * You give appropriate credit
* You are not using this material for commercial purposes
-- * You are not using this material for commercial purposes
* You are releasing it under the same license
-- * You are releasing it under the same license
The subpages of this page are adapted from internal data in Bloons TD 6, which is copyrighted by Ninja Kiwi Limited. Its usage on Blooncyclopedia is believed to be fair use.
-- The subpages of this page are adapted from internal data in Bloons TD 6, which is copyrighted by Ninja Kiwi Limited. Its usage on Blooncyclopedia is believed to be fair use.
Further information: https://www.bloonswiki.com/Blooncyclopedia:Copyrights
-- Further information: https://www.bloonswiki.com/Blooncyclopedia:Copyrights
By editing this code, you agree to release your changes under the same terms.]]
-- By editing this code, you agree to release your changes under the same terms.


local p = {}
local p = {}
local prev_data = {}
local my_data = {}


-- adds the "this data was last updated for" header
function last_updated(version)
return string.format("<div class='hatnote'>This data was last updated for: [[Bloons TD 6 v%s|version %s]]</div>", version, version)
end
-- table of template names for each type of object
local template_names = {
local template_names = {
attacks ="BTD6 attack",
attacks ="BTD6 attack",
Line 27: Line 21:
}
}


function paragon_table(data)
function parse_paragon_table(data)
local ret = {"\n{|class='wikitable mw-collapsible mw-collapsed'\n|+style='white-space:nowrap'|Degree-dependent stats"}
local ret = {"\n{|class='wikitable mw-collapsible mw-collapsed'\n|+style='white-space:nowrap'|Degree-dependent stats"}
Line 36: Line 30:
local msqrt = math.sqrt
local msqrt = math.sqrt
local mfloor = math.floor
local mfloor = math.floor
local degree_requirements = {
0,      2000,  2324,  2666,  3027,  3408,  3808,  4228,  4669,  5131,
5615,  6121,  6650,  7203,  7779,  8379,  9004,  9654,  10330,  11032,
11761,  12518,  13302,  14114,  14955,  15825,  16725,  17655,  18616,  19609,
20633,  21689,  22778,  23900,  25056,  26246,  27471,  28732,  30028,  31360,
32729,  34135,  35579,  37061,  38582,  40143,  41743,  43383,  45064,  46786,
48550,  50356,  52205,  54098,  56034,  58014,  60039,  62109,  64225,  66387,
68596,  70853,  73157,  75509,  77910,  80360,  82860,  85410,  88011,  90664,
93368,  96124,  98933,  101795, 104711, 107681, 110706, 113787, 116923, 120115,
123364, 126670, 130034, 133456, 136937, 140478, 144078, 147738, 151459, 155241,
159085, 162991, 166960, 170993, 175089, 179249, 183474, 187764, 192120, 200000
}
-- -- -- -- -- --
-- -- -- -- -- --
Line 121: Line 127:
if data["abilities"] ~= nil then heading_abilities(data["abilities"]) end
if data["abilities"] ~= nil then heading_abilities(data["abilities"]) end
tinsert(ret, "!rowspan=2|Power\n!rowspan=2|Boss multiplier")
tinsert(ret, "!rowspan=2|Degree\n!rowspan=2|Power\n!rowspan=2|Boss multiplier")
for i, v in ipairs(heading_upper) do
for i, v in ipairs(heading_upper) do
Line 218: Line 224:
while d <= 100 do
while d <= 100 do
-- new row
-- new row
tinsert(ret, sformat("|-\n!%i", d))
tinsert(ret, sformat("|-\n!%i\n|%i", d, degree_requirements[d]))
-- boss damage mult
-- boss damage mult
Line 250: Line 256:
local ssub = string.sub
local ssub = string.sub
-- search through variables
-- search through tables
for k, v in pairs(data) do
for k, v in pairs(data) do
-- subtables (attacks, abilities, etc)
if type(v) == "table" and ssub(k, 1, 1) ~= "_" then
if type(v) == "table" and ssub(k, 1, 1) ~= "_" then
local subtables = {}
template[k] = {}
for i, subtable_name in ipairs(v["_order"]) do
-- iterate through attacks/abilities/subtowers
-- insert header for subtowers
for _, i in ipairs(v["_order"]) do
if k == "subtowers" then tinsert(subtables, sformat(header, subtable_name)) end
if k == "subtowers" then tinsert(template[k], sformat(header, i)) end
-- parse subtable of table
local stats = parse_stats_base(frame, subtable_name, v)
-- append stats subtable to output
-- parse sub-table of table
tinsert(subtables, frame:expandTemplate{title = template_names[k], args = stats})
local stats = parse_stats_base(frame, i, v[i], template_names[k])
if k == "subtowers" and paragon then tinsert(subtables, paragon_table(v[subtable_name])) end
if k == "subtowers" and paragon then stats = stats .. parse_paragon_table(v[i]) end
tinsert(template[k], stats)
end
end
template[k] = tconcat(subtables)
template[k] = tconcat(template[k])
-- other variables (rate, pierce, etc)
elseif type(v) ~= "table" then
elseif type(v) ~= "table" then
template[k] = data[k]
template[k] = data[k]
Line 276: Line 278:
end
end
-- append paragon table to output
if paragon then template["paragon stats"] = parse_paragon_table(data) end
if paragon then template["paragon stats"] = paragon_table(data) end
return frame:expandTemplate{title = "BTD6 tower", args = template}
return frame:expandTemplate{title = "BTD6 tower", args = template}
end
end


function parse_stats_base(frame, p_name, data)
function parse_stats_base(frame, name, data, template_name)
local template = {name = p_name}
local template = {name = name}
-- local functions to improve performance
-- local functions to improve performance
local tinsert = table.insert
local tconcat = table.concat
local tconcat = table.concat
-- search through variables
-- search through tables
for k, v in pairs(data[p_name]) do
for k, v in pairs(data) do
-- subtables (attacks, abilities, etc)
if type(v) == "table" then
if type(v) == "table" then
local subtables = {}
template[k] = {}
for i, subtable_name in ipairs(v["_order"]) do
-- iterate through attacks/abilities/subtowers
-- parse subtable of table
for _, i in ipairs(v["_order"]) do
subtables[i] = frame:expandTemplate{title = template_names[k], args = parse_stats_base(frame, subtable_name, v)}
-- parse sub-table of table
tinsert(template[k], parse_stats_base(frame, i, v[i], template_names[k]))
end
end
template[k] = tconcat(subtables)
template[k] = tconcat(template[k])
-- other variables (rate, pierce, etc)
else
else
template[k] = v
template[k] = v
Line 307: Line 308:
end
end
return template
return frame:expandTemplate{title = template_name, args = template}
end
end


function parse_tower_changes(frame, base, changes, header)
function parse_tower(frame, new_data, prev_data, header)
local template = {}
local template = {}
Line 319: Line 320:
local ssub = string.sub
local ssub = string.sub
-- search through variables
-- search through tables
for k, v in pairs(changes) do
for k, v in pairs(new_data) do
-- subtables (attacks, abilities, etc)
if type(v) == "table" and ssub(k, 1, 1) ~= "_" then
if type(v) == "table" and ssub(k, 1, 1) ~= "_" then
local subtables = {}
template[k] = {}
if prev_data[k] == nil then prev_data[k] = {} end
if prev_data[k] == nil then prev_data[k] = {} end
for i, subtable_name in ipairs(v["_order"]) do
-- iterate through attacks/abilities/subtowers
if prev_data[k][subtable_name] == nil then prev_data[k][subtable_name] = {} end
for _, i in ipairs(v["_order"]) do
-- insert header for subtowers
if k == "subtowers" then tinsert(template[k], sformat(header, i)) end
if k == "subtowers" then tinsert(subtables, sformat(header, subtable_name)) end
if prev_data[k][i] == nil then prev_data[k][i] = {} end
-- parse subtable of table
-- parse sub-table of table
-- if neither the base tower nor a previous stat change has this subtable, then treat it as new
local stats = parse_stats(frame, i, v[i], prev_data[k][i], template_names[k])
if (base[k] == nil or base[k][subtable_name] == nil) and next(prev_data[k][subtable_name]) == nil then
tinsert(template[k], stats)
local stats = parse_stats_base(frame, subtable_name, v)
prev_data[k][subtable_name] = stats
tinsert(subtables, frame:expandTemplate{title = template_names[k], args = stats})
else
local stats = parse_stats_changes(frame, subtable_name, base[k][subtable_name], v[subtable_name], prev_data[k][subtable_name])
tinsert(subtables, frame:expandTemplate{title = template_names[k], args = stats})
end
end
end
template[k] = tconcat(subtables)
template[k] = tconcat(template[k])
-- other variables (rate, pierce, etc)
elseif type(v) ~= "table" then
elseif type(v) ~= "table" then
-- stat comparison
-- if previous value exists, make it a comparison
-- if this stat changed before, use that, otherwise use the base tower's stat
if prev_data[k] ~= nil then
if prev_data[k] ~= nil then template[k] = prev_data[k]
template[k] = prev_data[k]
else template[k] = base[k] end
template[k .. " after"] = v
else
template[k] = v
end
template[k .. " after"] = v
-- update previous value
prev_data[k] = v
prev_data[k] = v
end
end
Line 360: Line 355:
end
end


function parse_stats_changes(frame, p_name, base, changes, prev)
function parse_stats(frame, name, new_data, prev_data, template_name)
local template = {name = p_name}
local template = {name = name}
-- local functions to improve performance
-- local functions to improve performance
Line 368: Line 363:
-- search through tables
-- search through tables
for k, v in pairs(changes) do
for k, v in pairs(new_data) do
-- subtables (attacks, abilities, etc)
if type(v) == "table" then
if type(v) == "table" then
local subtables = {}
template[k] = {}
if prev[k] == nil then prev[k] = {} end
if prev_data[k] == nil then prev_data[k] = {} end
for i, subtable_name in ipairs(v["_order"]) do
-- iterate through attacks/abilities/subtowers
if prev[k][subtable_name] == nil then prev[k][subtable_name] = {} end
for _, i in ipairs(v["_order"]) do
if prev_data[k][i] == nil then prev_data[k][i] = {} end
-- parse subtable of table
-- parse sub-table of table
-- if neither the base tower nor a previous stat change has this subtable, then treat it as new
tinsert(template[k], parse_stats(frame, i, v[i], prev_data[k][i], template_names[k]))
if (base == nil or base[k] == nil or base[k][subtable_name] == nil) and next(prev[k][subtable_name]) == nil then
local stats = parse_stats_base(frame, subtable_name, v)
prev[k][subtable_name] = stats
tinsert(subtables, frame:expandTemplate{title = template_names[k], args = stats})
else
local stats = parse_stats_changes(frame, subtable_name, base[k][subtable_name], v[subtable_name], prev[k][subtable_name])
tinsert(subtables, frame:expandTemplate{title = template_names[k], args = stats})
end
end
end
template[k] = tconcat(subtables)
template[k] = tconcat(template[k])
-- other variables (rate, pierce, etc)
else
else
-- stat comparison
-- if previous value exists, make it a comparison
-- if this stat changed before, use that, otherwise use the base tower's stat
if prev_data[k] ~= nil then
if prev[k] ~= nil then template[k] = prev[k]
template[k] = prev_data[k]
else template[k] = base[k] end
template[k .. " after"] = v
else
template[k] = v
end
template[k .. " after"] = v
-- update previous value
prev[k] = v
prev_data[k] = v
end
end
end
end
return template
return frame:expandTemplate{title = template_name, args = template}
end
end


Line 432: Line 421:
prev_data[k]["_keys"][i] = true
prev_data[k]["_keys"][i] = true
end
end
-- parse subtable of table
-- parse sub-table of table
parse_stats_full(frame, i, v[i], prev_data[k][i], template_names[k])
parse_stats_full(frame, i, v[i], prev_data[k][i], template_names[k])
end
end
Line 488: Line 477:
prev_data[k]["_keys"][i] = true
prev_data[k]["_keys"][i] = true
end
end
-- parse subtable of table
-- parse sub-table of table
parse_stats_full(frame, i, v[i], prev_data[k][i], template_names[k])
parse_stats_full(frame, i, v[i], prev_data[k][i], template_names[k])
end
end
Line 505: Line 494:
for _, i in ipairs(v["_order"]) do
for _, i in ipairs(v["_order"]) do
if prev_data[k][i] == nil then prev_data[k][i] = {} end
if prev_data[k][i] == nil then prev_data[k][i] = {} end
-- parse subtable of table
-- parse sub-table of table
tinsert(template[k], parse_stats_full(frame, i, nil, v[i], template_names[k]))
tinsert(template[k], parse_stats_full(frame, i, nil, v[i], template_names[k]))
end
end
Line 520: Line 509:


-- stats of a single tower
-- stats of a single tower
function p.tower_base(frame)
function p.base_stats(frame)
local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
local header_level = frame.args["header level"] ~= nil and frame.args["header level"] or "3"
local header_level = frame.args["header level"] ~= nil and frame.args["header level"] or "3"
Line 540: Line 529:
local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
return last_updated(data["_last_updated"]) .. parse_tower_base(frame, data, "<h3>%s</h3>", false)
local out = parse_tower_base(frame, data, "<h3>%s</h3>", true)
return string.format("<div class='hatnote'>This data was last updated for: [[Bloons TD 6 v%s|version %s]]</div>", data["_last_updated"], data["_last_updated"]) .. out
end
end


-- stat changes of a tower
-- stat changes of a tower
function p.tower_upgrade_changes(frame)
function p.tower_upgrade_stats(frame)
local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
Line 553: Line 544:
local sformat = string.format
local sformat = string.format
local ssub = string.sub
local ssub = string.sub
local base_data = data[frame.args[2]]
local upgrade_crosspaths = {
local upgrade_crosspaths = {
Line 573: Line 566:
}
}
-- data for this upgrade + crosspaths
tinsert(ret, sformat("<div class='hatnote'>This data was last updated for: [[Bloons TD 6 v%s|version %s]]</div>", data["_last_updated"], data["_last_updated"]))
local base_data = data[frame.args[2]]
tinsert(ret, last_updated(data["_last_updated"], data["_last_updated"]))
tinsert(ret, parse_tower_base(frame, base_data, "<h3>%s</h3>", false))
for i, v in ipairs(upgrade_crosspaths[frame.args[2]]) do
for i, v in ipairs(upgrade_crosspaths[frame.args[2]]) do
my_data = {}
local result = parse_tower(frame, base_data, my_data, "<h3>%s</h3>", false)
if i == 1 then tinsert(ret, result) end
for ii, vv in ipairs(v) do
for ii, vv in ipairs(v) do
if base_data[vv] ~= nil then
if base_data[vv] ~= nil then
tinsert(ret, sformat("<h3>%s-%s-%s</h3>", ssub(vv, 2, 2), ssub(vv, 3, 3), ssub(vv, 4, 4)))
tinsert(ret, sformat("<h3>%s-%s-%s</h3>", ssub(vv, 2, 2), ssub(vv, 3, 3), ssub(vv, 4, 4)))
tinsert(ret, parse_tower_changes(frame, base_data, base_data[vv], "<h4>%s</h4>"))
tinsert(ret, parse_tower(frame, base_data[vv], my_data, "<h4>%s</h4>", false))
end
end
end
end
Line 601: Line 591:
local tinsert = table.insert
local tinsert = table.insert
local sformat = string.format
local sformat = string.format
-- calculate base tower stats for later comparison
parse_tower(frame, data, my_data, "<h3>%s</h3>", false)
for i = 2, 20 do
for i = 2, 20 do
tinsert(ret, sformat("===Level %i===", i))
tinsert(ret, sformat("===Level %i===", i))
tinsert(ret, parse_tower_changes(frame, data, data["_" .. i], "<h4>%s</h4>"))
tinsert(ret, parse_tower(frame, data["_" .. i], my_data, "<h4>%s</h4>", false))
end
end
Line 610: Line 603:
end
end


-- full stats of a hero for each level
-- stat changes of a hero
function p.hero_level_stats(frame)
function p.hero_level_stats(frame)
local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
Line 631: Line 624:
end
end


-- get "last updated" header
function p.last_updated(frame)
function p.last_updated(frame)
local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
return last_updated(data["_last_modified"])
return string.format("<div class='hatnote'>This data was last updated for: [[Bloons TD 6 v%s|version %s]]</div>", data["_last_updated"], data["_last_updated"])
end
end


return p
return p

Revision as of 10:08, 6 March 2025

Documentation for this module may be created at Module:BTD6 stats/doc

-- This code is copyrighted and licensed under the Creative Commons Attribution-NonCommercial-ShareAlike International license, version 4.0. You may reuse and adapt this code for other purposes if:
-- * You give appropriate credit
-- * You are not using this material for commercial purposes
-- * You are releasing it under the same license
-- The subpages of this page are adapted from internal data in Bloons TD 6, which is copyrighted by Ninja Kiwi Limited. Its usage on Blooncyclopedia is believed to be fair use.
-- Further information: https://www.bloonswiki.com/Blooncyclopedia:Copyrights
-- By editing this code, you agree to release your changes under the same terms.

local p = {}
local my_data = {}

local template_names = {
	attacks		="BTD6 attack",
	projectiles	="BTD6 projectile",
	collectables="BTD6 collectable",
	abilities	="BTD6 ability",
	effects		="BTD6 effect",
	buffs		="BTD6 buff",
	zones		="BTD6 zone",
	subtowers	="BTD6 tower"
}

function parse_paragon_table(data)
	local ret = {"\n{|class='wikitable mw-collapsible mw-collapsed'\n|+style='white-space:nowrap'|Degree-dependent stats"}
	
	-- local functions to improve performance
	local tinsert = table.insert
	local tconcat = table.concat
	local sformat = string.format
	local msqrt = math.sqrt
	local mfloor = math.floor
	local degree_requirements = {
		0,      2000,   2324,   2666,   3027,   3408,   3808,   4228,   4669,   5131,
		5615,   6121,   6650,   7203,   7779,   8379,   9004,   9654,   10330,  11032,
		11761,  12518,  13302,  14114,  14955,  15825,  16725,  17655,  18616,  19609,
		20633,  21689,  22778,  23900,  25056,  26246,  27471,  28732,  30028,  31360,
		32729,  34135,  35579,  37061,  38582,  40143,  41743,  43383,  45064,  46786,
		48550,  50356,  52205,  54098,  56034,  58014,  60039,  62109,  64225,  66387,
		68596,  70853,  73157,  75509,  77910,  80360,  82860,  85410,  88011,  90664,
		93368,  96124,  98933,  101795, 104711, 107681, 110706, 113787, 116923, 120115,
		123364, 126670, 130034, 133456, 136937, 140478, 144078, 147738, 151459, 155241,
		159085, 162991, 166960, 170993, 175089, 179249, 183474, 187764, 192120, 200000
	}
	
	-- -- -- -- -- --
	-- CREATE HEADING
	-- -- -- -- -- --
	local heading_upper = {}
	local upper_colspans = {}
	local heading_lower = {}
	
	local heading_effects = function(effects)
		for i, v in ipairs(effects["_order"]) do
			local effect = effects[v]
			local amount = 0
	
			if effect["damage"]~=nil					then tinsert(heading_lower, "Damage") amount = amount + 1 end
			if effect["damageModifierForBoss"]~=nil		then tinsert(heading_lower, "Damage to bosses") amount = amount + 1 end
			if effect["damageModifierForMoabs"]~=nil	then tinsert(heading_lower, "Damage to MOAB-Class") amount = amount + 1 end
			if effect["damageModifierForCeramic"]~=nil	then tinsert(heading_lower, "Damage to Ceramic") amount = amount + 1 end
			if effect["damageModifierForCamo"]~=nil		then tinsert(heading_lower, "Damage to Camo") amount = amount + 1 end
			if effect["damageModifierForStunned"]~=nil	then tinsert(heading_lower, "Damage to stunned") amount = amount + 1 end
			if effect["damageModifierForStickied"]~=nil	then tinsert(heading_lower, "Damage to stickied") amount = amount + 1 end
			
			if amount > 0 then
				tinsert(heading_upper, v)
				tinsert(upper_colspans, amount)
			end
		end
	end
	
	local heading_projectiles = function(projectiles)
		for i, v in ipairs(projectiles["_order"]) do
			local projectile = projectiles[v]
			local amount = 0
					
			if projectile["maxPierce"]<1 and projectile["pierce"]<99999	then tinsert(heading_lower, "Pierce") amount = amount + 1 end
			if projectile["damage"]~=nil and projectile["damage"]>0		then tinsert(heading_lower, "Damage") amount = amount + 1 end
			if projectile["damageModifierForBoss"]~=nil					then tinsert(heading_lower, "Damage to bosses") amount = amount + 1 end
			if projectile["damageModifierForMoabs"]~=nil				then tinsert(heading_lower, "Damage to MOAB-Class") amount = amount + 1 end
			if projectile["damageModifierForCeramic"]~=nil				then tinsert(heading_lower, "Damage to Ceramic") amount = amount + 1 end
			if projectile["damageModifierForCamo"]~=nil					then tinsert(heading_lower, "Damage to Camo") amount = amount + 1 end
			if projectile["damageModifierForStunned"]~=nil				then tinsert(heading_lower, "Damage to stunned") amount = amount + 1 end
			if projectile["damageModifierForStickied"]~=nil				then tinsert(heading_lower, "Damage to stickied") amount = amount + 1 end
			
			if amount > 0 then
				tinsert(heading_upper, v)
				tinsert(upper_colspans, amount)
			end
			if projectile["effects"] ~= nil then heading_effects(projectile["effects"]) end
		end
	end
	
	local heading_attacks = function(attacks)
		for i, v in ipairs(attacks["_order"]) do
			local attack = attacks[v]
			
			-- rate
			if attack["rate"] ~= nil and attack["rate"] < 9999 and attack["rateMin"] == nil then
				tinsert(heading_lower, "Cooldown")
				tinsert(heading_upper, v)
				tinsert(upper_colspans, 1)
			end
			
			-- projectiles
			if attack["projectiles"] ~= nil then heading_projectiles(attack["projectiles"]) end
		end
	end
	
	local heading_abilities = function(abils)
		for i, v in ipairs(abils["_order"]) do
			local abil = abils[v]
			
			-- rate
			tinsert(heading_lower, "Cooldown")
			tinsert(heading_upper, v)
			tinsert(upper_colspans, 1)
			
			if abil["projectiles"] ~= nil then heading_projectiles(abil["projectiles"]) end
			if abil["attacks"] ~= nil then heading_attacks(abil["attacks"]) end
		end
	end
	
	-- insert headings
	if data["projectiles"] ~= nil then heading_attacks(data["projectiles"]) end
	if data["attacks"] ~= nil then heading_attacks(data["attacks"]) end
	if data["abilities"] ~= nil then heading_abilities(data["abilities"]) end
	
	tinsert(ret, "!rowspan=2|Degree\n!rowspan=2|Power\n!rowspan=2|Boss multiplier")
	
	for i, v in ipairs(heading_upper) do
		tinsert(ret, sformat("!colspan=%i|%s", upper_colspans[i], v))
	end
	tinsert(ret, "|-\n!" .. table.concat(heading_lower, "!!"))
	
	-- -- -- -- -- -- --
	-- CALCULATE STATS
	-- -- -- -- -- -- --
	
	-- current degree
	local d = 1
	
	-- attack cooldowns and ability cooldowns
	local insert_rate = function(amt)
		tinsert(ret, sformat("|%gs", amt / (1 + (0.01 * msqrt((d-1)*50)))))
	end
	
	-- damage
	local insert_damage = function(amt)
		if d == 100 then	tinsert(ret, sformat("|%g", amt * 2 + 10))
		else				tinsert(ret, sformat("|%g", amt * (1 + (d-1) * 0.01) + mfloor((d-1)/10))) end
	end
	
	-- pierce
	local insert_pierce = function(amt)
		if d == 100 then	tinsert(ret, sformat("|%g", amt * 2 + 10))
		else				tinsert(ret, sformat("|%g", mfloor(amt * (1 + (d-1) * 0.01)) + (d-1)/10)) end
	end
	
	-- damage modifiers
	local insert_damage_mod = function(amt)
		if d == 100 then	tinsert(ret, sformat("| +%g", amt * 2 + 10))
		else				tinsert(ret, sformat("| +%g", amt * (1 + (d-1) * 0.01))) end
	end
	
	local parse_effects = function(effects)
		for i, v in ipairs(effects["_order"]) do
			local effect = effects[v]
	
			if effect["damage"]~=nil					then insert_damage(effect["damage"]) end
			if effect["damageModifierForBoss"]~=nil		then insert_damage_mod(effect["damageModifierForBoss"]) end
			if effect["damageModifierForMoabs"]~=nil	then insert_damage_mod(effect["damageModifierForMoabs"]) end
			if effect["damageModifierForCeramic"]~=nil	then insert_damage_mod(effect["damageModifierForCeramic"]) end
			if effect["damageModifierForCamo"]~=nil		then insert_damage_mod(effect["damageModifierForCamo"]) end
			if effect["damageModifierForStunned"]~=nil	then insert_damage_mod(effect["damageModifierForStunned"]) end
			if effect["damageModifierForStickied"]~=nil	then insert_damage_mod(effect["damageModifierForStickied"]) end
		end
	end
	
	local parse_projectiles = function(projectiles)
		for i, v in ipairs(projectiles["_order"]) do
			local projectile = projectiles[v]
					
			if projectile["maxPierce"]<1 and projectile["pierce"]<99999	then insert_pierce(projectile["pierce"]) end
			if projectile["damage"]~=nil and projectile["damage"]>0		then insert_damage_mod(projectile["damage"]) end
			if projectile["damageModifierForBoss"]~=nil					then insert_damage_mod(projectile["damageModifierForBoss"]) end
			if projectile["damageModifierForMoabs"]~=nil				then insert_damage_mod(projectile["damageModifierForMoabs"]) end
			if projectile["damageModifierForCeramic"]~=nil				then insert_damage_mod(projectile["damageModifierForCeramic"]) end
			if projectile["damageModifierForCamo"]~=nil					then insert_damage_mod(projectile["damageModifierForCamo"]) end
			if projectile["damageModifierForStunned"]~=nil				then insert_damage_mod(projectile["damageModifierForStunned"]) end
			if projectile["damageModifierForStickied"]~=nil				then insert_damage_mod(projectile["damageModifierForStickied"]) end
			
			if projectile["effects"] ~= nil then parse_effects(projectile["effects"]) end
		end
	end
	
	local parse_attacks = function(attacks)
		for i, v in ipairs(attacks["_order"]) do
			local attack = attacks[v]
			
			-- rate
			if attack["rate"] ~= nil and attack["rate"] < 9999 and attack["rateMin"] == nil then
				insert_rate(attack["rate"])
			end
			
			-- projectiles
			if attack["projectiles"] ~= nil then parse_projectiles(attack["projectiles"]) end
		end
	end
	
	local parse_abilities = function(abils)
		for i, v in ipairs(abils["_order"]) do
			local abil = abils[v]
			
			-- rate
			insert_rate(abil["cooldown"])
			
			if abil["projectiles"] ~= nil then parse_projectiles(abil["projectiles"]) end
			if abil["attacks"] ~= nil then parse_attacks(abil["attacks"]) end
		end
	end
	
	-- degree loop
	while d <= 100 do
		-- new row
		tinsert(ret, sformat("|-\n!%i\n|%i", d, degree_requirements[d]))
		
		-- boss damage mult
		if d < 20 then tinsert(ret, "|×1.0")
		elseif d < 40 then tinsert(ret, "|×1.25")
		elseif d < 60 then tinsert(ret, "|×1.5")
		elseif d < 80 then tinsert(ret, "|×1.75")
		elseif d ~= 100 then tinsert(ret, "|×2.0")
		else tinsert(ret, "|×2.25") end
		
		-- calculations
		if data["projectiles"] ~= nil then parse_attacks(data["projectiles"]) end
		if data["attacks"] ~= nil then parse_attacks(data["attacks"]) end
		if data["abilities"] ~= nil then parse_abilities(data["abilities"]) end
		
		d = d + 1
	end
	
	tinsert(ret, "|}")
	
	return table.concat(ret, "\n")
end

function parse_tower_base(frame, data, header, paragon)
	local template = {}
	
	-- local functions to improve performance
	local tinsert = table.insert
	local tconcat = table.concat
	local sformat = string.format
	local ssub = string.sub
	
	-- search through tables
	for k, v in pairs(data) do
		if type(v) == "table" and ssub(k, 1, 1) ~= "_" then
			template[k] = {}
			
			-- iterate through attacks/abilities/subtowers
			for _, i in ipairs(v["_order"]) do
				if k == "subtowers" then tinsert(template[k], sformat(header, i)) end
				
				-- parse sub-table of table
				local stats = parse_stats_base(frame, i, v[i], template_names[k])
				if k == "subtowers" and paragon then stats = stats .. parse_paragon_table(v[i]) end
				tinsert(template[k], stats)
			end
			
			template[k] = tconcat(template[k])
		
		elseif type(v) ~= "table" then
			template[k] = data[k]
		end
	end
	
	if paragon then template["paragon stats"] = parse_paragon_table(data) end
	
	return frame:expandTemplate{title = "BTD6 tower", args = template}
end

function parse_stats_base(frame, name, data, template_name)
	local template = {name = name}
	
	-- local functions to improve performance
	local tinsert = table.insert
	local tconcat = table.concat
	
	-- search through tables
	for k, v in pairs(data) do
		if type(v) == "table" then
			template[k] = {}
			
			-- iterate through attacks/abilities/subtowers
			for _, i in ipairs(v["_order"]) do
				-- parse sub-table of table
				tinsert(template[k], parse_stats_base(frame, i, v[i], template_names[k]))
			end
			
			template[k] = tconcat(template[k])
		
		else
			template[k] = v
		end
	end
	
	return frame:expandTemplate{title = template_name, args = template}
end

function parse_tower(frame, new_data, prev_data, header)
	local template = {}
	
	-- local functions to improve performance
	local tinsert = table.insert
	local tconcat = table.concat
	local sformat = string.format
	local ssub = string.sub
	
	-- search through tables
	for k, v in pairs(new_data) do
		if type(v) == "table" and ssub(k, 1, 1) ~= "_" then
			template[k] = {}
			if prev_data[k] == nil then prev_data[k] = {} end
			
			-- iterate through attacks/abilities/subtowers
			for _, i in ipairs(v["_order"]) do
				if k == "subtowers" then tinsert(template[k], sformat(header, i)) end
				if prev_data[k][i] == nil then prev_data[k][i] = {} end
				
				-- parse sub-table of table
				local stats = parse_stats(frame, i, v[i], prev_data[k][i], template_names[k])
				tinsert(template[k], stats)
			end
			
			template[k] = tconcat(template[k])
		
		elseif type(v) ~= "table" then
			-- if previous value exists, make it a comparison
			if prev_data[k] ~= nil then
				template[k] = prev_data[k]
				template[k .. " after"] = v
			else
				template[k] = v
			end
			
			-- update previous value
			prev_data[k] = v
		end
	end
	
	return frame:expandTemplate{title = "BTD6 tower", args = template}
end

function parse_stats(frame, name, new_data, prev_data, template_name)
	local template = {name = name}
	
	-- local functions to improve performance
	local tinsert = table.insert
	local tconcat = table.concat
	
	-- search through tables
	for k, v in pairs(new_data) do
		if type(v) == "table" then
			template[k] = {}
			
			if prev_data[k] == nil then prev_data[k] = {} end
			
			-- iterate through attacks/abilities/subtowers
			for _, i in ipairs(v["_order"]) do
				if prev_data[k][i] == nil then prev_data[k][i] = {} end
				-- parse sub-table of table
				tinsert(template[k], parse_stats(frame, i, v[i], prev_data[k][i], template_names[k]))
			end
			
			template[k] = tconcat(template[k])
		
		else
			-- if previous value exists, make it a comparison
			if prev_data[k] ~= nil then
				template[k] = prev_data[k]
				template[k .. " after"] = v
			else
				template[k] = v
			end
			
			-- update previous value
			prev_data[k] = v
		end
	end
	
	return frame:expandTemplate{title = template_name, args = template}
end

function parse_tower_full(frame, new_data, prev_data, header)
	local template = {}
	
	-- local functions to improve performance
	local tinsert = table.insert
	local tconcat = table.concat
	local sformat = string.format
	local ssub = string.sub
	
	-- search through tables
	for k, v in pairs(new_data) do
		if type(v) == "table" and ssub(k, 1, 1) ~= "_" then
			if template[k] == nil then template[k] = {} end
			if prev_data[k] == nil then prev_data[k] = {} end
			if prev_data[k]["_order"] == nil then
				prev_data[k]["_order"] = {}
				prev_data[k]["_keys"] = {}
			end
			
			-- iterate through attacks/abilities/subtowers
			for _, i in ipairs(v["_order"]) do
				if prev_data[k][i] == nil then prev_data[k][i] = {} end
				if prev_data[k]["_keys"][i] == nil then
					tinsert(prev_data[k]["_order"], i)
					prev_data[k]["_keys"][i] = true
				end
				-- parse sub-table of table
				parse_stats_full(frame, i, v[i], prev_data[k][i], template_names[k])
			end
		
		elseif type(v) ~= "table" then
			-- update previous value
			prev_data[k] = v
		end
	end
	
	-- update prev_data first
	for k, v in pairs(prev_data) do
		if k == "attacks" or k == "abilities" or k == "subtowers" or k == "zones" then
			template[k] = {}
			
			-- iterate through attacks/abilities/subtowers
			for _, i in ipairs(v["_order"]) do
				if k == "subtowers" then tinsert(template[k], sformat(header, i)) end
				tinsert(template[k], parse_stats_full(frame, i, nil, v[i], template_names[k]))
			end
			
			template[k] = tconcat(template[k])
		
		elseif type(v) ~= "table" then
			-- update previous value
			template[k] = v
		end
	end
	
	return frame:expandTemplate{title = "BTD6 tower", args = template}
end

function parse_stats_full(frame, name, new_data, prev_data, template_name)
	local template = {name = name}
	
	-- local functions to improve performance
	local tinsert = table.insert
	local tconcat = table.concat
	
	-- update prev_data first
	if new_data ~= nil then
		for k, v in pairs(new_data) do
			if type(v) == "table" then
				if prev_data[k] == nil then prev_data[k] = {} end
			if prev_data[k]["_order"] == nil then
				prev_data[k]["_order"] = {}
				prev_data[k]["_keys"] = {}
				end
				
				-- iterate through attacks/abilities/subtowers
				for _, i in ipairs(v["_order"]) do
					if prev_data[k][i] == nil then prev_data[k][i] = {} end
					if prev_data[k]["_keys"][i] == nil then
						tinsert(prev_data[k]["_order"], i)
						prev_data[k]["_keys"][i] = true
					end	
					-- parse sub-table of table
					parse_stats_full(frame, i, v[i], prev_data[k][i], template_names[k])
				end
			
			else
				prev_data[k] = v
			end
		end
	end
	
	for k, v in pairs(prev_data) do
		if type(v) == "table" then
			template[k] = {}
			
			-- iterate through attacks/abilities/subtowers
			for _, i in ipairs(v["_order"]) do
				if prev_data[k][i] == nil then prev_data[k][i] = {} end
				-- parse sub-table of table
				tinsert(template[k], parse_stats_full(frame, i, nil, v[i], template_names[k]))
			end
			
			template[k] = tconcat(template[k])
		
		else
			template[k] = v
		end
	end
	
	return frame:expandTemplate{title = template_name, args = template}
end

-- stats of a single tower
function p.base_stats(frame)
	local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
	local header_level = frame.args["header level"] ~= nil and frame.args["header level"] or "3"
	local header = "<h" .. header_level .. ">%s</h" .. header_level .. ">"
	
	if frame.args[2] == nil then
		if data["_000"] ~= nil then
			return parse_tower_base(frame, data["_000"], header, false)
		else
			return parse_tower_base(frame, data, header, false)
		end
	else
		return parse_tower_base(frame, data["_" .. frame.args[2]], header, false)
	end
end

-- stats of a paragon
function p.paragon(frame)
	local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
	
	local out = parse_tower_base(frame, data, "<h3>%s</h3>", true)
	
	return string.format("<div class='hatnote'>This data was last updated for: [[Bloons TD 6 v%s|version %s]]</div>", data["_last_updated"], data["_last_updated"]) .. out
end

-- stat changes of a tower
function p.tower_upgrade_stats(frame)
	local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
	
	local ret = {}
	
	-- local functions to improve performance
	local tinsert = table.insert
	local sformat = string.format
	local ssub = string.sub
	
	local base_data = data[frame.args[2]]
	
	local upgrade_crosspaths = {
		["_000"] = {{}},
		["_100"] = {{"_110"}, {"_101"}},
		["_200"] = {{"_210", "_220"}, {"_201", "_202"}},
		["_010"] = {{"_110"}, {"_011"}},
		["_020"] = {{"_120", "_220"}, {"_021", "_022"}},
		["_001"] = {{"_101"}, {"_011"}},
		["_002"] = {{"_102", "_202"}, {"_012", "_022"}},
		["_300"] = {{"_310", "_320"}, {"_301", "_302"}},
		["_400"] = {{"_410", "_420"}, {"_401", "_402"}},
		["_500"] = {{"_510", "_520"}, {"_501", "_502"}},
		["_030"] = {{"_130", "_230"}, {"_031", "_032"}},
		["_040"] = {{"_140", "_240"}, {"_041", "_042"}},
		["_050"] = {{"_150", "_250"}, {"_051", "_052"}},
		["_003"] = {{"_103", "_203"}, {"_013", "_023"}},
		["_004"] = {{"_104", "_204"}, {"_014", "_024"}},
		["_005"] = {{"_105", "_205"}, {"_015", "_025"}}
	}
	
	tinsert(ret, sformat("<div class='hatnote'>This data was last updated for: [[Bloons TD 6 v%s|version %s]]</div>", data["_last_updated"], data["_last_updated"]))
	
	for i, v in ipairs(upgrade_crosspaths[frame.args[2]]) do
		local result = parse_tower(frame, base_data, my_data, "<h3>%s</h3>", false)
		if i == 1 then tinsert(ret, result) end
		for ii, vv in ipairs(v) do
			if base_data[vv] ~= nil then
				tinsert(ret, sformat("<h3>%s-%s-%s</h3>", ssub(vv, 2, 2), ssub(vv, 3, 3), ssub(vv, 4, 4)))
				tinsert(ret, parse_tower(frame, base_data[vv], my_data, "<h4>%s</h4>", false))
			end
		end
	end
	
	return table.concat(ret, "\n")
end

-- stat changes of a hero
function p.hero_level_changes(frame)
	local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
	
	local ret = {}
	
	-- local functions to improve performance
	local tinsert = table.insert
	local sformat = string.format
	
	-- calculate base tower stats for later comparison
	parse_tower(frame, data, my_data, "<h3>%s</h3>", false)
	
	for i = 2, 20 do
		tinsert(ret, sformat("===Level %i===", i))
		tinsert(ret, parse_tower(frame, data["_" .. i], my_data, "<h4>%s</h4>", false))
	end
	
	return table.concat(ret, "\n")
end

-- stat changes of a hero
function p.hero_level_stats(frame)
	local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
	
	local ret = {}
	
	-- local functions to improve performance
	local tinsert = table.insert
	local sformat = string.format
	
	-- calculate base tower stats for later comparison
	parse_tower(frame, data, my_data, "<h3>%s</h3>", false)
	
	for i = 2, 20 do
		tinsert(ret, sformat("===Level %i===", i))
		tinsert(ret, parse_tower_full(frame, data["_" .. i], my_data, "<h4>%s</h4>", false))
	end
	
	return table.concat(ret, "\n")
end

function p.last_updated(frame)
	local data = mw.loadJsonData(string.format("Module:BTD6 stats/%s/new", frame.args[1]))
	return string.format("<div class='hatnote'>This data was last updated for: [[Bloons TD 6 v%s|version %s]]</div>", data["_last_updated"], data["_last_updated"])
end

return p