<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://deadlock.wiki/Module:ExpressShot?action=history&amp;feed=atom</id>
	<title>Module:ExpressShot - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://deadlock.wiki/Module:ExpressShot?action=history&amp;feed=atom"/>
	<link rel="alternate" type="text/html" href="https://deadlock.wiki/Module:ExpressShot?action=history"/>
	<updated>2026-09-10T22:39:45Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.46.0</generator>
	<entry>
		<id>https://deadlock.wiki/Module:ExpressShot?diff=89345&amp;oldid=prev</id>
		<title>Vergir: Add per-hero Express Shot proc damage table (with help from vergir-bot LLM)</title>
		<link rel="alternate" type="text/html" href="https://deadlock.wiki/Module:ExpressShot?diff=89345&amp;oldid=prev"/>
		<updated>2026-07-09T17:57:49Z</updated>

		<summary type="html">&lt;p&gt;Add per-hero Express Shot proc damage table (with help from vergir-bot LLM)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local p = {}&lt;br /&gt;
&lt;br /&gt;
local heroes_data = mw.loadJsonData(&amp;quot;Data:HeroData.json&amp;quot;)&lt;br /&gt;
local items_data  = mw.loadJsonData(&amp;quot;Data:ItemData.json&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
-- Reads a property from Data:ItemData.json, unwrapping the {Value, Scale} form&lt;br /&gt;
-- that scaling properties use. Returns the unscaled base value.&lt;br /&gt;
local function get_item_prop(item_name, property)&lt;br /&gt;
	for _, item in pairs(items_data) do&lt;br /&gt;
		if item[&amp;quot;Name&amp;quot;] == item_name and item[&amp;quot;IsDisabled&amp;quot;] ~= true then&lt;br /&gt;
			local value = item[property]&lt;br /&gt;
			if type(value) == &amp;quot;table&amp;quot; then value = value[&amp;quot;Value&amp;quot;] end&lt;br /&gt;
			return tonumber(value)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Damage of a single Express Shot proc attack.&lt;br /&gt;
--&lt;br /&gt;
-- The proc fires two bursts. Every bullet of the second burst is bonused, but&lt;br /&gt;
-- only the first bullet of the first burst is. For non-burst weapons&lt;br /&gt;
-- (BulletsPerBurst == 1) this collapses to both shots being fully bonused.&lt;br /&gt;
-- Spreadshot pellets are each bonused; weapons that only register one hit&lt;br /&gt;
-- across all their bullets (HitOnceAcrossAllBullets) count as a single bullet.&lt;br /&gt;
--&lt;br /&gt;
-- @param   {table}  weapon   a Weapon or Weapon.AltFire table&lt;br /&gt;
-- @param   {number} percent  proc bonus damage, in percent of Bullet Damage&lt;br /&gt;
-- @return  {number, number}  total attack damage, bonus damage over a normal attack&lt;br /&gt;
local function proc_damage(weapon, percent)&lt;br /&gt;
	local damage  = weapon[&amp;quot;BulletDamage&amp;quot;] or 0&lt;br /&gt;
	local bullets = weapon[&amp;quot;HitOnceAcrossAllBullets&amp;quot;] and 1 or (weapon[&amp;quot;BulletsPerShot&amp;quot;] or 1)&lt;br /&gt;
	local burst   = weapon[&amp;quot;BulletsPerBurst&amp;quot;] or 1&lt;br /&gt;
	local bonus_scale = percent / 100&lt;br /&gt;
&lt;br /&gt;
	local per_bullet = damage * bullets&lt;br /&gt;
	local bonused_bullets = burst + 1&lt;br /&gt;
	local total_bullets   = burst * 2&lt;br /&gt;
&lt;br /&gt;
	local total  = per_bullet * (total_bullets + bonused_bullets * bonus_scale)&lt;br /&gt;
	local normal = per_bullet * burst&lt;br /&gt;
&lt;br /&gt;
	return total, total - normal&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Builds one table row for a weapon.&lt;br /&gt;
local function build_row(html, frame, hero_name, weapon, percent, is_alt_fire)&lt;br /&gt;
	local dps = weapon[&amp;quot;DPS&amp;quot;]&lt;br /&gt;
	if dps == nil or dps == 0 then return end&lt;br /&gt;
&lt;br /&gt;
	local total, bonus = proc_damage(weapon, percent)&lt;br /&gt;
	local dps_increase = bonus / dps * 100&lt;br /&gt;
&lt;br /&gt;
	local label = frame:expandTemplate{ title = &amp;#039;HeroIcon&amp;#039;, args = { hero_name } }&lt;br /&gt;
	local sort_name = hero_name:gsub(&amp;quot;^The &amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
	if is_alt_fire then&lt;br /&gt;
		label = label .. &amp;quot; (alt fire)&amp;quot;&lt;br /&gt;
		sort_name = sort_name .. &amp;quot; (alt fire)&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local row = html:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
	row:tag(&amp;#039;td&amp;#039;):attr(&amp;#039;data-sort-value&amp;#039;, sort_name):wikitext(label)&lt;br /&gt;
	row:tag(&amp;#039;td&amp;#039;):wikitext(string.format(&amp;quot;%.1f&amp;quot;, total))&lt;br /&gt;
	row:tag(&amp;#039;td&amp;#039;):wikitext(string.format(&amp;quot;%.1f&amp;quot;, bonus))&lt;br /&gt;
	row:tag(&amp;#039;td&amp;#039;)&lt;br /&gt;
		:attr(&amp;#039;data-sort-value&amp;#039;, string.format(&amp;quot;%.4f&amp;quot;, dps_increase))&lt;br /&gt;
		:wikitext(string.format(&amp;quot;%.1f%%&amp;quot;, dps_increase))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--{{#invoke:ExpressShot|write_proc_damage_table|ITEM_NAME}}&lt;br /&gt;
--Generates a sortable wikitable of proc damage for every playable hero.&lt;br /&gt;
p.write_proc_damage_table = function(frame)&lt;br /&gt;
	local item_name = frame.args[1] or &amp;quot;Express Shot&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	local percent     = get_item_prop(item_name, &amp;quot;ProcBaseAttackDamagePercent&amp;quot;)&lt;br /&gt;
	local percent_alt = get_item_prop(item_name, &amp;quot;ProcBaseAttackDamagePercentAltFire&amp;quot;)&lt;br /&gt;
	if percent == nil then&lt;br /&gt;
		return &amp;quot;&amp;lt;span style=\&amp;quot;color:red;\&amp;quot;&amp;gt;Item &amp;#039;&amp;quot; .. item_name .. &amp;quot;&amp;#039; has no ProcBaseAttackDamagePercent.&amp;lt;/span&amp;gt;&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local hero_list = {}&lt;br /&gt;
	for _, hero in pairs(heroes_data) do&lt;br /&gt;
		local weapon = hero[&amp;quot;Weapon&amp;quot;]&lt;br /&gt;
		if hero[&amp;quot;Name&amp;quot;] and weapon and weapon[&amp;quot;BulletDamage&amp;quot;]&lt;br /&gt;
			and not hero[&amp;quot;InDevelopment&amp;quot;] and not hero[&amp;quot;IsDisabled&amp;quot;] then&lt;br /&gt;
			table.insert(hero_list, hero)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	table.sort(hero_list, function(a, b)&lt;br /&gt;
		return (a[&amp;quot;Name&amp;quot;]:gsub(&amp;quot;^The &amp;quot;, &amp;quot;&amp;quot;)) &amp;lt; (b[&amp;quot;Name&amp;quot;]:gsub(&amp;quot;^The &amp;quot;, &amp;quot;&amp;quot;))&lt;br /&gt;
	end)&lt;br /&gt;
&lt;br /&gt;
	local html = mw.html.create(&amp;#039;table&amp;#039;)&lt;br /&gt;
	html:addClass(&amp;#039;wikitable sortable&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
	local header = html:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
	header:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;Hero&amp;#039;)&lt;br /&gt;
	header:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;Attack damage&amp;#039;)&lt;br /&gt;
	header:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;Bonus damage&amp;#039;)&lt;br /&gt;
	header:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;DPS increase&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
	for _, hero in ipairs(hero_list) do&lt;br /&gt;
		local weapon = hero[&amp;quot;Weapon&amp;quot;]&lt;br /&gt;
		build_row(html, frame, hero[&amp;quot;Name&amp;quot;], weapon, percent, false)&lt;br /&gt;
&lt;br /&gt;
		local alt_fire = weapon[&amp;quot;AltFire&amp;quot;]&lt;br /&gt;
		if alt_fire and alt_fire[&amp;quot;BulletDamage&amp;quot;] and percent_alt then&lt;br /&gt;
			build_row(html, frame, hero[&amp;quot;Name&amp;quot;], alt_fire, percent_alt, true)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return tostring(html)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Vergir</name></author>
	</entry>
</feed>