Editing Module:Buildup

Warning: You are not logged in. Once you make an edit, a temporary account will be created for you. Learn more. Log in or create an account to continue receiving notifications after this account expires, and to access other features.
The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
Latest revision Your text
Line 7: Line 7:
-- Handles both flat numbers and lookup in the ItemData file
-- Handles both flat numbers and lookup in the ItemData file
local function get_item_bb(target_name)
local function get_item_bb(target_name)
    if target_name == nil then return nil end
if target_name == nil then return nil end
   
    -- Clean input
-- Clean input
    local clean_name = mw.text.trim(target_name)
local clean_name = mw.text.trim(target_name)
   
    -- If user input a number directly
-- If user input a number directly
    if tonumber(clean_name) then return tonumber(clean_name) end
if tonumber(clean_name) then return tonumber(clean_name) end


    -- Iterate Item Data
-- Iterate Item Data
    for key, item in pairs(items_data) do
for key, item in pairs(items_data) do
        if item["Name"] == clean_name then
if item["Name"] == clean_name then
           
            -- Valve data structure (nested)
-- Valve data structure (nested)
            if item["m_mapAbilityProperties"] and item["m_mapAbilityProperties"]["BuildUpPerShot"] then
if item["m_mapAbilityProperties"] and item["m_mapAbilityProperties"]["BuildUpPerShot"] then
                return tonumber(item["m_mapAbilityProperties"]["BuildUpPerShot"]["m_strValue"])
return tonumber(item["m_mapAbilityProperties"]["BuildUpPerShot"]["m_strValue"])
            end
end
           
            -- Flat data structure (fallback)
-- Flat data structure (fallback)
            if item["BuildUpPerShot"] then
if item["BuildUpPerShot"] then
                return tonumber(item["BuildUpPerShot"])
return tonumber(item["BuildUpPerShot"])
            end
end
        end
end
    end
end
    return nil
return nil
end
end


-- Helper to find Hero Data by name or key
-- Helper to find Hero Data by name or key
local function get_hero_data(hero_name)
local function get_hero_data(hero_name)
    if hero_name == nil then return nil end
if hero_name == nil then return nil end
   
    local clean_name = mw.text.trim(hero_name)
local clean_name = mw.text.trim(hero_name)
   
    -- Try direct Key lookup
-- Try direct Key lookup
    if heroes_data[clean_name] then return heroes_data[clean_name] end
if heroes_data[clean_name] then return heroes_data[clean_name] end
   
    -- Try adding "hero_" prefix
-- Try adding "hero_" prefix
    local key_lower = "hero_" .. string.lower(clean_name)
local key_lower = "hero_" .. string.lower(clean_name)
    if heroes_data[key_lower] then return heroes_data[key_lower] end
if heroes_data[key_lower] then return heroes_data[key_lower] end
   
    -- Search by Name field
-- Search by Name field
    for k, v in pairs(heroes_data) do
for k, v in pairs(heroes_data) do
        if v["Name"] == clean_name then return v end
if v["Name"] == clean_name then return v end
    end
end
    return nil
return nil
end
 
-- Helper: number of bullets/pellets fired per trigger pull
-- For shotguns (Abrams, etc.) this is the pellet count.
-- For burst weapons this is the burst size.
-- For normal weapons it is 1.
local function get_bullets_per_shot(hero)
    if not hero or not hero.Weapon then return 1 end
    local bps = tonumber(
        hero.Weapon.BulletsPerShot or
        hero.Weapon.PelletsPerShot or
        hero.Weapon.BulletsPerBurst or
        1
    )
    if bps == nil or bps < 1 then bps = 1 end
    return bps
end
end


-- Core calculation logic
-- Core calculation logic
-- Returns: per_bullet_bps (float), bullets_per_shot (int)
local function calculate_bps(hero, bb)
local function calculate_bps(hero, bb)
    if not hero or not hero.Weapon then return 0, 1 end
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
 
    -- 3. Handle Silver (Transformed) - uses base form's RPS for buildup
    if hero.Name == "Silver (Transformed)" then
        rps = 1.1111  -- Base Silver's RoundsPerSecond
    end


    if rps == 0 then return 0, 1 end
local rps = tonumber(hero.Weapon.RoundsPerSecond or 0)
local spin_rps = tonumber(hero.Weapon.RoundsPerSecondAtMaxSpin or 0)
-- Handle Spin-up logic (McGinnis/Bebop)
-- If they have a max spin speed, use that instead of base RPS
if spin_rps > 0 then rps = spin_rps end


    local bullets_per_shot = get_bullets_per_shot(hero)
if rps == 0 then return 0 end


    -- Original Formula: 100 / (BB * (RPS + 1))
-- Formula: 100 / (BB * (RPS + 1))
    -- This calculates the buildup per trigger pull assuming all pellets connect
return 100 / (bb * (rps + 1))
    local bps_per_shot = 100 / (bb * (rps + 1))
 
    -- Divide by bullets/pellets per shot to get the per-bullet buildup
    local bps_per_bullet = bps_per_shot / bullets_per_shot
 
    return bps_per_bullet, bullets_per_shot
end
end


--{{#invoke:Buildup|get_bps|HERO_NAME|ITEM_NAME}}
--{{#invoke:Buildup|get_bps|HERO_NAME|ITEM_NAME}}
--Returns a single float value (rounded to 1 decimal) representing buildup PER BULLET
--Returns a single float value (rounded to 1 decimal)
p.get_bps = function(frame)
p.get_bps = function(frame)
    local hero_name = frame.args[1] or frame.args['hero']
local hero_name = frame.args[1] or frame.args['hero']
    local item_name = frame.args[2] or frame.args['item']
local item_name = frame.args[2] or frame.args['item']


    local hero = get_hero_data(hero_name)
local hero = get_hero_data(hero_name)
    if not hero then return "Hero Not Found" end
if not hero then return "Hero Not Found" end


    local bb = get_item_bb(item_name)
local bb = get_item_bb(item_name)
    if not bb then return "Item Not Found" end
if not bb then return "Item Not Found" end


    local result, _ = calculate_bps(hero, bb)
local result = calculate_bps(hero, bb)
    return string.format("%.1f", result)
return string.format("%.1f", result)
end
end


--{{#invoke:Buildup|write_buildup_table|ITEM_NAME}}
--{{#invoke:Buildup|write_buildup_table|ITEM_NAME}}
--Generates a sortable wikitable for all heroes showing per-bullet buildup
--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 item_name = frame.args[1]
   
    local bb = get_item_bb(item_name)
local bb = get_item_bb(item_name)
    if not bb then return "Error: Item '" .. (item_name or "nil") .. "' not found." end
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"] and hero["Name"] then
            local val, bullets_per_shot = calculate_bps(hero, bb)
           
            if val > 0 then
                -- Shots to proc assumes every bullet in the shot connects
                local per_shot = val * bullets_per_shot
                local shots = math.ceil(100 / per_shot)
               
                table.insert(hero_list, {
                    name = hero["Name"],
                    bps = val,
                    bullets_per_shot = bullets_per_shot,
                    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)  
local hero_list = {}
    end)
-- Collect valid heroes
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
-- Calculate shots needed (Rounding UP is crucial for 19.99% edge cases)
local shots = math.ceil(100 / val)
table.insert(hero_list, {
name = hero["Name"],
bps = val,
shots = shots
})
end
end
end


    -- Build Table
-- Sort Alphabetically
    local html = mw.html.create('table')
table.sort(hero_list, function(a, b) return a.name < b.name end)
    html:addClass('wikitable sortable mw-collapsible')
   
    if item_name then
        html:tag('caption'):wikitext('Buildup Per Bullet: ' .. item_name)
    end


    -- Headers
-- Build Table
    local headerRow = html:tag('tr')
local html = mw.html.create('table')
    headerRow:tag('th'):wikitext('Hero')
html:addClass('wikitable sortable mw-collapsible')
    headerRow:tag('th'):wikitext('% per bullet')
    headerRow:tag('th'):wikitext('Bullets/Shot')
if item_name then
    headerRow:tag('th'):wikitext('Shots to Proc<br/><small>(all bullets hit)</small>')
html:tag('caption'):wikitext('Buildup Per Shot: ' .. item_name)
end


    -- Rows
-- Headers
    for _, h in ipairs(hero_list) do
local headerRow = html:tag('tr')
        local row = html:tag('tr')
headerRow:tag('th'):wikitext('Hero')
        local hero_display
headerRow:tag('th'):wikitext('% per shot')
   
headerRow:tag('th'):wikitext('Shots to Proc')
        if h.name == "Silver (Transformed)" then
            -- Detect page language from subpage (e.g. /ru)
            local currentTitle = mw.title.getCurrentTitle().fullText
            local langCode = currentTitle:match("/([^/]+)$") or "en"
            local isRussian = (langCode == "ru")
       
            local displayName = isRussian and "Сильвер (Трансформация)" or "Silver (Transformed)"
            local linkTarget = isRussian and "Silver (Transformed)/ru" or "Silver (Transformed)"
       
            local icon_file = string.format('[[File:Silver.png|20px|link=%s]]', linkTarget)
            local name_link = string.format('[[%s|%s]]', linkTarget, displayName)
            hero_display = string.format(
                '<span style="white-space:nowrap;">'
                .. '<span style="position:relative; bottom:2px;">%s</span>'
                .. ' %s'
                .. '</span>',
                icon_file, name_link
            )
        else
            hero_display = frame:expandTemplate{ title = 'HeroIcon', args = { h.name } }
        end
   
        row:tag('td')
            :attr('data-sort-value', h.name)
            :wikitext(hero_display)
   
        row:tag('td'):wikitext(string.format("%.1f", h.bps))
       
        -- Show "—" for normal single-bullet weapons, the actual count for shotguns/bursts
        if h.bullets_per_shot > 1 then
            row:tag('td'):wikitext(tostring(h.bullets_per_shot))
        else
            row:tag('td'):wikitext('')
        end


        row:tag('td'):wikitext(tostring(h.shots))
-- Rows
    end
for _, h in ipairs(hero_list) do
local row = html:tag('tr')
-- Expand HeroIcon template
local icon_template = frame:expandTemplate{ title = 'HeroIcon', args = { h.name } }
row:tag('td'):wikitext(icon_template)
row:tag('td'):wikitext(string.format("%.1f", h.bps))
row:tag('td'):wikitext(tostring(h.shots))
end


    return tostring(html)
return tostring(html)
end
end


Line 244: Line 152:
-- Decides whether to return a single value or a full table based on arguments
-- Decides whether to return a single value or a full table based on arguments
p.main = function(frame)
p.main = function(frame)
    local args = frame:getParent().args
local args = frame:getParent().args
    -- Handle direct #invoke calls vs Template calls
-- Handle direct #invoke calls vs Template calls
    if not args[1] and not args['item'] then args = frame.args end
if not args[1] and not args['item'] then args = frame.args end


    local arg1 = args[1] or args['hero']
local arg1 = args[1] or args['hero']
    local arg2 = args[2] or args['item']
local arg2 = args[2] or args['item']


    -- If both Hero and pItem are present -> Return Single Number
-- If both Hero and Item are present -> Return Single Number
    if arg1 and arg2 then
if arg1 and arg2 then
        -- Manually setting args for the helper function to read
-- Manually setting args for the helper function to read
        frame.args = {arg1, arg2}
frame.args = {arg1, arg2}
        return p.get_bps(frame)
return p.get_bps(frame)
       
    -- If only one arg (Item) is present -> Return Table
-- If only one arg (Item) is present -> Return Table
    elseif arg1 then
elseif arg1 then
        frame.args = {arg1}
frame.args = {arg1}
        return p.write_buildup_table(frame)
return p.write_buildup_table(frame)
       
    else
else
        return "Error: Provide [Item Name] for a table, or [Hero Name] [Item Name] for a value."
return "Error: Provide [Item Name] for a table, or [Hero Name] [Item Name] for a value."
    end
end
end
end


return p
return p
Please note that all contributions to The Deadlock Wiki are considered to be released under the Creative Commons Attribution-NonCommercial-ShareAlike (see Deadlock:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!
Cancel Editing help (opens in new window)
Preview page with this template