Module:BTD6 stats: Difference between revisions
Jump to navigation
Jump to search
new thrash rate, waiting for the stat page update |
Pymonkibot (talk | contribs) m Text replacement - "[[Bloons TD 6 v" to "[[Update history:Bloons TD 6/Version " |
||
| Line 771: | Line 771: | ||
local out = parse_tower_base(frame, data, "<h3>%s</h3>", true) | 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 | return string.format("<div class='hatnote'>This data was last updated for: [[Update history:Bloons TD 6/Version %s|version %s]]</div>", data["_last_updated"], data["_last_updated"]) .. out | ||
end | end | ||
| Line 806: | Line 806: | ||
} | } | ||
tinsert(ret, sformat("<div class='hatnote'>This data was last updated for: [[Bloons TD 6 | tinsert(ret, sformat("<div class='hatnote'>This data was last updated for: [[Update history:Bloons TD 6/Version %s|version %s]]</div>", data["_last_updated"], data["_last_updated"])) | ||
for i, v in ipairs(upgrade_crosspaths[frame.args[2]]) do | for i, v in ipairs(upgrade_crosspaths[frame.args[2]]) do | ||
| Line 877: | Line 877: | ||
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 string.format("<div class='hatnote'>This data was last updated for: [[Bloons TD 6 | return string.format("<div class='hatnote'>This data was last updated for: [[Update history:Bloons TD 6/Version %s|version %s]]</div>", data["_last_updated"], data["_last_updated"]) | ||
end | end | ||
return p | return p | ||
Latest revision as of 01:03, 24 April 2026
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"
}
local function parse_beast_table(data, tiers)
local ret = {"\n{|class='wikitable mw-collapsible mw-collapsed'\n|+style='white-space:nowrap'|Beast Power-dependent stats"}
-- local functions to improve performance
local tconcat = table.concat
local sformat = string.format
local msqrt = math.sqrt
local mfloor = math.floor
-- -- -- -- -- --
-- CREATE HEADING
-- -- -- -- -- --
local headingUpper = {}
local upperColspans = {}
local headingLower = {}
local headingEffects = function(effects)
for i, v in ipairs(effects["_order"]) do
local effect = effects[v]
local amount = 0
if v == "Knockback" and effect.lifespan then headingLower[#headingLower+1] = "Lifespan" amount = amount + 1 end
if amount > 0 then
headingUpper[#headingUpper+1] = v
upperColspans[#upperColspans+1] = amount
end
end
end
local headingProjectiles = 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 headingLower[#headingLower+1] = "Pierce" amount = amount + 1 end
if projectile["damage"]~=nil and projectile["damage"]>0 then headingLower[#headingLower+1] = "Damage" amount = amount + 1 end
if projectile["damageModifierForStunned"]~=nil then headingLower[#headingLower+1] = "Damage to stunned" amount = amount + 1 end
if amount > 0 then
headingUpper[#headingUpper+1] = v
upperColspans[#upperColspans+1] = amount
end
end
end
local headingAttacks = function(attacks)
for i, v in ipairs(attacks["_order"]) do
local attack = attacks[v]
local amount = 0
if attack.rate and attack.rate < 9999 and not attack.rateMin then headingLower[#headingLower+1] = "Cooldown" amount = amount + 1 end
if attack.initialDamage and attack.initialDamage > 0 then headingLower[#headingLower+1] = "Scratch damage" amount = amount + 1 end
if attack.grapplingDamage and attack.grapplingDamage > 0 then headingLower[#headingLower+1] = "Grappling damage" amount = amount + 1 end
if attack.thrashingProjectileRate then headingLower[#headingLower+1] = "Thrash cooldown" amount = amount + 1 end
if amount > 0 then
headingUpper[#headingUpper+1] = v
upperColspans[#upperColspans+1] = amount
end
-- projectiles
if attack.projectiles then headingProjectiles(attack.projectiles) end
end
end
local headingAbilities = function(abils)
for i, v in ipairs(abils["_order"]) do
local abil = abils[v]
local amount = 0
if abil.cooldown then headingLower[#headingLower+1] = "Cooldown" amount = amount + 1 end
if amount > 0 then
headingUpper[#headingUpper+1] = v
upperColspans[#upperColspans+1] = amount
end
-- projectiles
if abil.projectiles then headingProjectiles(abil.projectiles) end
end
end
local minPower = {
["_002"] = 3,
["_003"] = 8,
["_004"] = 16,
["_005"] = 36,
["_020"] = 3,
["_030"] = 8,
["_040"] = 16,
["_050"] = 36,
["_200"] = 3,
["_300"] = 8,
["_400"] = 16,
["_500"] = 36
}
local maxPower = {
["_200"] = 6,
["_300"] = 24,
["_400"] = 64,
["_500"] = 132,
["_020"] = 6,
["_030"] = 24,
["_040"] = 64,
["_050"] = 132,
["_002"] = 6,
["_003"] = 24,
["_004"] = 64,
["_005"] = 132
}
-- insert headings
if data.speedRangeGyrfalcon > 0 then
headingLower[#headingLower+1] = "Flight speed"
headingUpper[#headingUpper+1]= "Beast"
upperColspans[#upperColspans+1] = 1
end
if data.attacks then headingAttacks(data.attacks) end
if data.abilities then headingAbilities(data.abilities) end
ret[#ret+1] = "!rowspan=2|Power!!rowspan=2|Scale"
for i, v in ipairs(headingUpper) do
ret[#ret+1] = sformat("!colspan=%i|%s", upperColspans[i], v)
end
ret[#ret+1] = "|-\n!" .. table.concat(headingLower, "!!")
-- -- -- -- -- -- --
-- CALCULATE STATS
-- -- -- -- -- -- --
-- current scale
local scale
-- attack cooldown
local insertRate = function(amt)
ret[#ret+1] = sformat("| %.4gs", amt - (data.cooldownScaleRange*scale))
end
-- thrash cooldown
local insertThrashRate = function(amt, thrashRate)
ret[#ret+1] = sformat("| %.4gs", (1 - data.cooldownScaleRange*scale/amt) * thrashRate)
end
-- ability cooldown
local insertAbilityCooldown = function(amt, mainAttackRate)
ret[#ret+1] = sformat("|%.4gs", amt * (1 - (data.cooldownScaleRange*scale/(mainAttackRate - (data.cooldownScaleRange*scale)))))
end
-- damage
local insertDamage = function(amt)
ret[#ret+1] = sformat("|%i", amt + mfloor(data.damageRange*scale))
end
-- dino stun damage
local insertStunDamage = function(amt)
ret[#ret+1] = sformat("|%i", amt + mfloor(data.damageRange*scale/data.stunBonusDivideMicroraptor))
end
-- pierce
local insertPierce = function(amt)
ret[#ret+1] = sformat("|%i", amt + mfloor(data.pierceRange*scale))
end
-- thrash knockback duration
local insertKnockback = function(amt)
ret[#ret+1] = sformat("|%.4gs", amt + (data.thrashKnockbackLifetimeRange*scale))
end
-- bird flight speed
local insertGyrfalconSpeed = function()
ret[#ret+1] = sformat("|%.4g units/s", data.speed + (data.speedRangeGyrfalcon*scale))
end
local parseEffects = function(effects)
for i, v in ipairs(effects["_order"]) do
local effect = effects[v]
if v == "Knockback" and effect.duration then insertKnockback(effect.lifespan) end
end
end
local parseProjectiles = function(projectiles)
for i, v in ipairs(projectiles["_order"]) do
local projectile = projectiles[v]
if projectile.maxPierce < 1 and projectile.pierce < 99999 then insertPierce(projectile.pierce) end
if projectile.damage and projectile.damage > 0 then insertDamage(projectile.damage) end
if projectile.damageModifierForStunned then insertStunDamage(projectile.damageModifierForStunned) end
if projectile.effects then parseEffects(projectile.effects) end
end
end
local parseAttacks = function(attacks)
for i, v in ipairs(attacks["_order"]) do
local attack = attacks[v]
if attack.rate and attack.rate < 9999 and not attack.rateMin then insertRate(attack.rate) end
if attack.initialDamage and attack.initialDamage > 0 then insertDamage(attack.initialDamage) end
if attack.grapplingDamage and attack.grapplingDamage > 0 then insertDamage(attack.initialDamage) end
if attack.thrashingProjectileRate then insertThrashRate(attack.rate, attack.thrashingProjectileRate) end
-- projectiles
if attack.projectiles then parseProjectiles(attack.projectiles) end
end
end
local parseAbilities = function(abils)
for i, v in ipairs(abils["_order"]) do
local abil = abils[v]
local attackRate = data.attacks["Attack"].rate
-- rate
insertAbilityCooldown(abil.cooldown, attackRate)
if abil.projectiles ~= nil then parseProjectiles(abil.projectiles) end
end
end
for i = minPower[tiers], maxPower[tiers] do
scale = ((i - minPower[tiers]) / (maxPower[tiers] - minPower[tiers]))
ret[#ret+1] = sformat("|-\n!%i\n|%.4g%%", i, scale*100)
-- calculations
if data.speedRangeGyrfalcon > 0 then insertGyrfalconSpeed() end
if data.attacks then parseAttacks(data.attacks) end
if data.abilities then parseAbilities(data.abilities) end
end
ret[#ret+1] = "|}"
return table.concat(ret, "\n")
end
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
if data["zones"] ~= nil then heading_effects(data["zones"]) 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("|%.4gs", amt / (1 + (0.01 * msqrt((d-1)*50)))))
end
-- damage
local insert_damage = function(amt)
if d == 100 then tinsert(ret, sformat("|%.4g", amt * 2 + 10))
else tinsert(ret, sformat("|%.4g", 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("|%.4g", amt * 2 + 10))
else tinsert(ret, sformat("|%.4g", 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("| +%.4g", amt * 2 + 10))
else tinsert(ret, sformat("| +%.4g", 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(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
if data["zones"] ~= nil then parse_effects(data["zones"]) 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: [[Update history:Bloons TD 6/Version %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: [[Update history:Bloons TD 6/Version %s|version %s]]</div>", data["_last_updated"], data["_last_updated"]))
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)
-- inject beast handler stats table
if frame.args[1] == "Beast Handler" and not (frame.args[2] == "_000" or frame.args[2] == "_100" or frame.args[2] == "_010" or frame.args[2] == "_001") then
result = result .. parse_beast_table(base_data.subtowers.Beast, frame.args[2])
end
if i == 1 then tinsert(ret, result) end
for ii, vv in ipairs(v) do
if base_data[vv] ~= nil then
local crosspath = parse_tower(frame, base_data[vv], my_data, "<h4>%s</h4>", false)
if mw.text.trim(crosspath) ~= [[
]] then
tinsert(ret, sformat("<h3>%s-%s-%s</h3>", ssub(vv, 2, 2), ssub(vv, 3, 3), ssub(vv, 4, 4)))
tinsert(ret, crosspath)
end
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: [[Update history:Bloons TD 6/Version %s|version %s]]</div>", data["_last_updated"], data["_last_updated"])
end
return p