Module:Buildup: Difference between revisionsGive feedback
Module for item buildup stats |
No edit summary |
||
| Line 55: | Line 55: | ||
-- Core calculation logic | -- Core calculation logic | ||
local function calculate_bps(hero, bb) | local function calculate_bps(hero, bb) | ||
if not hero or not hero.Weapon then return 0 end | |||
local rps = tonumber(hero.Weapon.RoundsPerSecond or 0) | |||
local spin_rps = tonumber(hero.Weapon.RoundsPerSecondAtMaxSpin or 0) | |||
-- 1. Handle Spin-up (McGinnis/Bebop) | |||
if spin_rps > 0 then | |||
rps = spin_rps | |||
end | |||
-- 2. Handle "Fake Burst" Logic (Paige/Doorman) | |||
-- If they have a burst interval, but only 1 bullet per burst, | |||
-- the game ignores that interval for Buildup calculation. | |||
local burst_count = tonumber(hero.Weapon.BulletsPerBurst or 1) | |||
local burst_interval = tonumber(hero.Weapon.BurstInterShotInterval or 0) | |||
if burst_count == 1 and burst_interval > 0 and rps > 0 then | |||
-- Convert RPS back to time | |||
local total_time = 1 / rps | |||
-- Remove the interval | |||
local effective_time = total_time - burst_interval | |||
-- Prevent division by zero if something is weird | |||
if effective_time > 0.001 then | |||
rps = 1 / effective_time | |||
end | |||
end | |||
if rps == 0 then return 0 end | |||
-- Formula: 100 / (BB * (RPS + 1)) | |||
return 100 / (bb * (rps + 1)) | |||
end | end | ||
| Line 89: | Line 108: | ||
--Generates a sortable wikitable for all heroes | --Generates a sortable wikitable for all heroes | ||
p.write_buildup_table = function(frame) | p.write_buildup_table = function(frame) | ||
local item_name = frame.args[1] | |||
local bb = get_item_bb(item_name) | |||
if not bb then return "Error: Item '" .. (item_name or "nil") .. "' not found." end | |||
local hero_list = {} | |||
for key, hero in pairs(heroes_data) do | |||
local is_dev = hero["InDevelopment"] == true | |||
local is_disabled = hero["IsDisabled"] == true | |||
if not is_dev and not is_disabled and hero["Weapon"] then | |||
local val = calculate_bps(hero, bb) | |||
if val > 0 then | |||
local shots = math.ceil(100 / val) | |||
table.insert(hero_list, { | |||
name = hero["Name"], | |||
bps = val, | |||
shots = shots | |||
}) | |||
end | |||
end | |||
end | |||
-- Sorting Logic | |||
table.sort(hero_list, function(a, b) | |||
-- Helper to strip "The " from names just for sorting | |||
local function clean_sort_name(name) | |||
if string.sub(name, 1, 4) == "The " then | |||
return string.sub(name, 5) | |||
end | |||
return name | |||
end | |||
return clean_sort_name(a.name) < clean_sort_name(b.name) | |||
end) | |||
-- Build Table | |||
local html = mw.html.create('table') | |||
html:addClass('wikitable sortable mw-collapsible') | |||
if item_name then | |||
html:tag('caption'):wikitext('Buildup Per Shot: ' .. item_name) | |||
end | |||
-- Headers | |||
local headerRow = html:tag('tr') | |||
headerRow:tag('th'):wikitext('Hero') | |||
headerRow:tag('th'):wikitext('% per shot') | |||
headerRow:tag('th'):wikitext('Shots to Proc') | |||
-- Rows | |||
for _, h in ipairs(hero_list) do | |||
local row = html:tag('tr') | |||
local icon_template = frame:expandTemplate{ title = 'HeroIcon', args = { h.name } } | |||
-- Use data-sort-value so the table sorts correctly even with the icon | |||
row:tag('td') | |||
:attr('data-sort-value', h.name) -- Uses the name including "The" for consistent default sort | |||
:wikitext(icon_template) | |||
row:tag('td'):wikitext(string.format("%.1f", h.bps)) | |||
row:tag('td'):wikitext(tostring(h.shots)) | |||
end | |||
return tostring(html) | |||
end | end | ||
Revision as of 20:50, 20 January 2026
Overview
The Buildup module provides functions for calculating the Status Effect Buildup per shot for heroes.
This is used for items that apply a status effect based on filling a meter (e.g., Toxic Bullets, Silencer, Inhibitor). The calculation normalizes the buildup based on the hero's Fire Rate (RPS) to ensure the time-to-activate is roughly consistent across different weapon types.
It references Data:HeroData.json for weapon stats (RPS, Spin-up) and Data:ItemData.json for the item's base buildup value.
Functions
main
The primary entry point used by Template:Buildup. It automatically detects whether to generate a full table or a single value based on the number of parameters provided.
get_bps
Calculates the specific buildup percentage per shot for a single hero.
Parameters
- hero_name - Name of the hero in English (e.g., "Abrams") or the hero key.
- item_name - Name of the item (e.g., "Toxic Bullets").
Example
{{#invoke:Buildup|get_bps|Haze|Toxic Bullets}}
Returns:
7.4
write_buildup_table
Generates a sortable wikitable listing the buildup percentage and shots-to-proc for every valid hero.
Parameters
- item_name - Name of the item (e.g., "Silencer").
Example
{{#invoke:Buildup|write_buildup_table|Silencer}}
Internal Logic
The module uses the following formula derived from the game's mechanics:
The Effective RPS is determined by analyzing specific hero mechanics:
- Standard Heroes: Uses the standard
RoundsPerSecondfrom the data files. - Spin-Up Heroes: For heroes like McGinnis, the calculation uses their Max Spin RPS.
- Burst/Hybrid Heroes: For heroes like Paige and The Doorman, the game displays a Fire Rate that includes an "Intra-Burst" delay, but the Buildup mechanic ignores this delay. The module detects this configuration (Burst Count = 1, but Interval > 0) and mathematically removes the interval to determine the true Effective RPS.
- Shots to Proc: The table uses
math.ceilto always round up, ensuring accuracy for cases like Kelvin where the math results in 19.99% (requiring 6 shots, not 5).
local p = {}
local heroes_data = mw.loadJsonData("Data:HeroData.json")
local items_data = mw.loadJsonData("Data:ItemData.json")
-- Helper to find Item Buildup value by name
-- Handles both flat numbers and lookup in the ItemData file
local function get_item_bb(target_name)
if target_name == nil then return nil end
-- Clean input
local clean_name = mw.text.trim(target_name)
-- If user input a number directly
if tonumber(clean_name) then return tonumber(clean_name) end
-- Iterate Item Data
for key, item in pairs(items_data) do
if item["Name"] == clean_name then
-- Valve data structure (nested)
if item["m_mapAbilityProperties"] and item["m_mapAbilityProperties"]["BuildUpPerShot"] then
return tonumber(item["m_mapAbilityProperties"]["BuildUpPerShot"]["m_strValue"])
end
-- Flat data structure (fallback)
if item["BuildUpPerShot"] then
return tonumber(item["BuildUpPerShot"])
end
end
end
return nil
end
-- Helper to find Hero Data by name or key
local function get_hero_data(hero_name)
if hero_name == nil then return nil end
local clean_name = mw.text.trim(hero_name)
-- Try direct Key lookup
if heroes_data[clean_name] then return heroes_data[clean_name] end
-- Try adding "hero_" prefix
local key_lower = "hero_" .. string.lower(clean_name)
if heroes_data[key_lower] then return heroes_data[key_lower] end
-- Search by Name field
for k, v in pairs(heroes_data) do
if v["Name"] == clean_name then return v end
end
return nil
end
-- Core calculation logic
local function calculate_bps(hero, bb)
if not hero or not hero.Weapon then return 0 end
local rps = tonumber(hero.Weapon.RoundsPerSecond or 0)
local spin_rps = tonumber(hero.Weapon.RoundsPerSecondAtMaxSpin or 0)
-- 1. Handle Spin-up (McGinnis/Bebop)
if spin_rps > 0 then
rps = spin_rps
end
-- 2. Handle "Fake Burst" Logic (Paige/Doorman)
-- If they have a burst interval, but only 1 bullet per burst,
-- the game ignores that interval for Buildup calculation.
local burst_count = tonumber(hero.Weapon.BulletsPerBurst or 1)
local burst_interval = tonumber(hero.Weapon.BurstInterShotInterval or 0)
if burst_count == 1 and burst_interval > 0 and rps > 0 then
-- Convert RPS back to time
local total_time = 1 / rps
-- Remove the interval
local effective_time = total_time - burst_interval
-- Prevent division by zero if something is weird
if effective_time > 0.001 then
rps = 1 / effective_time
end
end
if rps == 0 then return 0 end
-- Formula: 100 / (BB * (RPS + 1))
return 100 / (bb * (rps + 1))
end
--{{#invoke:Buildup|get_bps|HERO_NAME|ITEM_NAME}}
--Returns a single float value (rounded to 1 decimal)
p.get_bps = function(frame)
local hero_name = frame.args[1] or frame.args['hero']
local item_name = frame.args[2] or frame.args['item']
local hero = get_hero_data(hero_name)
if not hero then return "Hero Not Found" end
local bb = get_item_bb(item_name)
if not bb then return "Item Not Found" end
local result = calculate_bps(hero, bb)
return string.format("%.1f", result)
end
--{{#invoke:Buildup|write_buildup_table|ITEM_NAME}}
--Generates a sortable wikitable for all heroes
p.write_buildup_table = function(frame)
local item_name = frame.args[1]
local bb = get_item_bb(item_name)
if not bb then return "Error: Item '" .. (item_name or "nil") .. "' not found." end
local hero_list = {}
for key, hero in pairs(heroes_data) do
local is_dev = hero["InDevelopment"] == true
local is_disabled = hero["IsDisabled"] == true
if not is_dev and not is_disabled and hero["Weapon"] then
local val = calculate_bps(hero, bb)
if val > 0 then
local shots = math.ceil(100 / val)
table.insert(hero_list, {
name = hero["Name"],
bps = val,
shots = shots
})
end
end
end
-- Sorting Logic
table.sort(hero_list, function(a, b)
-- Helper to strip "The " from names just for sorting
local function clean_sort_name(name)
if string.sub(name, 1, 4) == "The " then
return string.sub(name, 5)
end
return name
end
return clean_sort_name(a.name) < clean_sort_name(b.name)
end)
-- Build Table
local html = mw.html.create('table')
html:addClass('wikitable sortable mw-collapsible')
if item_name then
html:tag('caption'):wikitext('Buildup Per Shot: ' .. item_name)
end
-- Headers
local headerRow = html:tag('tr')
headerRow:tag('th'):wikitext('Hero')
headerRow:tag('th'):wikitext('% per shot')
headerRow:tag('th'):wikitext('Shots to Proc')
-- Rows
for _, h in ipairs(hero_list) do
local row = html:tag('tr')
local icon_template = frame:expandTemplate{ title = 'HeroIcon', args = { h.name } }
-- Use data-sort-value so the table sorts correctly even with the icon
row:tag('td')
:attr('data-sort-value', h.name) -- Uses the name including "The" for consistent default sort
:wikitext(icon_template)
row:tag('td'):wikitext(string.format("%.1f", h.bps))
row:tag('td'):wikitext(tostring(h.shots))
end
return tostring(html)
end
-- Main entry point for the template
-- Decides whether to return a single value or a full table based on arguments
p.main = function(frame)
local args = frame:getParent().args
-- Handle direct #invoke calls vs Template calls
if not args[1] and not args['item'] then args = frame.args end
local arg1 = args[1] or args['hero']
local arg2 = args[2] or args['item']
-- If both Hero and Item are present -> Return Single Number
if arg1 and arg2 then
-- Manually setting args for the helper function to read
frame.args = {arg1, arg2}
return p.get_bps(frame)
-- If only one arg (Item) is present -> Return Table
elseif arg1 then
frame.args = {arg1}
return p.write_buildup_table(frame)
else
return "Error: Provide [Item Name] for a table, or [Hero Name] [Item Name] for a value."
end
end
return p