Module:Buildup: Difference between revisionsGive feedback
No edit summary |
Fix shotgun buildup by calculating per-bullet instead of per-shot; add Bullets/Shot column. |
||
| (4 intermediate revisions by 2 users not shown) | |||
| 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 | |||
-- 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 | 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 | |||
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 | |||
-- 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 end | if not hero or not hero.Weapon then return 0, 1 end | ||
local rps = tonumber(hero.Weapon.RoundsPerSecond or 0) | local rps = tonumber(hero.Weapon.RoundsPerSecond or 0) | ||
| Line 83: | Line 100: | ||
end | end | ||
if rps == 0 then return 0 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 | |||
-- Formula: 100 / (BB * (RPS + 1)) | local bullets_per_shot = get_bullets_per_shot(hero) | ||
-- Original Formula: 100 / (BB * (RPS + 1)) | |||
-- This calculates the buildup per trigger pull assuming all pellets connect | |||
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) | --Returns a single float value (rounded to 1 decimal) representing buildup PER BULLET | ||
p.get_bps = function(frame) | 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 | end | ||
--{{#invoke:Buildup|write_buildup_table|ITEM_NAME}} | --{{#invoke:Buildup|write_buildup_table|ITEM_NAME}} | ||
--Generates a sortable wikitable for all heroes | --Generates a sortable wikitable for all heroes showing per-bullet buildup | ||
p.write_buildup_table = function(frame) | p.write_buildup_table = function(frame) | ||
local item_name = frame.args[1] | local item_name = frame.args[1] | ||
| Line 119: | Line 149: | ||
local is_disabled = hero["IsDisabled"] == true | local is_disabled = hero["IsDisabled"] == true | ||
if not is_dev and not is_disabled and hero["Weapon"] then | if not is_dev and not is_disabled and hero["Weapon"] and hero["Name"] then | ||
local val = calculate_bps(hero, bb) | local val, bullets_per_shot = calculate_bps(hero, bb) | ||
if val > 0 then | if val > 0 then | ||
local shots = math.ceil(100 / | -- 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, { | table.insert(hero_list, { | ||
name = hero["Name"], | name = hero["Name"], | ||
bps = val, | bps = val, | ||
bullets_per_shot = bullets_per_shot, | |||
shots = shots | shots = shots | ||
}) | }) | ||
| Line 152: | Line 185: | ||
if item_name then | if item_name then | ||
html:tag('caption'):wikitext('Buildup Per | html:tag('caption'):wikitext('Buildup Per Bullet: ' .. item_name) | ||
end | end | ||
| Line 158: | Line 191: | ||
local headerRow = html:tag('tr') | local headerRow = html:tag('tr') | ||
headerRow:tag('th'):wikitext('Hero') | headerRow:tag('th'):wikitext('Hero') | ||
headerRow:tag('th'):wikitext('% per | headerRow:tag('th'):wikitext('% per bullet') | ||
headerRow:tag('th'):wikitext('Shots to Proc') | headerRow:tag('th'):wikitext('Bullets/Shot') | ||
headerRow:tag('th'):wikitext('Shots to Proc<br/><small>(all bullets hit)</small>') | |||
-- Rows | -- Rows | ||
for _, h in ipairs(hero_list) do | for _, h in ipairs(hero_list) do | ||
local row = html:tag('tr') | local row = html:tag('tr') | ||
local | local hero_display | ||
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') | row:tag('td') | ||
:attr('data-sort-value', h.name) | :attr('data-sort-value', h.name) | ||
:wikitext( | :wikitext(hero_display) | ||
row:tag('td'):wikitext(string.format("%.1f", h.bps)) | 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)) | row:tag('td'):wikitext(tostring(h.shots)) | ||
end | end | ||
| Line 181: | Line 244: | ||
-- 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 | |||
-- 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 pItem 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 | end | ||
return p | return p | ||