Module:Sandbox/LVL: Difference between revisions

Jump to navigation Jump to search
LVL (talk | contribs)
No edit summary
LVL (talk | contribs)
No edit summary
Line 1: Line 1:
local p = {}
local p = {}


-- Load Data Files
local heroes_data = mw.loadJsonData("Data:HeroData.json")
local heroes_data = mw.loadJsonData("Data:HeroData.json")
local items_data = mw.loadJsonData("Data:ItemData.json")
local items_data = mw.loadJsonData("Data:ItemData.json")


--------------------------------------------------------------------------------
-- Helper to find Item Buildup value by name
-- HELPER FUNCTIONS (Logic only, no Frame stuff)
-- Handles both flat numbers and lookup in the ItemData file
--------------------------------------------------------------------------------
 
local function get_item_bb(target_name)
local function get_item_bb(target_name)
    -- Clean whitespace
if target_name == nil then return nil end
    target_name = mw.text.trim(target_name)
   
-- Clean input
    if tonumber(target_name) then return tonumber(target_name) end
local clean_name = mw.text.trim(target_name)
-- If user input a number directly
if tonumber(clean_name) then return tonumber(clean_name) end


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


-- Helper to find Hero Data by name or key
local function get_hero_data(hero_name)
local function get_hero_data(hero_name)
    hero_name = mw.text.trim(hero_name)
if hero_name == nil then return nil end
   
    -- Try direct Key
local clean_name = mw.text.trim(hero_name)
    if heroes_data[hero_name] then return heroes_data[hero_name] end
    -- Try "hero_" prefix
-- Try direct Key lookup
    if heroes_data["hero_" .. string.lower(hero_name)] then return heroes_data["hero_" .. string.lower(hero_name)] end
if heroes_data[clean_name] then return heroes_data[clean_name] end
   
    -- Search by Name field
-- Try adding "hero_" prefix
    for k, v in pairs(heroes_data) do
local key_lower = "hero_" .. string.lower(clean_name)
        if v["Name"] == hero_name then return v end
if heroes_data[key_lower] then return heroes_data[key_lower] end
    end
    return nil
-- 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
end


-- 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
if not hero or not hero.Weapon then return 0 end


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


    if rps == 0 then return 0 end
if rps == 0 then return 0 end


    -- Formula: 100 / (BB * (RPS + 1))
-- Formula: 100 / (BB * (RPS + 1))
    return 100 / (bb * (rps + 1))
return 100 / (bb * (rps + 1))
end
end


--------------------------------------------------------------------------------
--{{#invoke:Buildup|get_bps|HERO_NAME|ITEM_NAME}}
-- OUTPUT GENERATORS
--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 function generate_single_value(hero_name, item_name)
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


local function generate_table(item_name, frame)
--{{#invoke:Buildup|write_buildup_table|ITEM_NAME}}
    local bb = get_item_bb(item_name)
--Generates a sortable wikitable for all heroes
    if not bb then return "Error: Item '" .. item_name .. "' not found." end
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 = {}
local hero_list = {}
    for key, hero in pairs(heroes_data) do
        local is_dev = hero["InDevelopment"] == true
-- Collect valid heroes
        local is_disabled = hero["IsDisabled"] == true
for key, hero in pairs(heroes_data) do
       
local is_dev = hero["InDevelopment"] == true
        if not is_dev and not is_disabled and hero["Weapon"] then
local is_disabled = hero["IsDisabled"] == true
            local val = calculate_bps(hero, bb)
            if val > 0 then
if not is_dev and not is_disabled and hero["Weapon"] then
                -- Calculate shots needed using the raw, unrounded value
local val = calculate_bps(hero, bb)
                -- math.ceil rounds 5.0001 up to 6
                local shots = math.ceil(100 / val)
if val > 0 then
               
-- Calculate shots needed (Rounding UP is crucial for 19.99% edge cases)
                table.insert(hero_list, {
local shots = math.ceil(100 / val)
                    name = hero["Name"],
                    bps = val,
table.insert(hero_list, {
                    shots = shots
name = hero["Name"],
                })
bps = val,
            end
shots = shots
        end
})
    end
end
end
end


    table.sort(hero_list, function(a, b) return a.name < b.name end)
-- Sort Alphabetically
table.sort(hero_list, function(a, b) return a.name < b.name end)


    local html = mw.html.create('table')
-- Build Table
    html:addClass('wikitable sortable mw-collapsible')
local html = mw.html.create('table')
    html:tag('caption'):wikitext('Buildup Per Shot: ' .. item_name)
html:addClass('wikitable sortable mw-collapsible')
if item_name then
html:tag('caption'):wikitext('Buildup Per Shot: ' .. item_name)
end


    local headerRow = html:tag('tr')
-- Headers
    headerRow:tag('th'):wikitext('Hero')
local headerRow = html:tag('tr')
    headerRow:tag('th'):wikitext('% per shot')
headerRow:tag('th'):wikitext('Hero')
    headerRow:tag('th'):wikitext('Shots to Proc') -- New Column
headerRow:tag('th'):wikitext('% per shot')
headerRow:tag('th'):wikitext('Shots to Proc')


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


--------------------------------------------------------------------------------
-- Main entry point for the template
-- MAIN TRAFFIC COP
-- Decides whether to return a single value or a full table based on arguments
--------------------------------------------------------------------------------
p.main = function(frame)
 
local args = frame:getParent().args
function p.main(frame)
-- Handle direct #invoke calls vs Template calls
    local args = frame:getParent().args
if not args[1] and not args['item'] then args = frame.args end
    -- If called via #invoke directly with no parent template
    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']


    -- LOGIC:
local arg1 = args[1] or args['hero']
    -- If 2 args provided -> It's Hero + Item -> Return Single Number
local arg2 = args[2] or args['item']
    -- If 1 arg provided  -> It's Item Only  -> Return Table


    if arg1 and arg2 then
-- If both Hero and Item are present -> Return Single Number
        return generate_single_value(arg1, arg2)
if arg1 and arg2 then
    elseif arg1 then
-- Manually setting args for the helper function to read
        return generate_table(arg1, frame)
frame.args = {arg1, arg2}
    else
return p.get_bps(frame)
        return "Error: Please provide an Item Name (for table) or Hero + Item (for single stat)."
    end
-- 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
end


return p
return p