Module:BTD6 stats: Difference between revisions
mNo edit summary |
Pymonkibot (talk | contribs) m Text replacement - "[[Bloons TD 6 v" to "[[Update history:Bloons TD 6/Version " |
||
| (37 intermediate revisions by 2 users not shown) | |||
| Line 20: | Line 20: | ||
subtowers ="BTD6 tower" | 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) | 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"} | ||
-- local functions to improve performance | -- local functions to improve performance | ||
| Line 77: | Line 313: | ||
local amount = 0 | local amount = 0 | ||
if projectile["maxPierce"]<1 | if projectile["maxPierce"]<1 and projectile["pierce"]<99999 then tinsert(heading_lower, "Pierce") amount = amount + 1 end | ||
if projectile["damage"]~=nil | if projectile["damage"]~=nil and projectile["damage"]>0 then tinsert(heading_lower, "Damage") amount = amount + 1 end | ||
if projectile["damageModifierForBoss"]~=nil | 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["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["damageModifierForCeramic"]~=nil then tinsert(heading_lower, "Damage to Ceramic") amount = amount + 1 end | ||
if projectile["damageModifierForCamo"]~=nil | 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["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 projectile["damageModifierForStickied"]~=nil then tinsert(heading_lower, "Damage to stickied") amount = amount + 1 end | ||
if amount > 0 then | if amount > 0 then | ||
| Line 99: | Line 335: | ||
-- rate | -- rate | ||
if attack["rate"] ~= nil then | if attack["rate"] ~= nil and attack["rate"] < 9999 and attack["rateMin"] == nil then | ||
tinsert(heading_lower, "Cooldown") | tinsert(heading_lower, "Cooldown") | ||
tinsert(heading_upper, v) | tinsert(heading_upper, v) | ||
| Line 128: | Line 364: | ||
if data["attacks"] ~= nil then heading_attacks(data["attacks"]) end | if data["attacks"] ~= nil then heading_attacks(data["attacks"]) end | ||
if data["abilities"] ~= nil then heading_abilities(data["abilities"]) 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") | tinsert(ret, "!rowspan=2|Degree\n!rowspan=2|Power\n!rowspan=2|Boss multiplier") | ||
| Line 145: | Line 382: | ||
-- attack cooldowns and ability cooldowns | -- attack cooldowns and ability cooldowns | ||
local insert_rate = function(amt) | local insert_rate = function(amt) | ||
tinsert(ret, sformat("|% | 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 | end | ||
-- | -- pierce | ||
local | local insert_pierce = function(amt) | ||
if d == 100 then tinsert(ret, sformat("|% | if d == 100 then tinsert(ret, sformat("|%.4g", amt * 2 + 10)) | ||
else tinsert(ret, sformat("|% | else tinsert(ret, sformat("|%.4g", mfloor(amt * (1 + (d-1) * 0.01)) + (d-1)/10)) end | ||
end | end | ||
-- damage modifiers | -- damage modifiers | ||
local insert_damage_mod = function(amt) | local insert_damage_mod = function(amt) | ||
if d == 100 then tinsert(ret, sformat("| +% | if d == 100 then tinsert(ret, sformat("| +%.4g", amt * 2 + 10)) | ||
else tinsert(ret, sformat("| +% | else tinsert(ret, sformat("| +%.4g", amt * (1 + (d-1) * 0.01))) end | ||
end | end | ||
| Line 164: | Line 407: | ||
local effect = effects[v] | local effect = effects[v] | ||
if effect["damage"]~=nil then | if effect["damage"]~=nil then insert_damage(effect["damage"]) end | ||
if effect["damageModifierForBoss"]~=nil then insert_damage_mod(effect["damageModifierForBoss"]) 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["damageModifierForMoabs"]~=nil then insert_damage_mod(effect["damageModifierForMoabs"]) end | ||
| Line 178: | Line 421: | ||
local projectile = projectiles[v] | local projectile = projectiles[v] | ||
if projectile["maxPierce"]<1 | if projectile["maxPierce"]<1 and projectile["pierce"]<99999 then insert_pierce(projectile["pierce"]) end | ||
if projectile["damage"]~=nil | if projectile["damage"]~=nil and projectile["damage"]>0 then insert_damage(projectile["damage"]) end | ||
if projectile["damageModifierForBoss"]~=nil | if projectile["damageModifierForBoss"]~=nil then insert_damage_mod(projectile["damageModifierForBoss"]) end | ||
if projectile["damageModifierForMoabs"]~=nil then insert_damage_mod(projectile["damageModifierForMoabs"]) 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["damageModifierForCeramic"]~=nil then insert_damage_mod(projectile["damageModifierForCeramic"]) end | ||
if projectile["damageModifierForCamo"]~=nil | if projectile["damageModifierForCamo"]~=nil then insert_damage_mod(projectile["damageModifierForCamo"]) end | ||
if projectile["damageModifierForStunned"]~=nil then insert_damage_mod(projectile["damageModifierForStunned"]) 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["damageModifierForStickied"]~=nil then insert_damage_mod(projectile["damageModifierForStickied"]) end | ||
if projectile["effects"] ~= nil then parse_effects(projectile["effects"]) end | if projectile["effects"] ~= nil then parse_effects(projectile["effects"]) end | ||
| Line 196: | Line 439: | ||
-- rate | -- rate | ||
if attack["rate"] ~= nil then insert_rate(attack["rate"]) end | if attack["rate"] ~= nil and attack["rate"] < 9999 and attack["rateMin"] == nil then | ||
insert_rate(attack["rate"]) | |||
end | |||
-- projectiles | -- projectiles | ||
| Line 232: | Line 477: | ||
if data["attacks"] ~= nil then parse_attacks(data["attacks"]) end | if data["attacks"] ~= nil then parse_attacks(data["attacks"]) end | ||
if data["abilities"] ~= nil then parse_abilities(data["abilities"]) end | if data["abilities"] ~= nil then parse_abilities(data["abilities"]) end | ||
if data["zones"] ~= nil then parse_effects(data["zones"]) end | |||
d = d + 1 | d = d + 1 | ||
| Line 241: | Line 487: | ||
end | end | ||
function parse_tower(frame, new_data, prev_data, header | 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 template = {} | ||
| Line 263: | Line 573: | ||
-- parse sub-table of table | -- parse sub-table of table | ||
local stats = parse_stats(frame, i, v[i], prev_data[k][i], template_names[k]) | local stats = parse_stats(frame, i, v[i], prev_data[k][i], template_names[k]) | ||
tinsert(template[k], stats) | tinsert(template[k], stats) | ||
end | end | ||
| Line 282: | Line 591: | ||
end | end | ||
end | end | ||
return frame:expandTemplate{title = "BTD6 tower", args = template} | return frame:expandTemplate{title = "BTD6 tower", args = template} | ||
| Line 449: | Line 756: | ||
if frame.args[2] == nil then | if frame.args[2] == nil then | ||
if data["_000"] ~= nil then | if data["_000"] ~= nil then | ||
return | return parse_tower_base(frame, data["_000"], header, false) | ||
else | else | ||
return | return parse_tower_base(frame, data, header, false) | ||
end | end | ||
else | else | ||
return | return parse_tower_base(frame, data["_" .. frame.args[2]], header, false) | ||
end | end | ||
end | end | ||
| Line 462: | Line 769: | ||
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 out = | 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 499: | 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 | ||
my_data = {} | |||
local result = parse_tower(frame, base_data, my_data, "<h3>%s</h3>", false) | 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 | 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))) | 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 | end | ||
| Line 559: | 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 | ||