Module:LevelTables: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
One Bar (talk | contribs)
Create function to automate power level increase tables
 
m Undo revision 65388 by Saag (talk)
Tag: Manual revert
 
(15 intermediate revisions by 5 users not shown)
Line 1: Line 1:
local p = {};
local p = {};


local Icon = require('Module:Icon')
local Souls = require('Module:Souls')
-- Loads the necessary data from the wiki's JSON pages.
local heroes_data = mw.loadJsonData("Data:HeroData.json")
local heroes_data = mw.loadJsonData("Data:HeroData.json")
local soul_unlocks_data = mw.loadJsonData("Data:SoulUnlockData.json")
local soul_unlocks_data = mw.loadJsonData("Data:SoulUnlockData.json")


-- Helper to safely get stats that may be nested under Weapon
local function get_stat_value(data, key)
    if data[key] ~= nil then return data[key] end
    if data.Weapon and data.Weapon[key] ~= nil then return data.Weapon[key] end
    return 0
end
-- Defines human-readable names for the internal property keys used in headers.
local level_scaling_key_to_name = {
local level_scaling_key_to_name = {
BulletDamage = "Bullet Damage",
BulletDamage = "Bullet Damage",
Line 12: Line 24:
}
}


-- Not in HeroData.json although Shiv is just (bullet damage * 12)
-- A helper function that returns a sort-friendly version of a name by removing leading articles like "The ".
local alt_fires = {
local function get_sortable_name(name)
Shiv = {base = 59.4, level = 2.64, suffix = "alt fire 12 pellets"},
if string.sub(name, 1, 4) == "The " then
Viscous = {base = 63, level = 0.9, suffix = "alt fire AOE"},
return string.sub(name, 5)
Yamato = {base =63, level = 0.35, suffix = "alt fire AOE"}
end
}
return name
end


--[[
-- @function p.write_power_increase_table
-- @description Generates a responsive and readable table showing hero stat scaling based on boons.
--              Includes a compact summary view and an expandable, detailed view for all levels.
--              Can optionally wrap the entire output in a theme-aware collapsible section.
-- @param {Frame} frame The MediaWiki frame object.
-- @param {string} frame.args[1] The internal property name to display (e.g., 'BulletDamage').
-- @param {string} [frame.args.title] An optional title. If provided, the table is wrapped in a collapsible section.
-- @return {string} The complete HTML for the table or collapsible section.
--]]
p.write_power_increase_table = function(frame)
p.write_power_increase_table = function(frame)
    -- Use the same smart sorting for this list.
local function hero_sort(h1, h2)
return get_sortable_name(h1.Name) < get_sortable_name(h2.Name)
end
-- Read and validate the arguments passed from the #invoke call.
local property = frame.args[1]
local title = frame.args.title
if not property or property == '' then
return '<strong class="error">Error: Missing required property argument in #invoke call. Example: {{#invoke:ModuleName|write_power_increase_table|BulletDamage}}</strong>'
end
local name = level_scaling_key_to_name[property]
if not name then
return '<strong class="error">Error: Invalid property argument "' .. tostring(property) .. '". Must be one of: BulletDamage, MaxHealth, TechPower, LightMeleeDamage, HeavyMeleeDamage.</strong>'
end
-- Create an ordered list of soul requirements for each power increase.
local pi_souls_list = {}
local pi_souls_list = {}
for _, v in ipairs(soul_unlocks_data) do
for _, v in ipairs(soul_unlocks_data) do
Line 26: Line 68:
end
end
end
end
local level_count = 0
for _ in ipairs(pi_souls_list) do level_count = level_count + 1 end
local max_boons = #pi_souls_list
-- Define the milestones to display in the summary table.
local milestones = {10, 20, max_boons}
-- Filter out disabled heroes from the data.
local filtered_heroes = {}
local filtered_heroes = {}
for _, hero in pairs(heroes_data) do
for _, hero in pairs(heroes_data) do
if (not hero["IsDisabled"]) then
if (not hero["IsDisabled"] and not hero["InDevelopment"] and hero["Name"]) then
table.insert(filtered_heroes, hero)
table.insert(filtered_heroes, hero)
end
end
end
end
local property = frame.args[1] --frame:getParent().args[1]
-- Sort the hero list alphabetically, ignoring "The " for proper sorting.
local name = level_scaling_key_to_name[property]
table.sort(filtered_heroes, hero_sort)
local suffix = property == "BulletDamage" and " (Full Shot)" or ""
local create_table = mw.html.create("table"):addClass("wikitable sortable mw-collapsible mw-collapsed"):done()
-- Begin building the main data table.
local title_header = mw.html.create("tr")
local create_table = mw.html.create("table"):addClass("wikitable sortable")
title_header:tag("th"):attr("colspan", (level_count + 3)):wikitext("Base " .. name .. " based on PI"):done()
create_table:node(title_header)
local label_header = mw.html.create("tr")
-- Create the header row for the summary table.
label_header
local header_row = mw.html.create("tr")
:tag("th"):attr("rowspan", 2):wikitext("Hero"):done()
header_row
:tag("th"):attr("rowspan", 2):wikitext("Base " .. name .. suffix):done()
:tag("th"):wikitext("Hero"):done()
:tag("th"):attr("rowspan", 2):wikitext("PL Increase" .. suffix):done()
:tag("th"):wikitext("Base " .. name):done()
:tag("th"):attr("colspan", level_count):wikitext("Souls Collected"):done()
:tag("th"):wikitext("Increase per Boon"):done()
create_table:node(label_header)
 
-- Dynamically create milestone headers with both boon count and soul amount.
for _, count in ipairs(milestones) do
local header_text = count .. " Boons"
if count == max_boons then header_text = header_text .. " (Max)" end
local required_souls = pi_souls_list[count]
local souls_formatted = string.format("%.1fK", (required_souls + 400) / 1000)
local soul_display =  Souls._render(souls_formatted)
local full_header = header_text .. "<br /><small>(" .. soul_display .. ")</small>"
header_row:tag("th"):wikitext(full_header):done()
end
local souls_header = mw.html.create("tr")
header_row:tag("th"):wikitext("Full Details"):done()
for _, v in ipairs(pi_souls_list) do
create_table:node(header_row)
local souls_formatted = string.format("%.1fK", (v + 400) / 1000)
souls_formatted = frame:expandTemplate{ title = "Souls", args = {souls_formatted}}
souls_header:tag("th"):wikitext(souls_formatted):done()
end
create_table:node(souls_header)
for _, hero in ipairs(filtered_heroes) do
-- A helper function that generates the HTML for a single hero's row and its collapsible details.
local base = math.floor((hero[property] or 0) * 100) / 100
local function addHeroRows(hero_data, is_alt_fire)
local level = math.floor(hero["LevelScaling"][property] * 100) / 100
local base, level, suffix
local shots = hero["BulletsPerShot"]
local hero_icon_wikitext = Icon._render(hero_data['Name'])
local burst = hero["BulletsPerBurst"]
local bullet_mult = shots * burst
-- Determine if this is a main weapon or an alt-fire and set the correct data.
local hero_icon = frame:expandTemplate{ title = "HeroIcon", args = {hero["Name"]}}
if is_alt_fire then
local hero_suffix = ""
base = hero_data.alt_fire.base
if (property == "BulletDamage" and bullet_mult > 1) then
level = hero_data.alt_fire.level
base = base * bullet_mult
suffix = " (" .. hero_data.alt_fire.suffix .. ")"
level = level * bullet_mult
else
if (shots > 1) then hero_suffix = hero_suffix .. " (" .. shots .. " pellets)" end
base = get_stat_value(hero_data, property)
if (burst > 1) then hero_suffix = hero_suffix .. " (" .. burst .. " bullets)" end
level = (hero_data["LevelScaling"] and hero_data["LevelScaling"][property] or 0)
suffix = ""
local shots = get_stat_value(hero_data, "BulletsPerShot")
if shots == 0 then shots = 1 end
local burst = get_stat_value(hero_data, "BulletsPerBurst")
if burst == 0 then burst = 1 end
local hit_once = get_stat_value(hero_data, "HitOnceAcrossAllBullets")
if (property == "BulletDamage" and (shots * burst) > 1) then
if (not hit_once) then
base = base * shots * burst
level = level * shots * burst
end
if (shots > 1) then suffix = suffix .. " (" .. shots .. " pellets)" end
if (burst > 1) then suffix = suffix .. " (" .. burst .. " bullets)" end
end
end
end
local hero_row = mw.html.create("tr")
 
hero_row
-- Build the main summary row with the hero's info and milestone values.
:tag("td"):wikitext(hero_icon .. hero_suffix):done()
local summary_row = mw.html.create("tr")
:tag("td"):wikitext(base):done()
:tag("td"):wikitext("+" .. level):done()
for i, v in ipairs(pi_souls_list) do
local hero_cell_content = hero_icon_wikitext .. suffix
local total = (tonumber(base) ~= nil and base or 0) + (tonumber(level) ~= nil and level or 0)  * i
hero_row
summary_row:tag("td"):wikitext(hero_cell_content):done()
:tag("td"):wikitext(total):done()
summary_row
:tag("td"):wikitext(string.format("%.2f", base)):done()
:tag("td"):wikitext("+" .. string.format("%.2f", level)):done()
for _, count in ipairs(milestones) do
local total = base + level * count
summary_row:tag("td"):wikitext(string.format("%.2f", total)):done()
end
end
-- Create the main container for the collapsible "Full Details" section.
local details_id = "boon-details-" .. hero_data.Name .. "-" .. property .. (is_alt_fire and "-alt" or "")
local collapsible_container = mw.html.create("div")
:addClass("mw-collapsible mw-collapsed")
:attr("id", details_id)
:attr("style", "display: flex; flex-direction: column; align-items: flex-end; gap: 5px;")
create_table:node(hero_row)
-- Style the "Show All" toggle to look like a theme-aware button.
collapsible_container:tag("div")
    :addClass("mw-collapsible-toggle")
    :attr("style", "display: inline-block; padding: 2px 8px; border: 1px solid currentColor; border-radius: 1px; text-decoration: none;")
    :wikitext("Show All")
-- Create the container for the expanded content.
local content_div = mw.html.create("div"):addClass("mw-collapsible-content")
if (property == "BulletDamage" and alt_fires[hero["Name"]] ~= nil) then
-- Generate the three-column layout by distributing rows into mini-tables.
local alt_fire = alt_fires[hero["Name"]]
local column1_rows, column2_rows, column3_rows = {}, {}, {}
local alt_base = alt_fire["base"]
local rows_per_column = math.ceil(max_boons / 3)
local alt_level = alt_fire["level"]
local alt_suffix = " (" .. alt_fire["suffix"] .. ")"
local alt_fire_row = mw.html.create("tr")
alt_fire_row
:tag("td"):wikitext(hero_icon .. alt_suffix):done()
:tag("td"):wikitext(alt_base):done()
:tag("td"):wikitext("+" .. alt_level):done()
for i, v in ipairs(pi_souls_list) do
for i = 1, max_boons do
local total = alt_base + alt_level * i
local total = base + level * i
alt_fire_row:tag("td"):wikitext(total):done()
local required_souls = pi_souls_list[i]
local souls_formatted = string.format("%.1fK", (required_souls + 400) / 1000)
local soul_display = Souls._render(souls_formatted)
local row_html = string.format("<tr><td>%s</td><td style='text-align:right;'>%.2f</td></tr>", soul_display, total)
if i <= rows_per_column then
table.insert(column1_rows, row_html)
elseif i <= rows_per_column * 2 then
table.insert(column2_rows, row_html)
else
table.insert(column3_rows, row_html)
end
end
end
local table_style = "border:none; background:transparent; margin:0 10px; min-width:150px;"
local table1 = "<table style='" .. table_style .. "'>" .. table.concat(column1_rows) .. "</table>"
local table2 = "<table style='" .. table_style .. "'>" .. table.concat(column2_rows) .. "</table>"
local table3 = "<table style='" .. table_style .. "'>" .. table.concat(column3_rows) .. "</table>"
-- Use a flexbox container to display the mini-tables side-by-side.
local flex_container_style = "display:flex; justify-content:space-around; padding:10px 0;"
content_div:wikitext("<div style='" .. flex_container_style .. "'>" .. table1 .. table2 .. table3 .. "</div>")
collapsible_container:node(content_div)
summary_row:tag("td"):node(collapsible_container)
create_table:node(summary_row)
end
-- Iterate through each hero to generate their main row and an alt-fire row if applicable.
for _, hero in ipairs(filtered_heroes) do
addHeroRows(hero, false)
-- Dynamically add alt-fire rows if the data exists
if (property == "BulletDamage" and hero.Weapon and hero.Weapon.AltFire) then
    local alt_fire_data = hero.Weapon.AltFire
    local alt_fire_base = alt_fire_data.BulletDamage or 0
   
    if alt_fire_base > 0 then
        -- Get scaling: prefer alt-fire specific key, fall back to main weapon scaling
        local alt_fire_level = (hero.LevelScaling and
            (hero.LevelScaling.BulletDamageAltFire or hero.LevelScaling[property])) or 0
       
        -- Apply same pellet/burst logic as main weapon
        local shots = alt_fire_data.BulletsPerShot or 1
        local burst = alt_fire_data.BulletsPerBurst or 1
        local hit_once = alt_fire_data.HitOnceAcrossAllBullets or false
       
        local suffix = "alt fire"
       
        if (shots * burst) > 1 and not hit_once then
            alt_fire_base = alt_fire_base * shots * burst
            alt_fire_level = alt_fire_level * shots * burst
            if shots > 1 then suffix = suffix .. " " .. shots .. " pellets" end
            if burst > 1 then suffix = suffix .. " " .. burst .. " bullets" end
        end
       
        -- Add AOE indicator (without radius)
        if alt_fire_data.ExplosionRadius then
            suffix = suffix .. " AOE"
        end
       
        local alt_fire_hero_data = {
            Name = hero.Name,
            alt_fire = {
                base = alt_fire_base,
                level = alt_fire_level,
                suffix = suffix
            }
        }
        addHeroRows(alt_fire_hero_data, true)
    end
end
end
-- If a title was provided, wrap the completed table in a theme-aware collapsible container.
if title and title ~= '' then
local wrapper = mw.html.create('table')
:addClass('mw-collapsible mw-collapsed wikitable')
:css('width', '100%')
:css('margin-bottom', '1em')
local header_row_wrapper = wrapper:tag('tr')
header_row_wrapper:tag('th')
:css('text-align', 'center')
:wikitext('<span style="font-size: larger; font-weight: bold;">' .. title .. '</span><span class="mw-collapsible-toggle mw-collapsible-toggle-right">[Show]</span>')
local content_row_wrapper = wrapper:tag('tr')
content_row_wrapper:tag('td')
:addClass('mw-collapsible-content')
:node(create_table)
create_table:node(alt_fire_row)
return tostring(wrapper)
else
-- If no title is provided, return the table as before.
return tostring(create_table)
end
end
 
--[[
-- @function p.write_multishot_stats
-- @description Outputs a wikitext bulleted list of heroes with spreadshot or burst fire weapons.
-- @param {Frame} frame The MediaWiki frame object.
-- @return {string} A formatted wikitext string.
--]]
p.write_multishot_stats = function(frame)
-- Use the same smart sorting for this list.
local function hero_sort(h1, h2)
return get_sortable_name(h1.Name) < get_sortable_name(h2.Name)
end
local spreadshot_heroes = {}
local burst_heroes = {}
for _, hero in pairs(heroes_data) do
    local bullets_per_shot = get_stat_value(hero, "BulletsPerShot")
    local bullets_per_burst = get_stat_value(hero, "BulletsPerBurst")
   
if (bullets_per_shot > 1 and not hero["IsDisabled"] and not hero["InDevelopment"]) then
table.insert(spreadshot_heroes, hero)
elseif (bullets_per_burst > 1 and not hero["IsDisabled"] and not hero["InDevelopment"]) then
table.insert(burst_heroes, hero)
end
end
end
end
return tostring(create_table)
table.sort(spreadshot_heroes, hero_sort)
table.sort(burst_heroes, hero_sort)
local multishot_str = "Weapons with multiple projectiles (Spreadshot):\n"
for _, hero in ipairs(spreadshot_heroes) do
local base = math.floor(get_stat_value(hero, "BulletDamage") * 100) / 100
local level = math.floor(hero["LevelScaling"]["BulletDamage"] * 100) / 100
local shots = get_stat_value(hero, "BulletsPerShot")
local hit_once = get_stat_value(hero, "HitOnceAcrossAllBullets")
local hero_icon = Icon._render(hero["Name"])
-- Checks for the special property and changes the output text accordingly.
local stat_str
if hit_once then
stat_str = "* " .. hero_icon .. ": " .. shots .. " pellets, "
.. base .. " total damage (hits only once), +" .. level .. " total\n"
else
stat_str = "* " .. hero_icon .. ": " .. shots .. " pellets, "
.. base .. "/pellet, +" .. level .. "/pellet\n"
end
multishot_str = multishot_str .. stat_str
end
multishot_str = multishot_str .. "Weapons with burst fire (Burst Fire):\n"
for _, hero in ipairs(burst_heroes) do
local base = math.floor(get_stat_value(hero, "BulletDamage") * 100) / 100
local level = math.floor(hero["LevelScaling"]["BulletDamage"] * 100) / 100
local shots = get_stat_value(hero, "BulletsPerBurst")
local hero_icon = Icon._render(hero["Name"])
local stat_str = "* " .. hero_icon .. ": " .. shots .. " rounds, "
.. base .. "/bullet, +" .. level .. "/bullet\n"
multishot_str = multishot_str .. stat_str
end
return multishot_str
end
end


return p
return p

Latest revision as of 22:14, 23 April 2026

This module is used to create tables that display a stat of the heroes against the souls of each level powerup. The data comes from Data:HeroData.json and Data:SoulUnlockData.json.

The primary function creates a theme-aware, collapsible section containing a data table. This is invoked with: {{#invoke:LevelTables|write_power_increase_table|(stat)|title=Section Title}} where (stat) is replaced with one of the power level increase stats, and Section Title is the text that appears in the header of the collapsible box.

The generated table provides a summary of key milestones and includes an expandable '[Show All]' button for each hero to view the complete scaling data.

Currently, the valid stats are BulletDamage, MaxHealth, TechPower, LightMeleeDamage, and HeavyMeleeDamage

The stat keys and names are stored in the private variable level_scaling_key_to_name, so if a new stat gets added to level scaling in HeroData.json, you can add it to that variable.

Since the alternate fires of some heroes' guns are not stored in HeroData.json, there is a variable to store the damage and scaling called alt_fires.

Here is an example of the Bullet Damage Table:

Base Bullet Damage[Show]
HeroBase Bullet DamageIncrease per Boon10 Boons
(Souls5.7K)
20 Boons
(Souls14.2K)
35 Boons (Max)
(Souls49.0K)
Full Details
Abrams (9 pellets)32.40+0.9041.4050.4063.90
Show All
Souls0.6K33.30
Souls0.9K34.20
Souls1.3K35.10
Souls1.8K36.00
Souls2.4K36.90
Souls3.0K37.80
Souls3.6K38.70
Souls4.3K39.60
Souls5.0K40.50
Souls5.7K41.40
Souls6.4K42.30
Souls7.2K43.20
Souls8.0K44.10
Souls8.8K45.00
Souls9.6K45.90
Souls10.5K46.80
Souls11.4K47.70
Souls12.3K48.60
Souls13.2K49.50
Souls14.2K50.40
Souls15.3K51.30
Souls16.5K52.20
Souls17.8K53.10
Souls19.3K54.00
Souls21.0K54.90
Souls22.9K55.80
Souls25.0K56.70
Souls27.3K57.60
Souls29.8K58.50
Souls32.5K59.40
Souls35.4K60.30
Souls38.5K61.20
Souls41.8K62.10
Souls45.3K63.00
Souls49.0K63.90
Apollo18.50+0.8226.7535.0047.38
Show All
Souls0.6K19.32
Souls0.9K20.15
Souls1.3K20.98
Souls1.8K21.80
Souls2.4K22.62
Souls3.0K23.45
Souls3.6K24.27
Souls4.3K25.10
Souls5.0K25.93
Souls5.7K26.75
Souls6.4K27.57
Souls7.2K28.40
Souls8.0K29.23
Souls8.8K30.05
Souls9.6K30.88
Souls10.5K31.70
Souls11.4K32.52
Souls12.3K33.35
Souls13.2K34.17
Souls14.2K35.00
Souls15.3K35.83
Souls16.5K36.65
Souls17.8K37.47
Souls19.3K38.30
Souls21.0K39.12
Souls22.9K39.95
Souls25.0K40.77
Souls27.3K41.60
Souls29.8K42.42
Souls32.5K43.25
Souls35.4K44.08
Souls38.5K44.90
Souls41.8K45.72
Souls45.3K46.55
Souls49.0K47.38
Bebop4.98+0.126.137.289.01
Show All
Souls0.6K5.10
Souls0.9K5.21
Souls1.3K5.33
Souls1.8K5.44
Souls2.4K5.56
Souls3.0K5.67
Souls3.6K5.79
Souls4.3K5.90
Souls5.0K6.02
Souls5.7K6.13
Souls6.4K6.25
Souls7.2K6.36
Souls8.0K6.48
Souls8.8K6.59
Souls9.6K6.71
Souls10.5K6.82
Souls11.4K6.94
Souls12.3K7.05
Souls13.2K7.17
Souls14.2K7.28
Souls15.3K7.40
Souls16.5K7.51
Souls17.8K7.62
Souls19.3K7.74
Souls21.0K7.86
Souls22.9K7.97
Souls25.0K8.09
Souls27.3K8.20
Souls29.8K8.32
Souls32.5K8.43
Souls35.4K8.54
Souls38.5K8.66
Souls41.8K8.78
Souls45.3K8.89
Souls49.0K9.01
Billy6.30+0.137.578.8410.75
Show All
Souls0.6K6.43
Souls0.9K6.55
Souls1.3K6.68
Souls1.8K6.81
Souls2.4K6.93
Souls3.0K7.06
Souls3.6K7.19
Souls4.3K7.32
Souls5.0K7.44
Souls5.7K7.57
Souls6.4K7.70
Souls7.2K7.82
Souls8.0K7.95
Souls8.8K8.08
Souls9.6K8.21
Souls10.5K8.33
Souls11.4K8.46
Souls12.3K8.59
Souls13.2K8.71
Souls14.2K8.84
Souls15.3K8.97
Souls16.5K9.09
Souls17.8K9.22
Souls19.3K9.35
Souls21.0K9.47
Souls22.9K9.60
Souls25.0K9.73
Souls27.3K9.86
Souls29.8K9.98
Souls32.5K10.11
Souls35.4K10.24
Souls38.5K10.36
Souls41.8K10.49
Souls45.3K10.62
Souls49.0K10.75
Calico (9 pellets)16.56+0.3920.4324.3030.11
Show All
Souls0.6K16.95
Souls0.9K17.33
Souls1.3K17.72
Souls1.8K18.11
Souls2.4K18.50
Souls3.0K18.88
Souls3.6K19.27
Souls4.3K19.66
Souls5.0K20.04
Souls5.7K20.43
Souls6.4K20.82
Souls7.2K21.20
Souls8.0K21.59
Souls8.8K21.98
Souls9.6K22.37
Souls10.5K22.75
Souls11.4K23.14
Souls12.3K23.53
Souls13.2K23.91
Souls14.2K24.30
Souls15.3K24.69
Souls16.5K25.07
Souls17.8K25.46
Souls19.3K25.85
Souls21.0K26.23
Souls22.9K26.62
Souls25.0K27.01
Souls27.3K27.40
Souls29.8K27.78
Souls32.5K28.17
Souls35.4K28.56
Souls38.5K28.94
Souls41.8K29.33
Souls45.3K29.72
Souls49.0K30.11
Celeste18.00+0.8226.2034.4046.70
Show All
Souls0.6K18.82
Souls0.9K19.64
Souls1.3K20.46
Souls1.8K21.28
Souls2.4K22.10
Souls3.0K22.92
Souls3.6K23.74
Souls4.3K24.56
Souls5.0K25.38
Souls5.7K26.20
Souls6.4K27.02
Souls7.2K27.84
Souls8.0K28.66
Souls8.8K29.48
Souls9.6K30.30
Souls10.5K31.12
Souls11.4K31.94
Souls12.3K32.76
Souls13.2K33.58
Souls14.2K34.40
Souls15.3K35.22
Souls16.5K36.04
Souls17.8K36.86
Souls19.3K37.68
Souls21.0K38.50
Souls22.9K39.32
Souls25.0K40.14
Souls27.3K40.96
Souls29.8K41.78
Souls32.5K42.60
Souls35.4K43.42
Souls38.5K44.24
Souls41.8K45.06
Souls45.3K45.88
Souls49.0K46.70
The Doorman24.00+1.2536.5049.0067.75
Show All
Souls0.6K25.25
Souls0.9K26.50
Souls1.3K27.75
Souls1.8K29.00
Souls2.4K30.25
Souls3.0K31.50
Souls3.6K32.75
Souls4.3K34.00
Souls5.0K35.25
Souls5.7K36.50
Souls6.4K37.75
Souls7.2K39.00
Souls8.0K40.25
Souls8.8K41.50
Souls9.6K42.75
Souls10.5K44.00
Souls11.4K45.25
Souls12.3K46.50
Souls13.2K47.75
Souls14.2K49.00
Souls15.3K50.25
Souls16.5K51.50
Souls17.8K52.75
Souls19.3K54.00
Souls21.0K55.25
Souls22.9K56.50
Souls25.0K57.75
Souls27.3K59.00
Souls29.8K60.25
Souls32.5K61.50
Souls35.4K62.75
Souls38.5K64.00
Souls41.8K65.25
Souls45.3K66.50
Souls49.0K67.75
Drifter (3 pellets)19.50+0.4924.4029.3036.65
Show All
Souls0.6K19.99
Souls0.9K20.48
Souls1.3K20.97
Souls1.8K21.46
Souls2.4K21.95
Souls3.0K22.44
Souls3.6K22.93
Souls4.3K23.42
Souls5.0K23.91
Souls5.7K24.40
Souls6.4K24.89
Souls7.2K25.38
Souls8.0K25.87
Souls8.8K26.36
Souls9.6K26.85
Souls10.5K27.34
Souls11.4K27.83
Souls12.3K28.32
Souls13.2K28.81
Souls14.2K29.30
Souls15.3K29.79
Souls16.5K30.28
Souls17.8K30.77
Souls19.3K31.26
Souls21.0K31.75
Souls22.9K32.24
Souls25.0K32.73
Souls27.3K33.22
Souls29.8K33.71
Souls32.5K34.20
Souls35.4K34.69
Souls38.5K35.18
Souls41.8K35.67
Souls45.3K36.16
Souls49.0K36.65
Dynamo12.60+0.5017.6022.6030.10
Show All
Souls0.6K13.10
Souls0.9K13.60
Souls1.3K14.10
Souls1.8K14.60
Souls2.4K15.10
Souls3.0K15.60
Souls3.6K16.10
Souls4.3K16.60
Souls5.0K17.10
Souls5.7K17.60
Souls6.4K18.10
Souls7.2K18.60
Souls8.0K19.10
Souls8.8K19.60
Souls9.6K20.10
Souls10.5K20.60
Souls11.4K21.10
Souls12.3K21.60
Souls13.2K22.10
Souls14.2K22.60
Souls15.3K23.10
Souls16.5K23.60
Souls17.8K24.10
Souls19.3K24.60
Souls21.0K25.10
Souls22.9K25.60
Souls25.0K26.10
Souls27.3K26.60
Souls29.8K27.10
Souls32.5K27.60
Souls35.4K28.10
Souls38.5K28.60
Souls41.8K29.10
Souls45.3K29.60
Souls49.0K30.10
Graves3.60+0.054.144.685.49
Show All
Souls0.6K3.65
Souls0.9K3.71
Souls1.3K3.76
Souls1.8K3.82
Souls2.4K3.87
Souls3.0K3.92
Souls3.6K3.98
Souls4.3K4.03
Souls5.0K4.09
Souls5.7K4.14
Souls6.4K4.19
Souls7.2K4.25
Souls8.0K4.30
Souls8.8K4.36
Souls9.6K4.41
Souls10.5K4.46
Souls11.4K4.52
Souls12.3K4.57
Souls13.2K4.63
Souls14.2K4.68
Souls15.3K4.73
Souls16.5K4.79
Souls17.8K4.84
Souls19.3K4.90
Souls21.0K4.95
Souls22.9K5.00
Souls25.0K5.06
Souls27.3K5.11
Souls29.8K5.17
Souls32.5K5.22
Souls35.4K5.27
Souls38.5K5.33
Souls41.8K5.38
Souls45.3K5.44
Souls49.0K5.49
Grey Talon23.51+0.8532.0140.5153.26
Show All
Souls0.6K24.36
Souls0.9K25.21
Souls1.3K26.06
Souls1.8K26.91
Souls2.4K27.76
Souls3.0K28.61
Souls3.6K29.46
Souls4.3K30.31
Souls5.0K31.16
Souls5.7K32.01
Souls6.4K32.86
Souls7.2K33.71
Souls8.0K34.56
Souls8.8K35.41
Souls9.6K36.26
Souls10.5K37.11
Souls11.4K37.96
Souls12.3K38.81
Souls13.2K39.66
Souls14.2K40.51
Souls15.3K41.36
Souls16.5K42.21
Souls17.8K43.06
Souls19.3K43.91
Souls21.0K44.76
Souls22.9K45.61
Souls25.0K46.46
Souls27.3K47.31
Souls29.8K48.16
Souls32.5K49.01
Souls35.4K49.86
Souls38.5K50.71
Souls41.8K51.56
Souls45.3K52.41
Souls49.0K53.26
Haze5.26+0.146.698.1210.27
Show All
Souls0.6K5.40
Souls0.9K5.55
Souls1.3K5.69
Souls1.8K5.83
Souls2.4K5.97
Souls3.0K6.12
Souls3.6K6.26
Souls4.3K6.40
Souls5.0K6.55
Souls5.7K6.69
Souls6.4K6.83
Souls7.2K6.98
Souls8.0K7.12
Souls8.8K7.26
Souls9.6K7.40
Souls10.5K7.55
Souls11.4K7.69
Souls12.3K7.83
Souls13.2K7.98
Souls14.2K8.12
Souls15.3K8.26
Souls16.5K8.41
Souls17.8K8.55
Souls19.3K8.69
Souls21.0K8.83
Souls22.9K8.98
Souls25.0K9.12
Souls27.3K9.26
Souls29.8K9.41
Souls32.5K9.55
Souls35.4K9.69
Souls38.5K9.84
Souls41.8K9.98
Souls45.3K10.12
Souls49.0K10.27
Holliday19.70+1.1431.1442.5859.74
Show All
Souls0.6K20.84
Souls0.9K21.99
Souls1.3K23.13
Souls1.8K24.28
Souls2.4K25.42
Souls3.0K26.56
Souls3.6K27.71
Souls4.3K28.85
Souls5.0K30.00
Souls5.7K31.14
Souls6.4K32.28
Souls7.2K33.43
Souls8.0K34.57
Souls8.8K35.72
Souls9.6K36.86
Souls10.5K38.00
Souls11.4K39.15
Souls12.3K40.29
Souls13.2K41.44
Souls14.2K42.58
Souls15.3K43.72
Souls16.5K44.87
Souls17.8K46.01
Souls19.3K47.16
Souls21.0K48.30
Souls22.9K49.44
Souls25.0K50.59
Souls27.3K51.73
Souls29.8K52.88
Souls32.5K54.02
Souls35.4K55.16
Souls38.5K56.31
Souls41.8K57.45
Souls45.3K58.60
Souls49.0K59.74
Infernus5.50+0.096.387.268.58
Show All
Souls0.6K5.59
Souls0.9K5.68
Souls1.3K5.76
Souls1.8K5.85
Souls2.4K5.94
Souls3.0K6.03
Souls3.6K6.12
Souls4.3K6.20
Souls5.0K6.29
Souls5.7K6.38
Souls6.4K6.47
Souls7.2K6.56
Souls8.0K6.64
Souls8.8K6.73
Souls9.6K6.82
Souls10.5K6.91
Souls11.4K7.00
Souls12.3K7.08
Souls13.2K7.17
Souls14.2K7.26
Souls15.3K7.35
Souls16.5K7.44
Souls17.8K7.52
Souls19.3K7.61
Souls21.0K7.70
Souls22.9K7.79
Souls25.0K7.88
Souls27.3K7.96
Souls29.8K8.05
Souls32.5K8.14
Souls35.4K8.23
Souls38.5K8.32
Souls41.8K8.40
Souls45.3K8.49
Souls49.0K8.58
Ivy4.45+0.085.256.057.25
Show All
Souls0.6K4.53
Souls0.9K4.61
Souls1.3K4.69
Souls1.8K4.77
Souls2.4K4.85
Souls3.0K4.93
Souls3.6K5.01
Souls4.3K5.09
Souls5.0K5.17
Souls5.7K5.25
Souls6.4K5.33
Souls7.2K5.41
Souls8.0K5.49
Souls8.8K5.57
Souls9.6K5.65
Souls10.5K5.73
Souls11.4K5.81
Souls12.3K5.89
Souls13.2K5.97
Souls14.2K6.05
Souls15.3K6.13
Souls16.5K6.21
Souls17.8K6.29
Souls19.3K6.37
Souls21.0K6.45
Souls22.9K6.53
Souls25.0K6.61
Souls27.3K6.69
Souls29.8K6.77
Souls32.5K6.85
Souls35.4K6.93
Souls38.5K7.01
Souls41.8K7.09
Souls45.3K7.17
Souls49.0K7.25
Kelvin18.60+0.4222.7826.9633.23
Show All
Souls0.6K19.02
Souls0.9K19.44
Souls1.3K19.85
Souls1.8K20.27
Souls2.4K20.69
Souls3.0K21.11
Souls3.6K21.53
Souls4.3K21.94
Souls5.0K22.36
Souls5.7K22.78
Souls6.4K23.20
Souls7.2K23.62
Souls8.0K24.03
Souls8.8K24.45
Souls9.6K24.87
Souls10.5K25.29
Souls11.4K25.71
Souls12.3K26.12
Souls13.2K26.54
Souls14.2K26.96
Souls15.3K27.38
Souls16.5K27.80
Souls17.8K28.21
Souls19.3K28.63
Souls21.0K29.05
Souls22.9K29.47
Souls25.0K29.89
Souls27.3K30.30
Souls29.8K30.72
Souls32.5K31.14
Souls35.4K31.56
Souls38.5K31.98
Souls41.8K32.39
Souls45.3K32.81
Souls49.0K33.23
Lady Geist20.70+1.0030.7040.7055.70
Show All
Souls0.6K21.70
Souls0.9K22.70
Souls1.3K23.70
Souls1.8K24.70
Souls2.4K25.70
Souls3.0K26.70
Souls3.6K27.70
Souls4.3K28.70
Souls5.0K29.70
Souls5.7K30.70
Souls6.4K31.70
Souls7.2K32.70
Souls8.0K33.70
Souls8.8K34.70
Souls9.6K35.70
Souls10.5K36.70
Souls11.4K37.70
Souls12.3K38.70
Souls13.2K39.70
Souls14.2K40.70
Souls15.3K41.70
Souls16.5K42.70
Souls17.8K43.70
Souls19.3K44.70
Souls21.0K45.70
Souls22.9K46.70
Souls25.0K47.70
Souls27.3K48.70
Souls29.8K49.70
Souls32.5K50.70
Souls35.4K51.70
Souls38.5K52.70
Souls41.8K53.70
Souls45.3K54.70
Souls49.0K55.70
Lash (3 bullets)25.38+0.9334.6843.9857.93
Show All
Souls0.6K26.31
Souls0.9K27.24
Souls1.3K28.17
Souls1.8K29.10
Souls2.4K30.03
Souls3.0K30.96
Souls3.6K31.89
Souls4.3K32.82
Souls5.0K33.75
Souls5.7K34.68
Souls6.4K35.61
Souls7.2K36.54
Souls8.0K37.47
Souls8.8K38.40
Souls9.6K39.33
Souls10.5K40.26
Souls11.4K41.19
Souls12.3K42.12
Souls13.2K43.05
Souls14.2K43.98
Souls15.3K44.91
Souls16.5K45.84
Souls17.8K46.77
Souls19.3K47.70
Souls21.0K48.63
Souls22.9K49.56
Souls25.0K50.49
Souls27.3K51.42
Souls29.8K52.35
Souls32.5K53.28
Souls35.4K54.21
Souls38.5K55.14
Souls41.8K56.07
Souls45.3K57.00
Souls49.0K57.93
McGinnis6.08+0.177.799.5012.07
Show All
Souls0.6K6.25
Souls0.9K6.42
Souls1.3K6.59
Souls1.8K6.76
Souls2.4K6.94
Souls3.0K7.11
Souls3.6K7.28
Souls4.3K7.45
Souls5.0K7.62
Souls5.7K7.79
Souls6.4K7.96
Souls7.2K8.13
Souls8.0K8.30
Souls8.8K8.47
Souls9.6K8.64
Souls10.5K8.82
Souls11.4K8.99
Souls12.3K9.16
Souls13.2K9.33
Souls14.2K9.50
Souls15.3K9.67
Souls16.5K9.84
Souls17.8K10.01
Souls19.3K10.18
Souls21.0K10.36
Souls22.9K10.53
Souls25.0K10.70
Souls27.3K10.87
Souls29.8K11.04
Souls32.5K11.21
Souls35.4K11.38
Souls38.5K11.55
Souls41.8K11.72
Souls45.3K11.89
Souls49.0K12.07
Mina7.31+0.2810.0612.8116.93
Show All
Souls0.6K7.58
Souls0.9K7.86
Souls1.3K8.13
Souls1.8K8.41
Souls2.4K8.68
Souls3.0K8.96
Souls3.6K9.23
Souls4.3K9.51
Souls5.0K9.79
Souls5.7K10.06
Souls6.4K10.34
Souls7.2K10.61
Souls8.0K10.88
Souls8.8K11.16
Souls9.6K11.43
Souls10.5K11.71
Souls11.4K11.98
Souls12.3K12.26
Souls13.2K12.54
Souls14.2K12.81
Souls15.3K13.09
Souls16.5K13.36
Souls17.8K13.63
Souls19.3K13.91
Souls21.0K14.19
Souls22.9K14.46
Souls25.0K14.73
Souls27.3K15.01
Souls29.8K15.29
Souls32.5K15.56
Souls35.4K15.84
Souls38.5K16.11
Souls41.8K16.39
Souls45.3K16.66
Souls49.0K16.93
Mirage14.80+0.3017.8020.8025.30
Show All
Souls0.6K15.10
Souls0.9K15.40
Souls1.3K15.70
Souls1.8K16.00
Souls2.4K16.30
Souls3.0K16.60
Souls3.6K16.90
Souls4.3K17.20
Souls5.0K17.50
Souls5.7K17.80
Souls6.4K18.10
Souls7.2K18.40
Souls8.0K18.70
Souls8.8K19.00
Souls9.6K19.30
Souls10.5K19.60
Souls11.4K19.90
Souls12.3K20.20
Souls13.2K20.50
Souls14.2K20.80
Souls15.3K21.10
Souls16.5K21.40
Souls17.8K21.70
Souls19.3K22.00
Souls21.0K22.30
Souls22.9K22.60
Souls25.0K22.90
Souls27.3K23.20
Souls29.8K23.50
Souls32.5K23.80
Souls35.4K24.10
Souls38.5K24.40
Souls41.8K24.70
Souls45.3K25.00
Souls49.0K25.30
Mo & Krill (4 pellets)11.28+0.3414.6718.0623.14
Show All
Souls0.6K11.62
Souls0.9K11.96
Souls1.3K12.30
Souls1.8K12.64
Souls2.4K12.97
Souls3.0K13.31
Souls3.6K13.65
Souls4.3K13.99
Souls5.0K14.33
Souls5.7K14.67
Souls6.4K15.01
Souls7.2K15.35
Souls8.0K15.68
Souls8.8K16.02
Souls9.6K16.36
Souls10.5K16.70
Souls11.4K17.04
Souls12.3K17.38
Souls13.2K17.72
Souls14.2K18.06
Souls15.3K18.39
Souls16.5K18.73
Souls17.8K19.07
Souls19.3K19.41
Souls21.0K19.75
Souls22.9K20.09
Souls25.0K20.43
Souls27.3K20.77
Souls29.8K21.11
Souls32.5K21.44
Souls35.4K21.78
Souls38.5K22.12
Souls41.8K22.46
Souls45.3K22.80
Souls49.0K23.14
Paige35.00+0.5240.2045.4053.20
Show All
Souls0.6K35.52
Souls0.9K36.04
Souls1.3K36.56
Souls1.8K37.08
Souls2.4K37.60
Souls3.0K38.12
Souls3.6K38.64
Souls4.3K39.16
Souls5.0K39.68
Souls5.7K40.20
Souls6.4K40.72
Souls7.2K41.24
Souls8.0K41.76
Souls8.8K42.28
Souls9.6K42.80
Souls10.5K43.32
Souls11.4K43.84
Souls12.3K44.36
Souls13.2K44.88
Souls14.2K45.40
Souls15.3K45.92
Souls16.5K46.44
Souls17.8K46.96
Souls19.3K47.48
Souls21.0K48.00
Souls22.9K48.52
Souls25.0K49.04
Souls27.3K49.56
Souls29.8K50.08
Souls32.5K50.60
Souls35.4K51.12
Souls38.5K51.64
Souls41.8K52.16
Souls45.3K52.68
Souls49.0K53.20
Paradox (5 bullets)34.00+1.3047.0060.0079.50
Show All
Souls0.6K35.30
Souls0.9K36.60
Souls1.3K37.90
Souls1.8K39.20
Souls2.4K40.50
Souls3.0K41.80
Souls3.6K43.10
Souls4.3K44.40
Souls5.0K45.70
Souls5.7K47.00
Souls6.4K48.30
Souls7.2K49.60
Souls8.0K50.90
Souls8.8K52.20
Souls9.6K53.50
Souls10.5K54.80
Souls11.4K56.10
Souls12.3K57.40
Souls13.2K58.70
Souls14.2K60.00
Souls15.3K61.30
Souls16.5K62.60
Souls17.8K63.90
Souls19.3K65.20
Souls21.0K66.50
Souls22.9K67.80
Souls25.0K69.10
Souls27.3K70.40
Souls29.8K71.70
Souls32.5K73.00
Souls35.4K74.30
Souls38.5K75.60
Souls41.8K76.90
Souls45.3K78.20
Souls49.0K79.50
Pocket (7 pellets)29.96+0.9839.7649.5664.26
Show All
Souls0.6K30.94
Souls0.9K31.92
Souls1.3K32.90
Souls1.8K33.88
Souls2.4K34.86
Souls3.0K35.84
Souls3.6K36.82
Souls4.3K37.80
Souls5.0K38.78
Souls5.7K39.76
Souls6.4K40.74
Souls7.2K41.72
Souls8.0K42.70
Souls8.8K43.68
Souls9.6K44.66
Souls10.5K45.64
Souls11.4K46.62
Souls12.3K47.60
Souls13.2K48.58
Souls14.2K49.56
Souls15.3K50.54
Souls16.5K51.52
Souls17.8K52.50
Souls19.3K53.48
Souls21.0K54.46
Souls22.9K55.44
Souls25.0K56.42
Souls27.3K57.40
Souls29.8K58.38
Souls32.5K59.36
Souls35.4K60.34
Souls38.5K61.32
Souls41.8K62.30
Souls45.3K63.28
Souls49.0K64.26
Rem16.00+0.3419.4022.8027.90
Show All
Souls0.6K16.34
Souls0.9K16.68
Souls1.3K17.02
Souls1.8K17.36
Souls2.4K17.70
Souls3.0K18.04
Souls3.6K18.38
Souls4.3K18.72
Souls5.0K19.06
Souls5.7K19.40
Souls6.4K19.74
Souls7.2K20.08
Souls8.0K20.42
Souls8.8K20.76
Souls9.6K21.10
Souls10.5K21.44
Souls11.4K21.78
Souls12.3K22.12
Souls13.2K22.46
Souls14.2K22.80
Souls15.3K23.14
Souls16.5K23.48
Souls17.8K23.82
Souls19.3K24.16
Souls21.0K24.50
Souls22.9K24.84
Souls25.0K25.18
Souls27.3K25.52
Souls29.8K25.86
Souls32.5K26.20
Souls35.4K26.54
Souls38.5K26.88
Souls41.8K27.22
Souls45.3K27.56
Souls49.0K27.90
Seven (3 bullets)32.40+0.7239.6046.8057.60
Show All
Souls0.6K33.12
Souls0.9K33.84
Souls1.3K34.56
Souls1.8K35.28
Souls2.4K36.00
Souls3.0K36.72
Souls3.6K37.44
Souls4.3K38.16
Souls5.0K38.88
Souls5.7K39.60
Souls6.4K40.32
Souls7.2K41.04
Souls8.0K41.76
Souls8.8K42.48
Souls9.6K43.20
Souls10.5K43.92
Souls11.4K44.64
Souls12.3K45.36
Souls13.2K46.08
Souls14.2K46.80
Souls15.3K47.52
Souls16.5K48.24
Souls17.8K48.96
Souls19.3K49.68
Souls21.0K50.40
Souls22.9K51.12
Souls25.0K51.84
Souls27.3K52.56
Souls29.8K53.28
Souls32.5K54.00
Souls35.4K54.72
Souls38.5K55.44
Souls41.8K56.16
Souls45.3K56.88
Souls49.0K57.60
Shiv (6 pellets)28.80+0.9938.7048.6063.45
Show All
Souls0.6K29.79
Souls0.9K30.78
Souls1.3K31.77
Souls1.8K32.76
Souls2.4K33.75
Souls3.0K34.74
Souls3.6K35.73
Souls4.3K36.72
Souls5.0K37.71
Souls5.7K38.70
Souls6.4K39.69
Souls7.2K40.68
Souls8.0K41.67
Souls8.8K42.66
Souls9.6K43.65
Souls10.5K44.64
Souls11.4K45.63
Souls12.3K46.62
Souls13.2K47.61
Souls14.2K48.60
Souls15.3K49.59
Souls16.5K50.58
Souls17.8K51.57
Souls19.3K52.56
Souls21.0K53.55
Souls22.9K54.54
Souls25.0K55.53
Souls27.3K56.52
Souls29.8K57.51
Souls32.5K58.50
Souls35.4K59.49
Souls38.5K60.48
Souls41.8K61.47
Souls45.3K62.46
Souls49.0K63.45
Shiv (alt fire 12 pellets)52.32+2.4076.32100.32136.32
Show All
Souls0.6K54.72
Souls0.9K57.12
Souls1.3K59.52
Souls1.8K61.92
Souls2.4K64.32
Souls3.0K66.72
Souls3.6K69.12
Souls4.3K71.52
Souls5.0K73.92
Souls5.7K76.32
Souls6.4K78.72
Souls7.2K81.12
Souls8.0K83.52
Souls8.8K85.92
Souls9.6K88.32
Souls10.5K90.72
Souls11.4K93.12
Souls12.3K95.52
Souls13.2K97.92
Souls14.2K100.32
Souls15.3K102.72
Souls16.5K105.12
Souls17.8K107.52
Souls19.3K109.92
Souls21.0K112.32
Souls22.9K114.72
Souls25.0K117.12
Souls27.3K119.52
Souls29.8K121.92
Souls32.5K124.32
Souls35.4K126.72
Souls38.5K129.12
Souls41.8K131.52
Souls45.3K133.92
Souls49.0K136.32
Silver (7 pellets)35.70+0.8243.8952.0864.36
Show All
Souls0.6K36.52
Souls0.9K37.34
Souls1.3K38.16
Souls1.8K38.98
Souls2.4K39.79
Souls3.0K40.61
Souls3.6K41.43
Souls4.3K42.25
Souls5.0K43.07
Souls5.7K43.89
Souls6.4K44.71
Souls7.2K45.53
Souls8.0K46.35
Souls8.8K47.17
Souls9.6K47.98
Souls10.5K48.80
Souls11.4K49.62
Souls12.3K50.44
Souls13.2K51.26
Souls14.2K52.08
Souls15.3K52.90
Souls16.5K53.72
Souls17.8K54.54
Souls19.3K55.36
Souls21.0K56.17
Souls22.9K56.99
Souls25.0K57.81
Souls27.3K58.63
Souls29.8K59.45
Souls32.5K60.27
Souls35.4K61.09
Souls38.5K61.91
Souls41.8K62.73
Souls45.3K63.55
Souls49.0K64.36
Silver (Transformed)25.00+0.7732.7340.4752.07
Show All
Souls0.6K25.77
Souls0.9K26.55
Souls1.3K27.32
Souls1.8K28.09
Souls2.4K28.87
Souls3.0K29.64
Souls3.6K30.41
Souls4.3K31.19
Souls5.0K31.96
Souls5.7K32.73
Souls6.4K33.51
Souls7.2K34.28
Souls8.0K35.06
Souls8.8K35.83
Souls9.6K36.60
Souls10.5K37.38
Souls11.4K38.15
Souls12.3K38.92
Souls13.2K39.70
Souls14.2K40.47
Souls15.3K41.24
Souls16.5K42.02
Souls17.8K42.79
Souls19.3K43.56
Souls21.0K44.34
Souls22.9K45.11
Souls25.0K45.88
Souls27.3K46.66
Souls29.8K47.43
Souls32.5K48.20
Souls35.4K48.98
Souls38.5K49.75
Souls41.8K50.53
Souls45.3K51.30
Souls49.0K52.07
Sinclair (2 bullets)35.52+1.1046.5257.5274.02
Show All
Souls0.6K36.62
Souls0.9K37.72
Souls1.3K38.82
Souls1.8K39.92
Souls2.4K41.02
Souls3.0K42.12
Souls3.6K43.22
Souls4.3K44.32
Souls5.0K45.42
Souls5.7K46.52
Souls6.4K47.62
Souls7.2K48.72
Souls8.0K49.82
Souls8.8K50.92
Souls9.6K52.02
Souls10.5K53.12
Souls11.4K54.22
Souls12.3K55.32
Souls13.2K56.42
Souls14.2K57.52
Souls15.3K58.62
Souls16.5K59.72
Souls17.8K60.82
Souls19.3K61.92
Souls21.0K63.02
Souls22.9K64.12
Souls25.0K65.22
Souls27.3K66.32
Souls29.8K67.42
Souls32.5K68.52
Souls35.4K69.62
Souls38.5K70.72
Souls41.8K71.82
Souls45.3K72.92
Souls49.0K74.02
Venator8.00+0.2710.7013.4017.45
Show All
Souls0.6K8.27
Souls0.9K8.54
Souls1.3K8.81
Souls1.8K9.08
Souls2.4K9.35
Souls3.0K9.62
Souls3.6K9.89
Souls4.3K10.16
Souls5.0K10.43
Souls5.7K10.70
Souls6.4K10.97
Souls7.2K11.24
Souls8.0K11.51
Souls8.8K11.78
Souls9.6K12.05
Souls10.5K12.32
Souls11.4K12.59
Souls12.3K12.86
Souls13.2K13.13
Souls14.2K13.40
Souls15.3K13.67
Souls16.5K13.94
Souls17.8K14.21
Souls19.3K14.48
Souls21.0K14.75
Souls22.9K15.02
Souls25.0K15.29
Souls27.3K15.56
Souls29.8K15.83
Souls32.5K16.10
Souls35.4K16.37
Souls38.5K16.64
Souls41.8K16.91
Souls45.3K17.18
Souls49.0K17.45
Victor12.00+0.2614.6017.2021.10
Show All
Souls0.6K12.26
Souls0.9K12.52
Souls1.3K12.78
Souls1.8K13.04
Souls2.4K13.30
Souls3.0K13.56
Souls3.6K13.82
Souls4.3K14.08
Souls5.0K14.34
Souls5.7K14.60
Souls6.4K14.86
Souls7.2K15.12
Souls8.0K15.38
Souls8.8K15.64
Souls9.6K15.90
Souls10.5K16.16
Souls11.4K16.42
Souls12.3K16.68
Souls13.2K16.94
Souls14.2K17.20
Souls15.3K17.46
Souls16.5K17.72
Souls17.8K17.98
Souls19.3K18.24
Souls21.0K18.50
Souls22.9K18.76
Souls25.0K19.02
Souls27.3K19.28
Souls29.8K19.54
Souls32.5K19.80
Souls35.4K20.06
Souls38.5K20.32
Souls41.8K20.58
Souls45.3K20.84
Souls49.0K21.10
Vindicta12.33+0.4917.2822.2329.66
Show All
Souls0.6K12.82
Souls0.9K13.32
Souls1.3K13.81
Souls1.8K14.31
Souls2.4K14.80
Souls3.0K15.30
Souls3.6K15.79
Souls4.3K16.29
Souls5.0K16.79
Souls5.7K17.28
Souls6.4K17.77
Souls7.2K18.27
Souls8.0K18.77
Souls8.8K19.26
Souls9.6K19.75
Souls10.5K20.25
Souls11.4K20.74
Souls12.3K21.24
Souls13.2K21.73
Souls14.2K22.23
Souls15.3K22.73
Souls16.5K23.22
Souls17.8K23.71
Souls19.3K24.21
Souls21.0K24.70
Souls22.9K25.20
Souls25.0K25.70
Souls27.3K26.19
Souls29.8K26.69
Souls32.5K27.18
Souls35.4K27.68
Souls38.5K28.17
Souls41.8K28.66
Souls45.3K29.16
Souls49.0K29.66
Viscous10.34+0.3613.9417.5422.94
Show All
Souls0.6K10.70
Souls0.9K11.06
Souls1.3K11.42
Souls1.8K11.78
Souls2.4K12.14
Souls3.0K12.50
Souls3.6K12.86
Souls4.3K13.22
Souls5.0K13.58
Souls5.7K13.94
Souls6.4K14.30
Souls7.2K14.66
Souls8.0K15.02
Souls8.8K15.38
Souls9.6K15.74
Souls10.5K16.10
Souls11.4K16.46
Souls12.3K16.82
Souls13.2K17.18
Souls14.2K17.54
Souls15.3K17.90
Souls16.5K18.26
Souls17.8K18.62
Souls19.3K18.98
Souls21.0K19.34
Souls22.9K19.70
Souls25.0K20.06
Souls27.3K20.42
Souls29.8K20.78
Souls32.5K21.14
Souls35.4K21.50
Souls38.5K21.86
Souls41.8K22.22
Souls45.3K22.58
Souls49.0K22.94
Viscous (alt fire AOE)42.00+1.6058.0074.0098.00
Show All
Souls0.6K43.60
Souls0.9K45.20
Souls1.3K46.80
Souls1.8K48.40
Souls2.4K50.00
Souls3.0K51.60
Souls3.6K53.20
Souls4.3K54.80
Souls5.0K56.40
Souls5.7K58.00
Souls6.4K59.60
Souls7.2K61.20
Souls8.0K62.80
Souls8.8K64.40
Souls9.6K66.00
Souls10.5K67.60
Souls11.4K69.20
Souls12.3K70.80
Souls13.2K72.40
Souls14.2K74.00
Souls15.3K75.60
Souls16.5K77.20
Souls17.8K78.80
Souls19.3K80.40
Souls21.0K82.00
Souls22.9K83.60
Souls25.0K85.20
Souls27.3K86.80
Souls29.8K88.40
Souls32.5K90.00
Souls35.4K91.60
Souls38.5K93.20
Souls41.8K94.80
Souls45.3K96.40
Souls49.0K98.00
Vyper6.58+0.188.3610.1412.81
Show All
Souls0.6K6.76
Souls0.9K6.94
Souls1.3K7.11
Souls1.8K7.29
Souls2.4K7.47
Souls3.0K7.65
Souls3.6K7.83
Souls4.3K8.00
Souls5.0K8.18
Souls5.7K8.36
Souls6.4K8.54
Souls7.2K8.72
Souls8.0K8.89
Souls8.8K9.07
Souls9.6K9.25
Souls10.5K9.43
Souls11.4K9.61
Souls12.3K9.78
Souls13.2K9.96
Souls14.2K10.14
Souls15.3K10.32
Souls16.5K10.50
Souls17.8K10.67
Souls19.3K10.85
Souls21.0K11.03
Souls22.9K11.21
Souls25.0K11.39
Souls27.3K11.56
Souls29.8K11.74
Souls32.5K11.92
Souls35.4K12.10
Souls38.5K12.28
Souls41.8K12.45
Souls45.3K12.63
Souls49.0K12.81
Warden17.34+0.2820.1422.9427.14
Show All
Souls0.6K17.62
Souls0.9K17.90
Souls1.3K18.18
Souls1.8K18.46
Souls2.4K18.74
Souls3.0K19.02
Souls3.6K19.30
Souls4.3K19.58
Souls5.0K19.86
Souls5.7K20.14
Souls6.4K20.42
Souls7.2K20.70
Souls8.0K20.98
Souls8.8K21.26
Souls9.6K21.54
Souls10.5K21.82
Souls11.4K22.10
Souls12.3K22.38
Souls13.2K22.66
Souls14.2K22.94
Souls15.3K23.22
Souls16.5K23.50
Souls17.8K23.78
Souls19.3K24.06
Souls21.0K24.34
Souls22.9K24.62
Souls25.0K24.90
Souls27.3K25.18
Souls29.8K25.46
Souls32.5K25.74
Souls35.4K26.02
Souls38.5K26.30
Souls41.8K26.58
Souls45.3K26.86
Souls49.0K27.14
Wraith5.64+0.147.048.4410.54
Show All
Souls0.6K5.78
Souls0.9K5.92
Souls1.3K6.06
Souls1.8K6.20
Souls2.4K6.34
Souls3.0K6.48
Souls3.6K6.62
Souls4.3K6.76
Souls5.0K6.90
Souls5.7K7.04
Souls6.4K7.18
Souls7.2K7.32
Souls8.0K7.46
Souls8.8K7.60
Souls9.6K7.74
Souls10.5K7.88
Souls11.4K8.02
Souls12.3K8.16
Souls13.2K8.30
Souls14.2K8.44
Souls15.3K8.58
Souls16.5K8.72
Souls17.8K8.86
Souls19.3K9.00
Souls21.0K9.14
Souls22.9K9.28
Souls25.0K9.42
Souls27.3K9.56
Souls29.8K9.70
Souls32.5K9.84
Souls35.4K9.98
Souls38.5K10.12
Souls41.8K10.26
Souls45.3K10.40
Souls49.0K10.54
Yamato (5 pellets)26.55+0.7734.2541.9553.50
Show All
Souls0.6K27.32
Souls0.9K28.09
Souls1.3K28.86
Souls1.8K29.63
Souls2.4K30.40
Souls3.0K31.17
Souls3.6K31.94
Souls4.3K32.71
Souls5.0K33.48
Souls5.7K34.25
Souls6.4K35.02
Souls7.2K35.79
Souls8.0K36.56
Souls8.8K37.33
Souls9.6K38.10
Souls10.5K38.87
Souls11.4K39.64
Souls12.3K40.41
Souls13.2K41.18
Souls14.2K41.95
Souls15.3K42.72
Souls16.5K43.49
Souls17.8K44.26
Souls19.3K45.03
Souls21.0K45.80
Souls22.9K46.57
Souls25.0K47.34
Souls27.3K48.11
Souls29.8K48.88
Souls32.5K49.65
Souls35.4K50.42
Souls38.5K51.19
Souls41.8K51.96
Souls45.3K52.73
Souls49.0K53.50
Yamato (alt fire AOE)50.00+0.7057.0064.0074.50
Show All
Souls0.6K50.70
Souls0.9K51.40
Souls1.3K52.10
Souls1.8K52.80
Souls2.4K53.50
Souls3.0K54.20
Souls3.6K54.90
Souls4.3K55.60
Souls5.0K56.30
Souls5.7K57.00
Souls6.4K57.70
Souls7.2K58.40
Souls8.0K59.10
Souls8.8K59.80
Souls9.6K60.50
Souls10.5K61.20
Souls11.4K61.90
Souls12.3K62.60
Souls13.2K63.30
Souls14.2K64.00
Souls15.3K64.70
Souls16.5K65.40
Souls17.8K66.10
Souls19.3K66.80
Souls21.0K67.50
Souls22.9K68.20
Souls25.0K68.90
Souls27.3K69.60
Souls29.8K70.30
Souls32.5K71.00
Souls35.4K71.70
Souls38.5K72.40
Souls41.8K73.10
Souls45.3K73.80
Souls49.0K74.50

This module also has a function to write a list of heroes with multi-shot weapons, how many shots their weapon has, how much base damage a shot does, and how much damage a shot increases by per boon.

The list is automatically sorted alphabetically (ignoring "The" for names like The Doorman) and provides accurate descriptions for special cases, such as heroes whose multi-pellet attacks only deal damage once.

This list can be invoked with:
{{#invoke:LevelTables|write_multishot_stats}}

This is what the generated list looks like:

Weapons with multiple projectiles (Spreadshot):

  • Abrams: 9 pellets, 3.6/pellet, +0.1/pellet
  • Calico: 9 pellets, 1.84/pellet, +0.04/pellet
  • Drifter: 3 pellets, 19.5 total damage (hits only once), +0.49 total
  • Mo & Krill: 4 pellets, 2.82/pellet, +0.08/pellet
  • Pocket: 7 pellets, 4.28/pellet, +0.14/pellet
  • Shiv: 6 pellets, 4.8/pellet, +0.16/pellet
  • Silver: 7 pellets, 5.09/pellet, +0.11/pellet
  • Yamato: 5 pellets, 5.31/pellet, +0.15/pellet

Weapons with burst fire (Burst Fire):

  • Lash: 3 rounds, 8.46/bullet, +0.31/bullet
  • Paradox: 5 rounds, 6.8/bullet, +0.26/bullet
  • Seven: 3 rounds, 10.8/bullet, +0.24/bullet
  • Sinclair: 2 rounds, 17.76/bullet, +0.55/bullet

local p = {};

local Icon = require('Module:Icon')
local Souls = require('Module:Souls')

-- Loads the necessary data from the wiki's JSON pages.
local heroes_data = mw.loadJsonData("Data:HeroData.json")
local soul_unlocks_data = mw.loadJsonData("Data:SoulUnlockData.json")

-- Helper to safely get stats that may be nested under Weapon
local function get_stat_value(data, key)
    if data[key] ~= nil then return data[key] end
    if data.Weapon and data.Weapon[key] ~= nil then return data.Weapon[key] end
    return 0
end

-- Defines human-readable names for the internal property keys used in headers.
local level_scaling_key_to_name = {
	BulletDamage = "Bullet Damage",
	MaxHealth = "Health",
	TechPower = "Spirit Power",
	LightMeleeDamage = "Light Melee Damage",
	HeavyMeleeDamage = "Heavy Melee Damage"
}

-- A helper function that returns a sort-friendly version of a name by removing leading articles like "The ".
local function get_sortable_name(name)
	if string.sub(name, 1, 4) == "The " then
		return string.sub(name, 5)
	end
	return name
end

--[[
-- @function p.write_power_increase_table
-- @description Generates a responsive and readable table showing hero stat scaling based on boons.
--              Includes a compact summary view and an expandable, detailed view for all levels.
--              Can optionally wrap the entire output in a theme-aware collapsible section.
-- @param {Frame} frame The MediaWiki frame object.
-- @param {string} frame.args[1] The internal property name to display (e.g., 'BulletDamage').
-- @param {string} [frame.args.title] An optional title. If provided, the table is wrapped in a collapsible section.
-- @return {string} The complete HTML for the table or collapsible section.
--]]
p.write_power_increase_table = function(frame)
    -- Use the same smart sorting for this list.
	local function hero_sort(h1, h2) 
		return get_sortable_name(h1.Name) < get_sortable_name(h2.Name)
	end
	-- Read and validate the arguments passed from the #invoke call.
	local property = frame.args[1]
	local title = frame.args.title

	if not property or property == '' then
		return '<strong class="error">Error: Missing required property argument in #invoke call. Example: {{#invoke:ModuleName|write_power_increase_table|BulletDamage}}</strong>'
	end

	local name = level_scaling_key_to_name[property]

	if not name then
		 return '<strong class="error">Error: Invalid property argument "' .. tostring(property) .. '". Must be one of: BulletDamage, MaxHealth, TechPower, LightMeleeDamage, HeavyMeleeDamage.</strong>'
	end
	
	-- Create an ordered list of soul requirements for each power increase.
	local pi_souls_list = {}
	for _, v in ipairs(soul_unlocks_data) do
		if (v["PowerIncrease"]) then
			table.insert(pi_souls_list, v["RequiredSouls"])
		end
	end
	
	local max_boons = #pi_souls_list

	-- Define the milestones to display in the summary table.
	local milestones = {10, 20, max_boons}
	
	-- Filter out disabled heroes from the data.
	local filtered_heroes = {}
	for _, hero in pairs(heroes_data) do
		if (not hero["IsDisabled"] and not hero["InDevelopment"] and hero["Name"]) then
			table.insert(filtered_heroes, hero)
		end
	end
	
	-- Sort the hero list alphabetically, ignoring "The " for proper sorting.
	table.sort(filtered_heroes, hero_sort)
	
	-- Begin building the main data table.
	local create_table = mw.html.create("table"):addClass("wikitable sortable")
	
	-- Create the header row for the summary table.
	local header_row = mw.html.create("tr")
	header_row
		:tag("th"):wikitext("Hero"):done()
		:tag("th"):wikitext("Base " .. name):done()
		:tag("th"):wikitext("Increase per Boon"):done()

	-- Dynamically create milestone headers with both boon count and soul amount.
	for _, count in ipairs(milestones) do
		local header_text = count .. " Boons"
		if count == max_boons then header_text = header_text .. " (Max)" end
		local required_souls = pi_souls_list[count]
		local souls_formatted = string.format("%.1fK", (required_souls + 400) / 1000)
		local soul_display =  Souls._render(souls_formatted)
		local full_header = header_text .. "<br /><small>(" .. soul_display .. ")</small>"
		header_row:tag("th"):wikitext(full_header):done()
	end
	
	header_row:tag("th"):wikitext("Full Details"):done()
	create_table:node(header_row)
	
	-- A helper function that generates the HTML for a single hero's row and its collapsible details.
	local function addHeroRows(hero_data, is_alt_fire)
		local base, level, suffix
		local hero_icon_wikitext = Icon._render(hero_data['Name'])
		
		-- Determine if this is a main weapon or an alt-fire and set the correct data.
		if is_alt_fire then
			base = hero_data.alt_fire.base
			level = hero_data.alt_fire.level
			suffix = " (" .. hero_data.alt_fire.suffix .. ")"
		else
			base = get_stat_value(hero_data, property)
			level = (hero_data["LevelScaling"] and hero_data["LevelScaling"][property] or 0)
			suffix = ""
			
			local shots = get_stat_value(hero_data, "BulletsPerShot")
			if shots == 0 then shots = 1 end
			local burst = get_stat_value(hero_data, "BulletsPerBurst")
			if burst == 0 then burst = 1 end
			local hit_once = get_stat_value(hero_data, "HitOnceAcrossAllBullets")
			
			if (property == "BulletDamage" and (shots * burst) > 1) then
				if (not hit_once) then
					base = base * shots * burst
					level = level * shots * burst
				end
				if (shots > 1) then suffix = suffix .. " (" .. shots .. " pellets)" end
				if (burst > 1) then suffix = suffix .. " (" .. burst .. " bullets)" end
			end
		end

		-- Build the main summary row with the hero's info and milestone values.
		local summary_row = mw.html.create("tr")
		
		local hero_cell_content = hero_icon_wikitext .. suffix
		
		summary_row:tag("td"):wikitext(hero_cell_content):done()
		summary_row
			:tag("td"):wikitext(string.format("%.2f", base)):done()
			:tag("td"):wikitext("+" .. string.format("%.2f", level)):done()
		
		for _, count in ipairs(milestones) do
			local total = base + level * count
			summary_row:tag("td"):wikitext(string.format("%.2f", total)):done()
		end

		-- Create the main container for the collapsible "Full Details" section.
		local details_id = "boon-details-" .. hero_data.Name .. "-" .. property .. (is_alt_fire and "-alt" or "")
		local collapsible_container = mw.html.create("div")
			:addClass("mw-collapsible mw-collapsed")
			:attr("id", details_id)
			:attr("style", "display: flex; flex-direction: column; align-items: flex-end; gap: 5px;")
		
		-- Style the "Show All" toggle to look like a theme-aware button.
		collapsible_container:tag("div")
		    :addClass("mw-collapsible-toggle")
		    :attr("style", "display: inline-block; padding: 2px 8px; border: 1px solid currentColor; border-radius: 1px; text-decoration: none;")
		    :wikitext("Show All")
			
		-- Create the container for the expanded content.
		local content_div = mw.html.create("div"):addClass("mw-collapsible-content")
		
		-- Generate the three-column layout by distributing rows into mini-tables.
		local column1_rows, column2_rows, column3_rows = {}, {}, {}
		local rows_per_column = math.ceil(max_boons / 3)
		
		for i = 1, max_boons do
			local total = base + level * i
			local required_souls = pi_souls_list[i]
			local souls_formatted = string.format("%.1fK", (required_souls + 400) / 1000)
			local soul_display = Souls._render(souls_formatted)
			
			local row_html = string.format("<tr><td>%s</td><td style='text-align:right;'>%.2f</td></tr>", soul_display, total)
			
			if i <= rows_per_column then
				table.insert(column1_rows, row_html)
			elseif i <= rows_per_column * 2 then
				table.insert(column2_rows, row_html)
			else
				table.insert(column3_rows, row_html)
			end
		end
		
		local table_style = "border:none; background:transparent; margin:0 10px; min-width:150px;"
		local table1 = "<table style='" .. table_style .. "'>" .. table.concat(column1_rows) .. "</table>"
		local table2 = "<table style='" .. table_style .. "'>" .. table.concat(column2_rows) .. "</table>"
		local table3 = "<table style='" .. table_style .. "'>" .. table.concat(column3_rows) .. "</table>"
		
		-- Use a flexbox container to display the mini-tables side-by-side.
		local flex_container_style = "display:flex; justify-content:space-around; padding:10px 0;"
		content_div:wikitext("<div style='" .. flex_container_style .. "'>" .. table1 .. table2 .. table3 .. "</div>")
		
		collapsible_container:node(content_div)
		summary_row:tag("td"):node(collapsible_container)
		create_table:node(summary_row)
	end
	
	-- Iterate through each hero to generate their main row and an alt-fire row if applicable.
	for _, hero in ipairs(filtered_heroes) do
		addHeroRows(hero, false)
		
		-- Dynamically add alt-fire rows if the data exists
		if (property == "BulletDamage" and hero.Weapon and hero.Weapon.AltFire) then
		    local alt_fire_data = hero.Weapon.AltFire
		    local alt_fire_base = alt_fire_data.BulletDamage or 0
		    
		    if alt_fire_base > 0 then
		        -- Get scaling: prefer alt-fire specific key, fall back to main weapon scaling
		        local alt_fire_level = (hero.LevelScaling and 
		            (hero.LevelScaling.BulletDamageAltFire or hero.LevelScaling[property])) or 0
		        
		        -- Apply same pellet/burst logic as main weapon
		        local shots = alt_fire_data.BulletsPerShot or 1
		        local burst = alt_fire_data.BulletsPerBurst or 1
		        local hit_once = alt_fire_data.HitOnceAcrossAllBullets or false
		        
		        local suffix = "alt fire"
		        
		        if (shots * burst) > 1 and not hit_once then
		            alt_fire_base = alt_fire_base * shots * burst
		            alt_fire_level = alt_fire_level * shots * burst
		            if shots > 1 then suffix = suffix .. " " .. shots .. " pellets" end
		            if burst > 1 then suffix = suffix .. " " .. burst .. " bullets" end
		        end
		        
		        -- Add AOE indicator (without radius)
		        if alt_fire_data.ExplosionRadius then
		            suffix = suffix .. " AOE"
		        end
		        
		        local alt_fire_hero_data = {
		            Name = hero.Name,
		            alt_fire = {
		                base = alt_fire_base,
		                level = alt_fire_level,
		                suffix = suffix
		            }
		        }
		        addHeroRows(alt_fire_hero_data, true)
		    end
		end
	end
	
	-- If a title was provided, wrap the completed table in a theme-aware collapsible container.
	if title and title ~= '' then
		local wrapper = mw.html.create('table')
			:addClass('mw-collapsible mw-collapsed wikitable')
			:css('width', '100%')
			:css('margin-bottom', '1em')

		local header_row_wrapper = wrapper:tag('tr')

		header_row_wrapper:tag('th')
			:css('text-align', 'center')
			:wikitext('<span style="font-size: larger; font-weight: bold;">' .. title .. '</span><span class="mw-collapsible-toggle mw-collapsible-toggle-right">[Show]</span>')

		local content_row_wrapper = wrapper:tag('tr')

		content_row_wrapper:tag('td')
			:addClass('mw-collapsible-content')
			:node(create_table)
			
		return tostring(wrapper)
	else
		-- If no title is provided, return the table as before.
		return tostring(create_table)
	end
end

--[[
-- @function p.write_multishot_stats
-- @description Outputs a wikitext bulleted list of heroes with spreadshot or burst fire weapons.
-- @param {Frame} frame The MediaWiki frame object.
-- @return {string} A formatted wikitext string.
--]]
p.write_multishot_stats = function(frame)
	-- Use the same smart sorting for this list.
	local function hero_sort(h1, h2) 
		return get_sortable_name(h1.Name) < get_sortable_name(h2.Name)
	end
	local spreadshot_heroes = {}
	local burst_heroes = {}
	for _, hero in pairs(heroes_data) do
	    local bullets_per_shot = get_stat_value(hero, "BulletsPerShot")
	    local bullets_per_burst = get_stat_value(hero, "BulletsPerBurst")
	    
		if (bullets_per_shot > 1 and not hero["IsDisabled"] and not hero["InDevelopment"]) then
			table.insert(spreadshot_heroes, hero)
		elseif (bullets_per_burst > 1 and not hero["IsDisabled"] and not hero["InDevelopment"]) then
			table.insert(burst_heroes, hero)
		end
	end
	
	table.sort(spreadshot_heroes, hero_sort)
	table.sort(burst_heroes, hero_sort)
	
	local multishot_str = "Weapons with multiple projectiles (Spreadshot):\n"
	for _, hero in ipairs(spreadshot_heroes) do
		local base = math.floor(get_stat_value(hero, "BulletDamage") * 100) / 100
		local level = math.floor(hero["LevelScaling"]["BulletDamage"] * 100) / 100
		local shots = get_stat_value(hero, "BulletsPerShot")
		local hit_once = get_stat_value(hero, "HitOnceAcrossAllBullets")
		local hero_icon = Icon._render(hero["Name"])
		
		-- Checks for the special property and changes the output text accordingly.
		local stat_str
		if hit_once then
			stat_str = "* " .. hero_icon .. ": " .. shots .. " pellets, " 
				.. base .. " total damage (hits only once), +" .. level .. " total\n"
		else
			stat_str = "* " .. hero_icon .. ": " .. shots .. " pellets, " 
				.. base .. "/pellet, +" .. level .. "/pellet\n"
		end
		multishot_str = multishot_str .. stat_str
	end
	
	multishot_str = multishot_str .. "Weapons with burst fire (Burst Fire):\n"
	for _, hero in ipairs(burst_heroes) do
		local base = math.floor(get_stat_value(hero, "BulletDamage") * 100) / 100
		local level = math.floor(hero["LevelScaling"]["BulletDamage"] * 100) / 100
		local shots = get_stat_value(hero, "BulletsPerBurst")
		local hero_icon = Icon._render(hero["Name"])
		local stat_str = "* " .. hero_icon .. ": " .. shots .. " rounds, " 
			.. base .. "/bullet, +" .. level .. "/bullet\n"
		multishot_str = multishot_str .. stat_str
	end
	
	return multishot_str
end

return p