Module:ExpressShot

From The Deadlock Wiki
Revision as of 17:57, 9 July 2026 by Vergir (talk | contribs) (Add per-hero Express Shot proc damage table (with help from vergir-bot LLM))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Documentation for this module may be created at Module:ExpressShot/doc

local p = {}

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

-- Reads a property from Data:ItemData.json, unwrapping the {Value, Scale} form
-- that scaling properties use. Returns the unscaled base value.
local function get_item_prop(item_name, property)
	for _, item in pairs(items_data) do
		if item["Name"] == item_name and item["IsDisabled"] ~= true then
			local value = item[property]
			if type(value) == "table" then value = value["Value"] end
			return tonumber(value)
		end
	end
	return nil
end

-- Damage of a single Express Shot proc attack.
--
-- The proc fires two bursts. Every bullet of the second burst is bonused, but
-- only the first bullet of the first burst is. For non-burst weapons
-- (BulletsPerBurst == 1) this collapses to both shots being fully bonused.
-- Spreadshot pellets are each bonused; weapons that only register one hit
-- across all their bullets (HitOnceAcrossAllBullets) count as a single bullet.
--
-- @param   {table}  weapon   a Weapon or Weapon.AltFire table
-- @param   {number} percent  proc bonus damage, in percent of Bullet Damage
-- @return  {number, number}  total attack damage, bonus damage over a normal attack
local function proc_damage(weapon, percent)
	local damage  = weapon["BulletDamage"] or 0
	local bullets = weapon["HitOnceAcrossAllBullets"] and 1 or (weapon["BulletsPerShot"] or 1)
	local burst   = weapon["BulletsPerBurst"] or 1
	local bonus_scale = percent / 100

	local per_bullet = damage * bullets
	local bonused_bullets = burst + 1
	local total_bullets   = burst * 2

	local total  = per_bullet * (total_bullets + bonused_bullets * bonus_scale)
	local normal = per_bullet * burst

	return total, total - normal
end

-- Builds one table row for a weapon.
local function build_row(html, frame, hero_name, weapon, percent, is_alt_fire)
	local dps = weapon["DPS"]
	if dps == nil or dps == 0 then return end

	local total, bonus = proc_damage(weapon, percent)
	local dps_increase = bonus / dps * 100

	local label = frame:expandTemplate{ title = 'HeroIcon', args = { hero_name } }
	local sort_name = hero_name:gsub("^The ", "")
	if is_alt_fire then
		label = label .. " (alt fire)"
		sort_name = sort_name .. " (alt fire)"
	end

	local row = html:tag('tr')
	row:tag('td'):attr('data-sort-value', sort_name):wikitext(label)
	row:tag('td'):wikitext(string.format("%.1f", total))
	row:tag('td'):wikitext(string.format("%.1f", bonus))
	row:tag('td')
		:attr('data-sort-value', string.format("%.4f", dps_increase))
		:wikitext(string.format("%.1f%%", dps_increase))
end

--{{#invoke:ExpressShot|write_proc_damage_table|ITEM_NAME}}
--Generates a sortable wikitable of proc damage for every playable hero.
p.write_proc_damage_table = function(frame)
	local item_name = frame.args[1] or "Express Shot"

	local percent     = get_item_prop(item_name, "ProcBaseAttackDamagePercent")
	local percent_alt = get_item_prop(item_name, "ProcBaseAttackDamagePercentAltFire")
	if percent == nil then
		return "<span style=\"color:red;\">Item '" .. item_name .. "' has no ProcBaseAttackDamagePercent.</span>"
	end

	local hero_list = {}
	for _, hero in pairs(heroes_data) do
		local weapon = hero["Weapon"]
		if hero["Name"] and weapon and weapon["BulletDamage"]
			and not hero["InDevelopment"] and not hero["IsDisabled"] then
			table.insert(hero_list, hero)
		end
	end

	table.sort(hero_list, function(a, b)
		return (a["Name"]:gsub("^The ", "")) < (b["Name"]:gsub("^The ", ""))
	end)

	local html = mw.html.create('table')
	html:addClass('wikitable sortable')

	local header = html:tag('tr')
	header:tag('th'):wikitext('Hero')
	header:tag('th'):wikitext('Attack damage')
	header:tag('th'):wikitext('Bonus damage')
	header:tag('th'):wikitext('DPS increase')

	for _, hero in ipairs(hero_list) do
		local weapon = hero["Weapon"]
		build_row(html, frame, hero["Name"], weapon, percent, false)

		local alt_fire = weapon["AltFire"]
		if alt_fire and alt_fire["BulletDamage"] and percent_alt then
			build_row(html, frame, hero["Name"], alt_fire, percent_alt, true)
		end
	end

	return tostring(html)
end

return p