🌟現在、鉄壁 鉄壁ヘッドショットには対応済みです。
鉄壁HSは通常HSと同じダメージになります。LMG及びDMR、チャージライフル、ハンマーポイント弾を除き、すべてのダメージ値が一致していることを確認しています。

「モジュール:WeaponInfobox/DPS」の版間の差分

提供:Apex Data
ナビゲーションに移動 検索に移動
(rechamber時間の計算に Module:Utility/ApexLibrary の関数を使用)
(マスティフショットガンのDPS表がおかしなことになっている問題に対処)
 
(同じ利用者による、間の15版が非表示)
3行目: 3行目:
local p = {}
local p = {}
local cfg = mw.loadData('Module:WeaponInfobox/configuration')
local cfg = mw.loadData('Module:WeaponInfobox/configuration')
local Hopup = mw.loadData('Module:Stat/Hopup')


local aw = require('Module:Utility/Library')
local aw   = require('Module:Utility/Library')
local apex = require('Module:Utility/ApexLibrary')
local apex = require('Module:Utility/ApexLibrary')
local iu = require('Module:Utility/Image')
local iu   = require('Module:Utility/Image')
local proto = require('Module:Utility/Prototypes')
local proto = require('Module:Utility/Prototypes')
local table = require('Module:TableExtensions')


-- Constants
-- Constants
19行目: 21行目:
['border-left-width'] = '1px',
['border-left-width'] = '1px',
['border-left-style'] = 'solid',
['border-left-style'] = 'solid',
}
local empty = {}
local steplessChargeRates = { 0.5, 1 }
local structures = {
{
levels = 1,
root  = empty,
},
{
levels = 2,
root  = { colspan = 2 },
level2 = { offset  = 1 },
extra  = { offset  = 1 },
},
{
levels = 3,
root  = { colspan = 3 },
level2 = { colspan = 2, offset  = 1 },
level3 = { offset  = 2, styles = { nil, { leftBorder = true } } },
extra  = { offset  = 2 },
},
}
}


-- Protos
-- Protos
local MinMaxProto = {
local MultipleProto = {
proto.NumberRange(0),
proto.NumberRange(0),
}
local MultipleFirerateProto = {
proto.NumberRange(0),
proto.NumberRange(0),
proto.NumberRange(0),
proto.NumberRange(0),
32行目: 52行目:
proto.NumberRange(0),
proto.NumberRange(0),
}
}
local SelectfireReceiverProto = {
local DamageProto = {
damage = {
damage_near_value     = proto.NumberRange(1),
base     = proto.NumberRange(1),
damage_very_far_value = proto.NumberRange(1),
headshot = proto.NumberRange(1, 10),
}
legshot  = proto.NumberRange(0, 1),
local AltfireProto = {
},
altfire = {
firerate = proto.NumberRange(0),
firerate = proto.NumberRange(0),
raise   = proto.NumberRange(0),
}
}
local RevvedUpProto = {
firerate_revvedup = proto.NumberRange(1),
}
local BurstProto = {
burst_count = proto.NumberRange(2),
burst_delay = proto.NumberRange(0),
firerate   = proto.NumberRange(0),
}
local MultipleBurstProto = {
burst_count = proto.NumberRange(2),
burst_delay = MultipleProto,
firerate   = proto.NumberRange(0),
}
local HammerpointRoundsProto = {
damage_unshielded_scale = proto.NumberRange(1),
}
}
local AnvilReceiverProto = {
local DisruptorRoundsProto = {
damage = {
damage_shield_scale = proto.NumberRange(1),
base    = proto.NumberRange(1),
headshot = proto.NumberRange(1, 10),
legshot  = proto.NumberRange(0, 1),
},
firerate = proto.NumberRange(0),
}
}
local ShatterCapsProto = {
local ShatterCapsProto = {
damage = {
damage_near_value     = proto.NumberRange(1),
base     = proto.NumberRange(1),
damage_very_far_value = proto.NumberRange(1),
headshot = proto.NumberRange(1, 10),
damage_head_scale    = proto.NumberRange(1),
legshot  = proto.NumberRange(0, 1),
damage_legs_scale    = proto.NumberRange(0, 1),
},
pellet                = proto.NumberRange(2),
pellet = proto.NumberRange(1),
}
local DeadeyesTempoWithRechamberProto = {
rechamber = proto.NumberRange(0),
}
}


local function calcBurstRPS(stat, delay)
-- Get property
delay = delay or stat.burst_delay
local function getProperty(stat, stat2, key, defaultValue)
return stat.burst_count / ((stat.burst_count - 1) / stat.firerate + delay)
if aw.isNumber(stat[key]) then
return stat[key]
elseif aw.isNumber(stat2[key]) then
return stat2[key]
else
return defaultValue
end
end
 
-- Get multiple number property
local function getMultipleNumberProperty(stat, stat2, key, defaultValue)
if proto.validateTypes(stat[key], MultipleProto) then
return stat[key]
elseif proto.validateTypes(stat2[key], MultipleProto) then
return stat2[key]
else
return defaultValue
end
end
end


local function getStats(stat1, stat2)
-- Get property
local ret
local function getBooleanProperty(stat, stat2, key, defaultValue)
if stat2 then
if type(stat[key]) == 'boolean' then
local semi
return stat[key]
if stat2.is_semi_auto ~= nil then
elseif type(stat2[key]) == 'boolean' then
semi = stat2.is_semi_auto
return stat2[key]
else
semi = stat1.is_semi_auto
end
ret = {
base = stat2.damage.base    or stat1.damage.base,
head = stat2.damage.headshot or stat1.damage.headshot or 1.75,
legs = stat2.damage.legshot  or stat1.damage.legshot  or 0.75,
semi = semi,
}
else
else
ret = {
return defaultValue
base = stat1.damage.base,
head = stat1.damage.headshot or 1.75,
legs = stat1.damage.legshot  or 0.75,
semi = stat1.is_semi_auto,
}
end
end
return ret
end
end


local function getDamages(stat, helmets)
-- Calc base damages
local helmet = helmets.level0.disabled and helmets.level1 or helmets.level0
local function calcBaseDamages(stat, stat2, chargeRate)
chargeRate = chargeRate or 0
local ret
local chargeAddScale = getProperty(stat, stat2, 'charge_additional_scale', 0)
if stat.legs > 0 and stat.legs < 1 then
local nearDamage    = getProperty(stat, stat2, 'damage_near_value', 0)
ret = {
local verfarDamage  = getProperty(stat, stat2, 'damage_very_far_value', nil)    or getProperty(stat, stat2, 'damage_far_value', 0)
aw.round(stat.legs * stat.base),
local verfarDistance = getProperty(stat, stat2, 'damage_very_far_distance', nil) or getProperty(stat, stat2, 'damage_far_distance', 15748)
stat.base,
if chargeRate > 0 then
{
local mul = 1 + chargeRate * chargeAddScale
aw.round(helmets.level3.func(stat.head) * stat.base),
nearDamage  = aw.round(nearDamage  * mul)
aw.round(helmet.func(stat.head) * stat.base),
verfarDamage = aw.round(verfarDamage * mul)
},
end
}
local headDistance = getProperty(stat, stat2, 'damage_head_distance', math.huge)
local headScale    = getProperty(stat, stat2, 'damage_head_scale', 1)
local headLv1Scale = apex.calcHeadshotMultiplier(headScale, apex.HEAD_HLMLV1)
local headLv2Scale = apex.calcHeadshotMultiplier(headScale, apex.HEAD_HLMLV2)
local headLv3Scale = apex.calcHeadshotMultiplier(headScale, apex.HEAD_HLMLV3)
local legsScale    = getProperty(stat, stat2, 'damage_legs_scale', 1)
local ret = {
hed1max = apex.calcPartDamage(nearDamage, headLv1Scale),
hed2max = apex.calcPartDamage(nearDamage, headLv2Scale),
hed3max = apex.calcPartDamage(nearDamage, headLv3Scale),
bodymax = nearDamage,
legsmax = apex.calcPartDamage(nearDamage, legsScale),
}
if nearDamage == verfarDamage then
--[[if verfarDistance > headDistance then
ret.hed1min = ret.bodymax
ret.hed2min = ret.bodymax
ret.hed3min = ret.bodymax
else]]
ret.hed1min = ret.hed1max
ret.hed2min = ret.hed2max
ret.hed3min = ret.hed3max
--end
ret.bodymin = ret.bodymax
ret.legsmin = ret.legsmax
--[[elseif verfarDistance > headDistance then
ret.hed1min = verfarDamage
ret.hed2min = verfarDamage
ret.hed3min = verfarDamage
ret.bodymin = verfarDamage
ret.legsmin = apex.calcPartDamage(verfarDamage, legsScale)]]
else
else
ret = {
ret.hed1min = apex.calcPartDamage(verfarDamage, headLv1Scale)
stat.base,
ret.hed2min = apex.calcPartDamage(verfarDamage, headLv2Scale)
{
ret.hed3min = apex.calcPartDamage(verfarDamage, headLv3Scale)
aw.round(helmets.level3.func(stat.head) * stat.base),
ret.bodymin = verfarDamage
aw.round(helmet.func(stat.head) * stat.base),
ret.legsmin = apex.calcPartDamage(verfarDamage, legsScale)
},
}
end
end
return ret
return ret
end
end


local function getDPSs(damages, perk, pellets, firerate, useRound)
-- Apply perk scale (Low Profile / Fortified) to damages
local ret = {}
local function applyPerkToDamages(damages, perk, useRound)
for _, damage in ipairs(damages) do
if perk ~= 1 then
if proto.validateTypes(damage, MinMaxProto) then
return table.map(damages, function(key, damage)
local minPerked = apex.calcPassiveDamage(damage[1], perk, { round = useRound })
if aw.stringstarts(key, 'hed') then
local maxPerked = apex.calcPassiveDamage(damage[2], perk, { round = useRound })
return damage
table.insert(ret, { minPerked * firerate, pellets * maxPerked * firerate })
else
elseif pellets > 1 then
return apex.calcPassiveDamage(damage, perk, { round = useRound })
local perked = apex.calcPassiveDamage(damage, perk, { round = useRound })
end
table.insert(ret, { perked * firerate, pellets * perked * firerate })
end)
else
return damages
end
end
 
-- Apply fresh scale to damages (BEFORE PERK SCALE)
local function applyUnshieldScaleToDamages(stat, stat2, damages)
local unshieldedScale = getProperty(stat, stat2, 'damage_unshielded_scale', 0)
if unshieldedScale > 1 then
return table.mapValues(damages, function(damage)
return apex.calcHammerpointDamage(damage, unshieldedScale)
end)
else
return damages
end
end
 
-- Apply shield scale to damages (AFTER PERK SCALE)
local function applyShieldScaleToDamages(stat, stat2, damages)
local shieldScale = getProperty(stat, stat2, 'damage_shield_scale', 0)
if shieldScale > 1 then
return table.mapValues(damages, function(damage)
return apex.calcDisruptorDamage(damage, shieldScale)
end)
else
return damages
end
end
 
-- Calc DPSs
local function calcDPSs(stat, stat2, damages, firerate)
local pellets = getProperty(stat, stat2, 'pellet', 1)
--[[if pellets >= 2 then
return table.map(damages, function(key, damage)
if aw.stringends(key, 'max') then
return damage * firerate * pellets
else
return damage * firerate
end
end)
else]]
return table.mapValues(damages, function(damage)
return damage * firerate * pellets
end)
--end
end
 
-- Calc firerates
local function calcFirerates(stat, stat2, chargeRate)
chargeRate = chargeRate or 0
local charge    = getProperty(stat, stat2, 'charge', 0)
local firerate
local rechamber = getMultipleNumberProperty(stat, stat2, 'rechamber', nil)
if rechamber then
firerate = getProperty(stat, stat2, 'firerate', 1)
else
firerate = getMultipleNumberProperty(stat, stat2, 'firerate', { 1, 1.15, 1.25, 1.35 })
end
if proto.validateTypes(stat, MultipleBurstProto) then
firerate = {
apex.calcBurstAverageFirerate(stat.burst_count, stat.burst_delay[1], stat.firerate),
apex.calcBurstAverageFirerate(stat.burst_count, stat.burst_delay[2], stat.firerate),
apex.calcBurstAverageFirerate(stat.burst_count, stat.burst_delay[3], stat.firerate),
apex.calcBurstAverageFirerate(stat.burst_count, stat.burst_delay[4], stat.firerate),
}
elseif rechamber then
if charge > 0 then
firerate = {
apex.calcRechamberFirerate(firerate, rechamber[1] + chargeRate * charge),
apex.calcRechamberFirerate(firerate, rechamber[2] + chargeRate * charge),
apex.calcRechamberFirerate(firerate, rechamber[3] + chargeRate * charge),
apex.calcRechamberFirerate(firerate, rechamber[4] + chargeRate * charge),
}
else
else
local perked = apex.calcPassiveDamage(damage, perk, { round = useRound })
firerate = {
table.insert(ret, pellets * perked * firerate)
apex.calcRechamberFirerate(firerate, rechamber[1]),
apex.calcRechamberFirerate(firerate, rechamber[2]),
apex.calcRechamberFirerate(firerate, rechamber[3]),
apex.calcRechamberFirerate(firerate, rechamber[4]),
}
end
end
end
end
return ret
return firerate
end
end


local function toDPSText(value, format)
-- Calc firerate
if proto.validateTypes(value, MinMaxProto) then
local function calcFirerate(stat, stat2, chargeRate)
return string.format(format, string.format("%.1f", value[1]), string.format("%.1f", value[2]))
chargeRate = chargeRate or math.max(0, getProperty(stat, stat2, 'charge_minimum', 0))
else
return string.format("%.1f", value)
local charge    = getProperty(stat, stat2, 'charge', 0)
local firerate  = getProperty(stat, stat2, 'firerate', 1)
local raise    = getProperty(stat, stat2, 'raise', 0)
local rechamber = getProperty(stat, stat2, 'rechamber', 0)
local semiauto  = getBooleanProperty(stat, stat2, 'is_semi_auto', false)
if proto.validateTypes(stat, BurstProto) then
firerate = apex.calcBurstAverageFirerate(stat.burst_count, stat.burst_delay, stat.firerate)
elseif rechamber > 0 then
if charge > 0 then
firerate = apex.calcRechamberFirerate(firerate, rechamber + chargeRate * charge)
else
firerate = apex.calcRechamberFirerate(firerate, rechamber)
end
elseif charge > 0 then
firerate = apex.calcRechamberFirerate(firerate, chargeRate * charge)
elseif semiauto and raise > 0 then
firerate = apex.calcRechamberFirerate(firerate, raise)
end
end
return firerate
end
end


local function renderDPSRow(tbl, name, format, data, opts)
local function renderDPSRow(tbl, name, dps, structure, opts)
local row = tbl:tag('tr')
local row = tbl:tag('tr')
if aw.isNumberAndGreaterThanZero(opts.offset) then
if aw.isNumberAndGreaterThanOrEqualToX(structure.offset, 1) then
for i = 1, opts.offset do
for i = 1, structure.offset do
local hasLeftBorder = opts.offsetStyles and opts.offsetStyles[i] and opts.offsetStyles[i].leftBorder
local hasLeftBorder = structure.styles and structure.styles[i] and structure.styles[i].leftBorder
if hasLeftBorder then
if hasLeftBorder then
row:tag('th')
row:tag('th')
161行目: 316行目:
end
end
row:tag('th')
row:tag('th')
:attrIf(aw.isNumber(opts.colspan), { colspan = opts.colspan })
:attrIf(aw.isNumber(structure.colspan), { colspan = structure.colspan })
:cssIf(aw.isNumberAndGreaterThanZero(opts.offset), leftBorder)
:cssIf(aw.isNumberAndGreaterThanZero(structure.offset), leftBorder)
:wikitext(name)
:wikitext(name)
if #data >= 3 then
if data[1] == nil then
if opts.legsoff then
row:tag('td')
row:tag('td')
:attr('align', 'center')
:attr('align', 'right')
:addClass('disabled')
:wikitext(opts.format(dps.bodymax, dps.bodymin))
:wikitext('–')
elseif dps.legsmax == dps.bodymax and dps.legsmin == dps.bodymin then
else
row:tag('td')
row:tag('td')
:attr({ align = 'right', colspan = 2 })
:addClass('cell-type-number')
:wikitext(opts.format(dps.bodymax, dps.bodymin))
:attr('align', 'right')
:done()
:wikitext(toDPSText(data[1], format))
else
end
row
row
:tag('td')
:tag('td')
:addClass('cell-type-number')
:attr('align', 'right')
:attr('align', 'right')
:wikitext(toDPSText(data[2], format))
:wikitext(opts.format(dps.legsmax, dps.legsmin))
:done()
:done()
:tag('td')
:tag('td')
:attr('align', 'right')
:attr('align', 'right')
:wikitext(toDPSText(data[3], format))
:wikitext(opts.format(dps.bodymax, dps.bodymin))
end
row
:tag('td')
:attr('align', 'right')
:wikitext(opts.format(dps.hed3max, dps.hed3min))
:done()
:tag('td')
:attr('align', 'right')
:wikitext(opts.format(dps.hed2max, dps.hed2min))
:done()
:tag('td')
:attr('align', 'right')
:wikitext(opts.format(dps.hed1max, dps.hed1min))
end
 
-- Render the header node
local function renderHeader(tbl, opts)
local header = tbl:tag('tr'):tag('th')
:attrIf(opts.structure.levels > 1, { colspan = opts.structure.levels })
:wikitext('')
:done()
if opts.legsoff then
header:tag('th'):wikitext(opts.cfg.part.bodylegs)
else
else
row
header
:tag('td')
:tag('th'):wikitext(opts.cfg.part.legs):done()
:addClass('cell-type-number')
:tag('th'):wikitext(opts.cfg.part.body)
:attr('align', 'right')
end
:attrIf(opts.hideLegs, { colspan = 2 })
header
:wikitext(toDPSText(data[1], format))
:tag('th'):wikitext(iu.gear('helmet', 3))
:done()
:tag('th'):wikitext(iu.gear('helmet', 2))
:tag('td')
:tag('th'):wikitext(iu.gear('helmet', 1))
:attr('align', 'right')
end
:wikitext(toDPSText(data[2], format))
 
-- Render the DPS section for Shotguns
local function renderShotgunDPSSection(tbl, name, stat, baseDamages, chargeLevels, firerates, perk, opts)
local perkDamages = applyPerkToDamages(baseDamages, perk, opts.round)
-- Normal
local dps = calcDPSs(stat, empty, perkDamages, firerates[1])
renderDPSRow(tbl, name, dps, opts.structure.root, opts)
-- w/bolt lvl1
local label = iu.attachment('ショットガンボルト', 1)
dps = calcDPSs(stat, empty, perkDamages, firerates[2])
renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
-- w/bolt lvl2
label = iu.attachment('ショットガンボルト', 2)
dps = calcDPSs(stat, empty, perkDamages, firerates[3])
renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
-- w/bolt lvl3
label = iu.attachment('ショットガンボルト', 3)
dps = calcDPSs(stat, empty, perkDamages, firerates[4])
renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
-- Hop-ups
for _, metadata in ipairs(opts.hopups) do
local label = iu.hopup(metadata.key)
local hopupStat    = stat[metadata.key]
local baseDamages  = calcBaseDamages(hopupStat, stat)
local perkDamages  = applyPerkToDamages(baseDamages, perk, opts.round)
local chargeLevels = getProperty(hopupStat, stat, 'charge_levels', 1)
local firerates    = calcFirerates(hopupStat, stat)
-- Normal
local dps = calcDPSs(hopupStat, stat, perkDamages, firerates[1])
renderDPSRow(tbl, label, dps, opts.structure.level2, opts)
-- w/bolt lvl1
label = iu.attachment('ショットガンボルト', 1)
dps = calcDPSs(hopupStat, stat, perkDamages, firerates[2])
renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
-- w/bolt lvl2
label = iu.attachment('ショットガンボルト', 2)
dps = calcDPSs(hopupStat, stat, perkDamages, firerates[3])
renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
-- w/bolt lvl3
label = iu.attachment('ショットガンボルト', 3)
dps = calcDPSs(hopupStat, stat, perkDamages, firerates[4])
renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
end
end
 
-- Render the DPS section with shield/unshield scale
local function renderShieldDPSSection(tbl, name, stat, stat2, baseDamages, firerate, perk, firstStructure, structure, opts)
local unshldDPS = calcDPSs(stat, stat2, applyPerkToDamages(applyUnshieldScaleToDamages(stat, stat2, baseDamages), perk, opts.round), firerate)
local shieldDPS = calcDPSs(stat, stat2, applyPerkToDamages(applyShieldScaleToDamages(stat, stat2, baseDamages), perk, opts.round), firerate)
local lvl1DPS = {}
local lvl2DPS = {}
local lvl3DPS = {}
local lvl5DPS = {}
for key in pairs(unshldDPS) do
lvl1DPS[key] = (2 * unshldDPS[key] +    shieldDPS[key]) / 3
lvl2DPS[key] = (4 * unshldDPS[key] + 3 * shieldDPS[key]) / 7
lvl3DPS[key] = (    unshldDPS[key] +    shieldDPS[key]) / 2
lvl5DPS[key] = (4 * unshldDPS[key] + 5 * shieldDPS[key]) / 9
end
end
-- w/o shield
renderDPSRow(tbl, name, unshldDPS, firstStructure, opts)
-- w/shield lvl1
local label = iu.gear('evo_shield', 1)
renderDPSRow(tbl, label, lvl1DPS, structure, opts)
-- w/shield lvl2
label = iu.gear('evo_shield', 2)
renderDPSRow(tbl, label, lvl2DPS, structure, opts)
-- w/shield lvl3
label = iu.gear('evo_shield', 3)
renderDPSRow(tbl, label, lvl3DPS, structure, opts)
-- w/shield lvl5
label = iu.gear('evo_shield', 5)
renderDPSRow(tbl, label, lvl5DPS, structure, opts)
end
end


function p.renderDPS(stat, shieldinfo, lang)
-- Render the DPS section (Normal weapons)
local cfg      = cfg[lang].dps
local function renderNormalDPSSection(tbl, name, stat, baseDamages, chargeLevels, firerate, perk, opts)
local basic    = getStats(stat)
local perkDamages = applyPerkToDamages(baseDamages, perk, opts.round)
local damages  = getDamages(basic, shieldinfo.helmets)
local pellet  = aw.getAsNumber(stat.pellet, 1)
-- Normal
local useRound = aw.getAsBoolean(stat.damage.round, false)
if not (opts.special and #opts.hopups > 0 and opts.hopups[1].key == 'disruptor_rounds') then
local dps = calcDPSs(stat, empty, perkDamages, firerate)
renderDPSRow(tbl, name, dps, opts.structure.root, opts)
end
local hasSlct  = proto.validateTypes(stat.selectfire_receiver, SelectfireReceiverProto)
-- Altfire
local hasAnvil = proto.validateTypes(stat.anvil_receiver, AnvilReceiverProto)
if opts.flags.mode_altfire then
local hasShat  = proto.validateTypes(stat.shatter_caps, ShatterCapsProto)
local label = opts.cfg.altfire
local hasTempo = proto.validateTypes(stat.deadeyes_tempo, DeadeyesTempoWithRechamberProto)
local firerate = stat.altfire.firerate
local hasTrmts = aw.isNumberAndGreaterThanZero(stat.firerate_revvedup)
local dps      = calcDPSs(stat.altfire, stat, perkDamages, firerate)
local hasHopup = hasSlct or hasAnvil or hasShat or hasTempo or hasTrmts
renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
end
local hasAmped = aw.isNumberAndGreaterThanZero(stat.damage.amped)
-- Amped mode
local hasChgd  = aw.isNumberAndGreaterThanZero(stat.damage.charged)
if opts.flags.mode_amped then
local hasExtra = hasAmped or hasChgd
local label = iu.item('シールドセル')
local baseDamages = calcBaseDamages(stat.energized, stat, chargeRate)
local perkDamages = applyPerkToDamages(baseDamages, perk, opts.round)
local dps        = calcDPSs(stat.energized, stat, perkDamages, firerate)
renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
end
-- Load firerate
-- Revved Up mode
local firerate
if opts.flags.mode_revvedup then
if aw.isNumberAndGreaterThanOrEqualToX(stat.burst_count, 2) then
local label = iu.grenade('テルミットグレネード')
firerate = calcBurstRPS(stat)
local firerate = stat.firerate_revvedup
elseif proto.validateTypes(stat.rechamber, MultipleFirerateProto) then
local dps      = calcDPSs(stat, empty, perkDamages, firerate)
firerate = {
renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
apex.calcRechamberFirerate(stat.firerate, stat.rechamber[1]),
apex.calcRechamberFirerate(stat.firerate, stat.rechamber[2]),
apex.calcRechamberFirerate(stat.firerate, stat.rechamber[3]),
apex.calcRechamberFirerate(stat.firerate, stat.rechamber[4]),
}
elseif aw.isNumberAndGreaterThanZero(stat.rechamber) then
firerate = apex.calcRechamberFirerate(stat.firerate, stat.rechamber)
else
firerate = stat.firerate
end
end
local hasMultiple = proto.validateTypes(firerate, MultipleFirerateProto)
local headerColspan
-- Chargefire
local defopts, mpopts, huopts, exopts, ex2opts
if chargeLevels == 2 then
if hasMultiple then
for _, chargeRate in ipairs(steplessChargeRates) do
if hasExtra then
local label
headerColspan = 3
if chargeRate >= 1 then
defopts = { colspan = 3 }
label = opts.cfg.charge_stepless_max
muopts  = { offset = 1, colspan = 2 }
elseif chargeRate == 0.5 then
exopts  = { offset = 2 }
label = opts.cfg.charge_stepless_half
ex2opts = { offset = 2, offsetStyles = { nil, { leftBorder = true } } }
else
else
label = string.format(opts.cfg.charge_stepless_format, 100 * chargeRate)
headerColspan = 2
end
defopts = { colspan = 2 }
local baseDamages = calcBaseDamages(stat, empty, chargeRate)
muopts  = { offset = 1 }
local perkDamages = applyPerkToDamages(baseDamages, perk, opts.round)
exopts  = {}
local firerate    = calcFirerate(stat, empty, chargeRate)
ex2opts = {}
local dps        = calcDPSs(stat, empty, perkDamages, firerate)
renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
end
end
huopts      = {}
elseif chargeLevels > 2 then
elseif hasHopup then
for chargeLevel = 1, chargeLevels - 1 do
if hasExtra then
local chargeRate = chargeLevel / (chargeLevels - 1)
local hideLegs = damages.legs ~= 1
local label    = aw.getQuantityString(opts.cfg.charge_step_quantity, chargeLevel)
headerColspan = 3
local firerate = calcFirerate(stat, empty, chargeRate)
defopts = { colspan = 3 }
local dps      = calcDPSs(stat, empty, perkDamages, firerate)
huopts  = { offset = 1, colspan = 2, hideLegs = hideLegs }
renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
exopts  = { offset = 2 }
ex2opts = { offset = 2, offsetStyles = { nil, { leftBorder = true } }, hideLegs = hideLegs }
else
headerColspan = 2
defopts = { colspan = 2 }
huopts  = { offset = 1, hideLegs = damages.legs ~= 1 }
exopts  = {}
ex2opts = {}
end
end
muopts      = {}
elseif hasExtra then
headerColspan = 1
defopts = { colspan = 2 }
muopts  = {}
huopts  = {}
exopts  = {}
ex2opts = { offset = 1 }
else
headerColspan = 1
defopts = {}
muopts  = {}
huopts  = {}
exopts  = {}
ex2opts = {}
end
end
-- Hop-ups
-- Hop-ups
local damages2, pellet2, firerate2, label
for _, metadata in ipairs(opts.hopups) do
if hasSlct then
local label = iu.hopup(metadata.key)
local slct  = getStats(stat, stat.selectfire_receiver)
local hopupStat   = stat[metadata.key]
damages2   = getDamages(slct, shieldinfo.helmets)
local baseDamages  = calcBaseDamages(hopupStat, stat)
pellet2    = pellet
local perkDamages  = applyPerkToDamages(baseDamages, perk, opts.round)
firerate2  = slct.semi
local chargeLevels = getProperty(hopupStat, stat, 'charge_levels', 1)
and apex.calcRechamberFirerate(stat.selectfire_receiver.firerate, stat.selectfire_receiver.raise)
local firerate    = calcFirerate(hopupStat, stat)
or  stat.selectfire_receiver.firerate
label      = iu.hopup('selectfire_receiver')
-- Hammerpoint Rounds / Disruptor Rounds
elseif hasAnvil then
if metadata.key == 'hammerpoint_rounds' or metadata.key == 'disruptor_rounds' then
local anvil = getStats(stat, stat.anvil_receiver)
local item, root, next
damages2    = getDamages(anvil, shieldinfo.helmets)
if opts.special then
pellet2    = pellet
item = name
firerate2  = stat.anvil_receiver.firerate
root = opts.structure.root
label      = iu.hopup('anvil_receiver')
next = opts.structure.level2
elseif hasShat then
else
local shat  = getStats(stat, stat.shatter_caps)
item = label
damages2    = getDamages(shat, shieldinfo.helmets)
root = opts.structure.level2
pellet2    = stat.shatter_caps.pellet
next = opts.structure.level3
firerate2  = firerate
end
label      = iu.hopup('shatter_caps')
renderShieldDPSSection(tbl, item, hopupStat, stat, baseDamages, firerate, perk, root, next, opts)
elseif hasTempo then
damages2    = damages
-- Other
pellet2    = pellet
else
firerate2  = apex.calcRechamberFirerate(stat.firerate, stat.deadeyes_tempo.rechamber)
local dps = calcDPSs(hopupStat, stat, perkDamages, firerate)
label      = string.format(cfg.hopup.deadeyes_tempo, iu.hopup('deadeyes_tempo'))
renderDPSRow(tbl, label, dps, opts.structure.level2, opts)
elseif hasTrmts then
damages2    = damages
pellet2    = pellet
firerate2  = stat.firerate_revvedup
label      = iu.grenade('テルミットグレネード')
end
-- Extra damages
local damagesE, labelE, damages2E
if hasAmped then
local extra = aw.shallowCopy(basic)
extra.base  = stat.damage.amped
damagesE    = getDamages(extra, shieldinfo.helmets)
labelE      = iu.item('シールドセル')
elseif hasChgd then
local extra = aw.shallowCopy(basic)
extra.base  = stat.damage.charged
damagesE    = getDamages(extra, shieldinfo.helmets)
labelE      = iu.scope('スロット')
if hasShat then
-- Amped mode
local shat2    = getStats(stat, stat.shatter_caps)
if opts.flags.mode_amped then
shat2.base    = stat.shatter_caps.damage.charged
local label = iu.item('シールドセル')
if aw.isNumberAndGreaterThanZero(stat.shatter_caps.damage.headshot_charged) then
local mergedStat  = aw.mergeTable(hopupStat, stat.energized)
shat2.head = stat.shatter_caps.damage.headshot_charged
local baseDamages = calcBaseDamages(mergedStat, stat, chargeRate)
local perkDamages = applyPerkToDamages(baseDamages, perk, opts.round)
local dps        = calcDPSs(mergedStat, stat, perkDamages, firerate)
renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
end
end
if aw.isNumberAndGreaterThanZero(stat.shatter_caps.damage.legshot_charged) then
local legs = stat.shatter_caps.damage.legshot_charged
-- Chargefire
shat2.legs = legs
if chargeLevels == 2 then
for _, chargeRate in ipairs(steplessChargeRates) do
if legs == 1 then
local label
ex2opts.hideLegs = true
if chargeRate >= 1 then
label = opts.cfg.charge_stepless_max
elseif chargeRate == 0.5 then
label = opts.cfg.charge_stepless_half
else
label = string.format(opts.cfg.charge_stepless_format, 100 * chargeRate)
end
local baseDamages  = calcBaseDamages(hopupStat, stat, chargeRate)
local perkDamages  = applyPerkToDamages(baseDamages, perk, opts.round)
local firerate    = calcFirerate(hopupStat, stat, chargeRate)
local dps          = calcDPSs(hopupStat, stat, perkDamages, firerate)
renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
end
elseif chargeLevels > 2 then
for chargeLevel = 1, chargeLevels - 1 do
local chargeRate = chargeLevel / (chargeLevels - 1)
local label    = aw.getQuantityString(opts.cfg.charge_step_quantity, chargeLevel)
local firerate = calcFirerate(hopupStat, stat, chargeRate)
local dps      = calcDPSs(hopupStat, stat, perkDamages, firerate)
renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
end
end
end
end
damages2E      = getDamages(shat2, shieldinfo.helmets)
end
end
end
end
end
local tbl = mw.html.create('table'):addClass('intable')
 
local function numformat(num)
-- Header
if num >= 100 then
local header = tbl:tag('tr'):tag('th')
num = aw.roundx(num, 1)
:attrIf(headerColspan > 1, { colspan = headerColspan })
local int = math.floor(num)
:wikitext('')
return string.format('%d<span class="text-smaller text-secondary">.%01.0f</span>', int, 10 * (num - int))
:done()
elseif num >= 10 then
if #damages >= 3 then
num = aw.roundx(num, 2)
header
local int = math.floor(num)
:tag('th')
return string.format('%d<span class="text-smaller text-secondary">.%02.0f</span>', int, 100 * (num - int))
:wikitext(cfg.part.legs)
:tag('th')
:wikitext(cfg.part.body)
else
else
header:tag('th')
num = aw.roundx(num, 3)
:wikitext(cfg.part.bodylegs)
local int = math.floor(num)
return string.format('%d<span class="text-smaller text-secondary">.%03.0f</span>', int, 1000 * (num - int))
end
end
header:tag('th')
end
:wikitext(cfg.part.head)
 
local function numformatmin(num)
-- Normal
if num >= 100 then
if hasMultiple then
num = aw.round(num)
local normalDPS0 = getDPSs(damages, 1, pellet, firerate[1], useRound)
local int = math.floor(num)
local normalDPS1 = getDPSs(damages, 1, pellet, firerate[2], useRound)
return string.format('%d', int)
local normalDPS2 = getDPSs(damages, 1, pellet, firerate[3], useRound)
elseif num >= 10 then
local normalDPS3 = getDPSs(damages, 1, pellet, firerate[4], useRound)
num = aw.roundx(num, 1)
renderDPSRow(tbl, cfg.perk.normal, cfg.complex, normalDPS0, defopts)
local int = math.floor(num)
renderDPSRow(tbl, iu.attachment('ショットガンボルト', 1), cfg.complex, normalDPS1, muopts)
return string.format('%d<span class="text-smaller text-secondary">.%01.0f</span>', int, 10 * (num - int))
renderDPSRow(tbl, iu.attachment('ショットガンボルト', 2), cfg.complex, normalDPS2, muopts)
renderDPSRow(tbl, iu.attachment('ショットガンボルト', 3), cfg.complex, normalDPS3, muopts)
else
else
local normalDPS = getDPSs(damages, 1, pellet, firerate, useRound)
num = aw.roundx(num, 2)
renderDPSRow(tbl, cfg.perk.normal, cfg.complex, normalDPS, defopts)
local int = math.floor(num)
return string.format('%d<span class="text-smaller text-secondary">.%02.0f</span>', int, 100 * (num - int))
end
end
end
function p.renderDPS(stat, shieldinfo, lang)
local cfg  = cfg[lang].dps
local opts = {
cfg    = cfg,
flags  = {},
format  = function(max, min)
if max ~= min then
return string.format(cfg.complex, numformatmin(min), numformat(max))
else
return string.format(cfg.simple,  numformat(max))
end
end,
hopups  = {},
legsoff = (stat.damage_legs_scale or 1) == 1,
round  = aw.getAsBoolean(stat.damage.round, false),
special = aw.stringstarts(stat.ammo, 'special_'),
}
local baseDamages  = calcBaseDamages(stat, empty)
local chargeLevels = getProperty(stat, empty, 'charge_levels', 1)
local tbl = mw.html.create('table'):addClass('intable')
--- Extra
-- Shotgun
if hasExtra then
if stat.ammo == 'shotgun' then
local extraDPS = getDPSs(damagesE, 1, pellet, firerate, useRound)
local firerates = calcFirerates(stat, empty)
renderDPSRow(tbl, labelE, cfg.complex, extraDPS, exopts)
end
local level = 2
if (Hopup.double_tap_trigger.enabled or opts.special) and proto.validateTypes(stat.double_tap_trigger, MultipleBurstProto) then
level = math.max(level, 3)
table.insert(opts.hopups, {
key = 'double_tap_trigger',
})
end
opts.structure = structures[level]
renderHeader(tbl, opts)
renderShotgunDPSSection(tbl, opts.cfg.perk.normal, stat, baseDamages, chargeLevels, firerates, 1, opts)
--- Hop-up
-- Low Profile
if hasHopup then
if aw.isNumber(shieldinfo.lowprofile) and shieldinfo.lowprofile > 1 then
local hopupDPS = getDPSs(damages2, 1, pellet2, firerate2, useRound)
renderShotgunDPSSection(tbl, opts.cfg.perk.lowprofile, stat, baseDamages, chargeLevels, firerates, shieldinfo.lowprofile, opts)
renderDPSRow(tbl, label, cfg.complex, hopupDPS, huopts)
end
---- Extra
-- Fortified
if hasTempo and hasAmped then
if aw.isNumber(shieldinfo.fortified) and shieldinfo.fortified < 1 then
local tempoExtraDPS = getDPSs(damagesE, 1, pellet2, firerate2, useRound)
renderShotgunDPSSection(tbl, opts.cfg.perk.fortified, stat, baseDamages, chargeLevels, firerates, shieldinfo.fortified, opts)
renderDPSRow(tbl, labelE, cfg.complex, tempoExtraDPS, ex2opts)
elseif hasShat and hasChgd then
local shatChargedDPS = getDPSs(damages2E, 1, pellet2, firerate2, useRound)
renderDPSRow(tbl, labelE, cfg.complex, shatChargedDPS, ex2opts)
end
end
end
-- Low Profile
-- Other
if aw.isNumber(shieldinfo.lowprofile) and shieldinfo.lowprofile > 1 then
else
if hasMultiple then
local firerate = calcFirerate(stat, empty)
local normalDPS0 = getDPSs(damages, shieldinfo.lowprofile, pellet, firerate[1], useRound)
local normalDPS1 = getDPSs(damages, shieldinfo.lowprofile, pellet, firerate[2], useRound)
local level = 1
local normalDPS2 = getDPSs(damages, shieldinfo.lowprofile, pellet, firerate[3], useRound)
if proto.validateTypes(stat, AltfireProto) then
local normalDPS3 = getDPSs(damages, shieldinfo.lowprofile, pellet, firerate[4], useRound)
level = math.max(level, 2)
renderDPSRow(tbl, cfg.perk.lowprofile, cfg.complex, normalDPS0, defopts)
opts.flags.mode_altfire = true
renderDPSRow(tbl, iu.attachment('ショットガンボルト', 1), cfg.complex, normalDPS1, muopts)
end
renderDPSRow(tbl, iu.attachment('ショットガンボルト', 2), cfg.complex, normalDPS2, muopts)
if proto.validateTypes(stat, RevvedUpProto) then
renderDPSRow(tbl, iu.attachment('ショットガンボルト', 3), cfg.complex, normalDPS3, muopts)
level = math.max(level, 2)
else
opts.flags.mode_revvedup = true
local lowprofileDPS = getDPSs(damages, shieldinfo.lowprofile, pellet, firerate, useRound)
end
renderDPSRow(tbl, cfg.perk.lowprofile, cfg.complex, lowprofileDPS, defopts)
if (Hopup.selectfire_receiver.enabled or opts.special) and type(stat.selectfire_receiver) == 'table' then
level = math.max(level, 2)
table.insert(opts.hopups, {
key = 'selectfire_receiver',
})
end
end
if Hopup.hammerpoint_rounds.enabled and proto.validateTypes(stat.hammerpoint_rounds, HammerpointRoundsProto) then
--- Extra
level = math.max(level, 3)
if hasExtra then
table.insert(opts.hopups, {
local extraLowProfileDPS = getDPSs(damagesE, shieldinfo.lowprofile, pellet, firerate, useRound)
key = 'hammerpoint_rounds',
renderDPSRow(tbl, labelE, cfg.complex, extraLowProfileDPS, exopts)
})
end
end
if (Hopup.disruptor_rounds.enabled or opts.special) and proto.validateTypes(stat.disruptor_rounds, DisruptorRoundsProto) then
--- Hop-up
if opts.special then
if hasHopup then
level = math.max(level, 2)
local hopupLowProfileDPS = getDPSs(damages2, shieldinfo.lowprofile, pellet2, firerate2, useRound)
else
renderDPSRow(tbl, label, cfg.complex, hopupLowProfileDPS, huopts)
level = math.max(level, 3)
---- Extra
if hasTempo and hasAmped then
local tempoAmpedLowProfileDPS = getDPSs(damagesE, shieldinfo.lowprofile, pellet2, firerate2, useRound)
renderDPSRow(tbl, labelE, cfg.complex, tempoAmpedLowProfileDPS, ex2opts)
elseif hasShat and hasChgd then
local shatChargedLowProfileDPS = getDPSs(damages2E, shieldinfo.lowprofile, pellet2, firerate2, useRound)
renderDPSRow(tbl, labelE, cfg.complex, shatChargedLowProfileDPS, ex2opts)
end
end
table.insert(opts.hopups, {
key = 'disruptor_rounds',
})
end
if Hopup.anvil_receiver.enabled and type(stat.anvil_receiver) == 'table' then
level = math.max(level, 2)
table.insert(opts.hopups, {
key = 'anvil_receiver',
})
end
if (Hopup.double_tap_trigger.enabled or opts.special) and proto.validateTypes(stat.double_tap_trigger, BurstProto) then
level = math.max(level, 2)
table.insert(opts.hopups, {
key = 'double_tap_trigger',
})
end
if Hopup.shatter_caps.enabled and proto.validateTypes(stat.shatter_caps, ShatterCapsProto) then
level = math.max(level, 2)
table.insert(opts.hopups, {
key = 'shatter_caps',
})
end
if Hopup.deadeyes_tempo.enabled and type(stat.deadeyes_tempo) == 'table' then
level = math.max(level, 2)
table.insert(opts.hopups, {
key = 'deadeyes_tempo',
})
end
if proto.validateTypes(stat.energized, DamageProto) then
level = level + 1
opts.flags.mode_amped = true
elseif chargeLevels >= 2 then
level = level + 1
end
end
end
opts.structure = structures[level]
-- Fortified
renderHeader(tbl, opts)
if aw.isNumber(shieldinfo.fortified) and shieldinfo.fortified < 1 then
renderNormalDPSSection(tbl, opts.cfg.perk.normal, stat, baseDamages, chargeLevels, firerate, 1, opts)
if hasMultiple then
local normalDPS0 = getDPSs(damages, shieldinfo.fortified, pellet, firerate[1], useRound)
-- Low Profile
local normalDPS1 = getDPSs(damages, shieldinfo.fortified, pellet, firerate[2], useRound)
if aw.isNumber(shieldinfo.lowprofile) and shieldinfo.lowprofile > 1 then
local normalDPS2 = getDPSs(damages, shieldinfo.fortified, pellet, firerate[3], useRound)
renderNormalDPSSection(tbl, opts.cfg.perk.lowprofile, stat, baseDamages, chargeLevels, firerate, shieldinfo.lowprofile, opts)
local normalDPS3 = getDPSs(damages, shieldinfo.fortified, pellet, firerate[4], useRound)
renderDPSRow(tbl, cfg.perk.fortified, cfg.complex, normalDPS0, defopts)
renderDPSRow(tbl, iu.attachment('ショットガンボルト', 1), cfg.complex, normalDPS1, muopts)
renderDPSRow(tbl, iu.attachment('ショットガンボルト', 2), cfg.complex, normalDPS2, muopts)
renderDPSRow(tbl, iu.attachment('ショットガンボルト', 3), cfg.complex, normalDPS3, muopts)
else
local lowprofileDPS = getDPSs(damages, shieldinfo.fortified, pellet, firerate, useRound)
renderDPSRow(tbl, cfg.perk.fortified, cfg.complex, lowprofileDPS, defopts)
end
end
--- Extra
-- Fortified
if hasExtra then
if aw.isNumber(shieldinfo.fortified) and shieldinfo.fortified < 1 then
local extraFortifiedDPS = getDPSs(damagesE, shieldinfo.fortified, pellet, firerate, useRound)
renderNormalDPSSection(tbl, opts.cfg.perk.fortified, stat, baseDamages, chargeLevels, firerate, shieldinfo.fortified, opts)
renderDPSRow(tbl, labelE, cfg.complex, extraFortifiedDPS, exopts)
end
--- Hop-up
if hasHopup then
local hopupFortifiedDPS = getDPSs(damages2, shieldinfo.fortified, pellet2, firerate2, useRound)
renderDPSRow(tbl, label, cfg.complex, hopupFortifiedDPS, huopts)
---- Extra
if hasTempo and hasAmped then
local tempoAmpedFortifiedDPS = getDPSs(damagesE, shieldinfo.fortified, pellet2, firerate2, useRound)
renderDPSRow(tbl, labelE, cfg.complex, tempoAmpedFortifiedDPS, ex2opts)
elseif hasShat and hasChgd then
local shatChargedFortifiedDPS = getDPSs(damages2E, shieldinfo.fortified, pellet2, firerate2, useRound)
renderDPSRow(tbl, labelE, cfg.complex, shatChargedFortifiedDPS, ex2opts)
end
end
end
end
end
488行目: 759行目:
local stat = mw.loadData('Module:Stat/Weapon')[name]
local stat = mw.loadData('Module:Stat/Weapon')[name]
local sld  = require('Module:Stat/Shield')['removelowprofile']
local sld  = require('Module:Stat/Shield')['reinforcehelmets']
local node = p.renderDPS(stat, sld, lang)
local node = p.renderDPS(stat, sld, lang)

2022年5月23日 (月) 13:14時点における最新版

このモジュールについての説明文ページを モジュール:WeaponInfobox/DPS/doc に作成できます

require('Module:Utility/mw.html Extensions')

local p = {}
local cfg = mw.loadData('Module:WeaponInfobox/configuration')
local Hopup = mw.loadData('Module:Stat/Hopup')

local aw    = require('Module:Utility/Library')
local apex  = require('Module:Utility/ApexLibrary')
local iu    = require('Module:Utility/Image')
local proto = require('Module:Utility/Prototypes')
local table = require('Module:TableExtensions')

-- Constants
local leftBorder = {
	['border-left-width'] = '1px',
	['border-left-style'] = 'solid',
}
local leftBorderWithoutTopRightBorder = {
	['border-top'] = '0 none transparent',
	['border-right'] = '0 none transparent',
	['border-left-width'] = '1px',
	['border-left-style'] = 'solid',
}

local empty = {}
local steplessChargeRates = { 0.5, 1 }
local structures = {
	{
		levels = 1,
		root  = empty,
	},
	{
		levels = 2,
		root   = { colspan = 2 },
		level2 = { offset  = 1 },
		extra  = { offset  = 1 },
	},
	{
		levels = 3,
		root   = { colspan = 3 },
		level2 = { colspan = 2, offset  = 1 },
		level3 = { offset  = 2, styles = { nil, { leftBorder = true } } },
		extra  = { offset  = 2 },
	},
}

-- Protos
local MultipleProto = {
	proto.NumberRange(0),
	proto.NumberRange(0),
	proto.NumberRange(0),
	proto.NumberRange(0),
}
local DamageProto = {
	damage_near_value     = proto.NumberRange(1),
	damage_very_far_value = proto.NumberRange(1),
}
local AltfireProto = {
	altfire = {
		firerate = proto.NumberRange(0),
	}
}
local RevvedUpProto = {
	firerate_revvedup = proto.NumberRange(1),
}
local BurstProto = {
	burst_count = proto.NumberRange(2),
	burst_delay = proto.NumberRange(0),
	firerate    = proto.NumberRange(0),
}
local MultipleBurstProto = {
	burst_count = proto.NumberRange(2),
	burst_delay = MultipleProto,
	firerate    = proto.NumberRange(0),
}
local HammerpointRoundsProto = {
	damage_unshielded_scale = proto.NumberRange(1),
}
local DisruptorRoundsProto = {
	damage_shield_scale = proto.NumberRange(1),
}
local ShatterCapsProto = {
	damage_near_value     = proto.NumberRange(1),
	damage_very_far_value = proto.NumberRange(1),
	damage_head_scale     = proto.NumberRange(1),
	damage_legs_scale     = proto.NumberRange(0, 1),
	pellet                = proto.NumberRange(2),
}

-- Get property
local function getProperty(stat, stat2, key, defaultValue)
	if aw.isNumber(stat[key]) then
		return stat[key]
	elseif aw.isNumber(stat2[key]) then
		return stat2[key]
	else
		return defaultValue
	end
end

-- Get multiple number property
local function getMultipleNumberProperty(stat, stat2, key, defaultValue)
	if proto.validateTypes(stat[key], MultipleProto) then
		return stat[key]
	elseif proto.validateTypes(stat2[key], MultipleProto) then
		return stat2[key]
	else
		return defaultValue
	end
end

-- Get property
local function getBooleanProperty(stat, stat2, key, defaultValue)
	if type(stat[key]) == 'boolean' then
		return stat[key]
	elseif type(stat2[key]) == 'boolean' then
		return stat2[key]
	else
		return defaultValue
	end
end

-- Calc base damages
local function calcBaseDamages(stat, stat2, chargeRate)
	chargeRate = chargeRate or 0
	
	local chargeAddScale = getProperty(stat, stat2, 'charge_additional_scale', 0)
	local nearDamage     = getProperty(stat, stat2, 'damage_near_value', 0)
	local verfarDamage   = getProperty(stat, stat2, 'damage_very_far_value', nil)    or getProperty(stat, stat2, 'damage_far_value', 0)
	local verfarDistance = getProperty(stat, stat2, 'damage_very_far_distance', nil) or getProperty(stat, stat2, 'damage_far_distance', 15748)
	if chargeRate > 0 then
		local mul = 1 + chargeRate * chargeAddScale
		nearDamage   = aw.round(nearDamage   * mul)
		verfarDamage = aw.round(verfarDamage * mul)
	end
	
	local headDistance = getProperty(stat, stat2, 'damage_head_distance', math.huge)
	local headScale    = getProperty(stat, stat2, 'damage_head_scale', 1)
	local headLv1Scale = apex.calcHeadshotMultiplier(headScale, apex.HEAD_HLMLV1)
	local headLv2Scale = apex.calcHeadshotMultiplier(headScale, apex.HEAD_HLMLV2)
	local headLv3Scale = apex.calcHeadshotMultiplier(headScale, apex.HEAD_HLMLV3)
	local legsScale    = getProperty(stat, stat2, 'damage_legs_scale', 1)
	local ret = {
		hed1max = apex.calcPartDamage(nearDamage, headLv1Scale),
		hed2max = apex.calcPartDamage(nearDamage, headLv2Scale),
		hed3max = apex.calcPartDamage(nearDamage, headLv3Scale),
		bodymax = nearDamage,
		legsmax = apex.calcPartDamage(nearDamage, legsScale),
	}
	if nearDamage == verfarDamage then
		--[[if verfarDistance > headDistance then
			ret.hed1min = ret.bodymax
			ret.hed2min = ret.bodymax
			ret.hed3min = ret.bodymax
		else]]
			ret.hed1min = ret.hed1max
			ret.hed2min = ret.hed2max
			ret.hed3min = ret.hed3max
		--end
		ret.bodymin = ret.bodymax
		ret.legsmin = ret.legsmax
	--[[elseif verfarDistance > headDistance then
		ret.hed1min = verfarDamage
		ret.hed2min = verfarDamage
		ret.hed3min = verfarDamage
		ret.bodymin = verfarDamage
		ret.legsmin = apex.calcPartDamage(verfarDamage, legsScale)]]
	else
		ret.hed1min = apex.calcPartDamage(verfarDamage, headLv1Scale)
		ret.hed2min = apex.calcPartDamage(verfarDamage, headLv2Scale)
		ret.hed3min = apex.calcPartDamage(verfarDamage, headLv3Scale)
		ret.bodymin = verfarDamage
		ret.legsmin = apex.calcPartDamage(verfarDamage, legsScale)
	end
	return ret
end

-- Apply perk scale (Low Profile / Fortified) to damages
local function applyPerkToDamages(damages, perk, useRound)
	if perk ~= 1 then
		return table.map(damages, function(key, damage)
			if aw.stringstarts(key, 'hed') then
				return damage
			else
				return apex.calcPassiveDamage(damage, perk, { round = useRound })
			end
		end)
	else
		return damages
	end
end

-- Apply fresh scale to damages (BEFORE PERK SCALE)
local function applyUnshieldScaleToDamages(stat, stat2, damages)
	local unshieldedScale = getProperty(stat, stat2, 'damage_unshielded_scale', 0)
	if unshieldedScale > 1 then
		return table.mapValues(damages, function(damage)
			return apex.calcHammerpointDamage(damage, unshieldedScale)
		end)
	else
		return damages
	end
end

-- Apply shield scale to damages (AFTER PERK SCALE)
local function applyShieldScaleToDamages(stat, stat2, damages)
	local shieldScale = getProperty(stat, stat2, 'damage_shield_scale', 0)
	if shieldScale > 1 then
		return table.mapValues(damages, function(damage)
			return apex.calcDisruptorDamage(damage, shieldScale)
		end)
	else
		return damages
	end
end

-- Calc DPSs
local function calcDPSs(stat, stat2, damages, firerate)
	local pellets = getProperty(stat, stat2, 'pellet', 1)
	--[[if pellets >= 2 then
		return table.map(damages, function(key, damage)
			if aw.stringends(key, 'max') then
				return damage * firerate * pellets
			else
				return damage * firerate
			end
		end)
	else]]
		return table.mapValues(damages, function(damage)
			return damage * firerate * pellets
		end)
	--end
end

-- Calc firerates
local function calcFirerates(stat, stat2, chargeRate)
	chargeRate = chargeRate or 0
	
	local charge    = getProperty(stat, stat2, 'charge', 0)
	local firerate
	local rechamber = getMultipleNumberProperty(stat, stat2, 'rechamber', nil)
	if rechamber then
		firerate = getProperty(stat, stat2, 'firerate', 1)
	else
		firerate = getMultipleNumberProperty(stat, stat2, 'firerate', { 1, 1.15, 1.25, 1.35 })
	end
	
	if proto.validateTypes(stat, MultipleBurstProto) then
		firerate = {
			apex.calcBurstAverageFirerate(stat.burst_count, stat.burst_delay[1], stat.firerate),
			apex.calcBurstAverageFirerate(stat.burst_count, stat.burst_delay[2], stat.firerate),
			apex.calcBurstAverageFirerate(stat.burst_count, stat.burst_delay[3], stat.firerate),
			apex.calcBurstAverageFirerate(stat.burst_count, stat.burst_delay[4], stat.firerate),
		}
	elseif rechamber then
		if charge > 0 then
			firerate = {
				apex.calcRechamberFirerate(firerate, rechamber[1] + chargeRate * charge),
				apex.calcRechamberFirerate(firerate, rechamber[2] + chargeRate * charge),
				apex.calcRechamberFirerate(firerate, rechamber[3] + chargeRate * charge),
				apex.calcRechamberFirerate(firerate, rechamber[4] + chargeRate * charge),
			}
		else
			firerate = {
				apex.calcRechamberFirerate(firerate, rechamber[1]),
				apex.calcRechamberFirerate(firerate, rechamber[2]),
				apex.calcRechamberFirerate(firerate, rechamber[3]),
				apex.calcRechamberFirerate(firerate, rechamber[4]),
			}
		end
	end
	return firerate
end

-- Calc firerate
local function calcFirerate(stat, stat2, chargeRate)
	chargeRate = chargeRate or math.max(0, getProperty(stat, stat2, 'charge_minimum', 0))
	
	local charge    = getProperty(stat, stat2, 'charge', 0)
	local firerate  = getProperty(stat, stat2, 'firerate', 1)
	local raise     = getProperty(stat, stat2, 'raise', 0)
	local rechamber = getProperty(stat, stat2, 'rechamber', 0)
	local semiauto  = getBooleanProperty(stat, stat2, 'is_semi_auto', false)
	
	if proto.validateTypes(stat, BurstProto) then
		firerate = apex.calcBurstAverageFirerate(stat.burst_count, stat.burst_delay, stat.firerate)
	elseif rechamber > 0 then
		if charge > 0 then
			firerate = apex.calcRechamberFirerate(firerate, rechamber + chargeRate * charge)
		else
			firerate = apex.calcRechamberFirerate(firerate, rechamber)
		end
	elseif charge > 0 then
		firerate = apex.calcRechamberFirerate(firerate, chargeRate * charge)
	elseif semiauto and raise > 0 then
		firerate = apex.calcRechamberFirerate(firerate, raise)
	end
	return firerate
end

local function renderDPSRow(tbl, name, dps, structure, opts)
	local row = tbl:tag('tr')
	if aw.isNumberAndGreaterThanOrEqualToX(structure.offset, 1) then
		for i = 1, structure.offset do
			local hasLeftBorder = structure.styles and structure.styles[i] and structure.styles[i].leftBorder
			if hasLeftBorder then
				row:tag('th')
					:css(leftBorderWithoutTopRightBorder)
					:wikitext('&nbsp;')
			else
				row:tag('th')
					:css('border', '0 none transparent')
					:wikitext('&nbsp;')
			end
		end
	end
	row:tag('th')
		:attrIf(aw.isNumber(structure.colspan), { colspan = structure.colspan })
		:cssIf(aw.isNumberAndGreaterThanZero(structure.offset), leftBorder)
		:wikitext(name)
	
	if opts.legsoff then
		row:tag('td')
			:attr('align', 'right')
			:wikitext(opts.format(dps.bodymax, dps.bodymin))
	elseif dps.legsmax == dps.bodymax and dps.legsmin == dps.bodymin then
		row:tag('td')
			:attr({ align = 'right', colspan = 2 })
			:wikitext(opts.format(dps.bodymax, dps.bodymin))
			:done()
	else
		row
			:tag('td')
				:attr('align', 'right')
				:wikitext(opts.format(dps.legsmax, dps.legsmin))
				:done()
			:tag('td')
				:attr('align', 'right')
				:wikitext(opts.format(dps.bodymax, dps.bodymin))
	end
	
	row
		:tag('td')
			:attr('align', 'right')
			:wikitext(opts.format(dps.hed3max, dps.hed3min))
			:done()
		:tag('td')
			:attr('align', 'right')
			:wikitext(opts.format(dps.hed2max, dps.hed2min))
			:done()
		:tag('td')
			:attr('align', 'right')
			:wikitext(opts.format(dps.hed1max, dps.hed1min))
end

-- Render the header node
local function renderHeader(tbl, opts)
	local header = tbl:tag('tr'):tag('th')
		:attrIf(opts.structure.levels > 1, { colspan = opts.structure.levels })
		:wikitext('')
		:done()
	if opts.legsoff then
		header:tag('th'):wikitext(opts.cfg.part.bodylegs)
	else
		header
			:tag('th'):wikitext(opts.cfg.part.legs):done()
			:tag('th'):wikitext(opts.cfg.part.body)
	end
	header
		:tag('th'):wikitext(iu.gear('helmet', 3))
		:tag('th'):wikitext(iu.gear('helmet', 2))
		:tag('th'):wikitext(iu.gear('helmet', 1))
end

-- Render the DPS section for Shotguns
local function renderShotgunDPSSection(tbl, name, stat, baseDamages, chargeLevels, firerates, perk, opts)
	local perkDamages = applyPerkToDamages(baseDamages, perk, opts.round)
	
	-- Normal
	local dps = calcDPSs(stat, empty, perkDamages, firerates[1])
	renderDPSRow(tbl, name, dps, opts.structure.root, opts)
	
	-- w/bolt lvl1
	local label = iu.attachment('ショットガンボルト', 1)
	dps = calcDPSs(stat, empty, perkDamages, firerates[2])
	renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
	
	-- w/bolt lvl2
	label = iu.attachment('ショットガンボルト', 2)
	dps = calcDPSs(stat, empty, perkDamages, firerates[3])
	renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
	
	-- w/bolt lvl3
	label = iu.attachment('ショットガンボルト', 3)
	dps = calcDPSs(stat, empty, perkDamages, firerates[4])
	renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
	
	
	-- Hop-ups
	for _, metadata in ipairs(opts.hopups) do
		local label = iu.hopup(metadata.key)
		local hopupStat    = stat[metadata.key]
		local baseDamages  = calcBaseDamages(hopupStat, stat)
		local perkDamages  = applyPerkToDamages(baseDamages, perk, opts.round)
		local chargeLevels = getProperty(hopupStat, stat, 'charge_levels', 1)
		local firerates     = calcFirerates(hopupStat, stat)
		
		-- Normal
		local dps = calcDPSs(hopupStat, stat, perkDamages, firerates[1])
		renderDPSRow(tbl, label, dps, opts.structure.level2, opts)
		
		-- w/bolt lvl1
		label = iu.attachment('ショットガンボルト', 1)
		dps = calcDPSs(hopupStat, stat, perkDamages, firerates[2])
		renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
		
		-- w/bolt lvl2
		label = iu.attachment('ショットガンボルト', 2)
		dps = calcDPSs(hopupStat, stat, perkDamages, firerates[3])
		renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
		
		-- w/bolt lvl3
		label = iu.attachment('ショットガンボルト', 3)
		dps = calcDPSs(hopupStat, stat, perkDamages, firerates[4])
		renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
	end
end

-- Render the DPS section with shield/unshield scale
local function renderShieldDPSSection(tbl, name, stat, stat2, baseDamages, firerate, perk, firstStructure, structure, opts)
	local unshldDPS = calcDPSs(stat, stat2, applyPerkToDamages(applyUnshieldScaleToDamages(stat, stat2, baseDamages), perk, opts.round), firerate)
	local shieldDPS = calcDPSs(stat, stat2, applyPerkToDamages(applyShieldScaleToDamages(stat, stat2, baseDamages), perk, opts.round), firerate)
	local lvl1DPS = {}
	local lvl2DPS = {}
	local lvl3DPS = {}
	local lvl5DPS = {}
	for key in pairs(unshldDPS) do
		lvl1DPS[key] = (2 * unshldDPS[key] +     shieldDPS[key]) / 3
		lvl2DPS[key] = (4 * unshldDPS[key] + 3 * shieldDPS[key]) / 7
		lvl3DPS[key] = (    unshldDPS[key] +     shieldDPS[key]) / 2
		lvl5DPS[key] = (4 * unshldDPS[key] + 5 * shieldDPS[key]) / 9
	end
	
	-- w/o shield
	renderDPSRow(tbl, name, unshldDPS, firstStructure, opts)
	
	-- w/shield lvl1
	local label = iu.gear('evo_shield', 1)
	renderDPSRow(tbl, label, lvl1DPS, structure, opts)
	
	-- w/shield lvl2
	label = iu.gear('evo_shield', 2)
	renderDPSRow(tbl, label, lvl2DPS, structure, opts)
	
	-- w/shield lvl3
	label = iu.gear('evo_shield', 3)
	renderDPSRow(tbl, label, lvl3DPS, structure, opts)
	
	-- w/shield lvl5
	label = iu.gear('evo_shield', 5)
	renderDPSRow(tbl, label, lvl5DPS, structure, opts)
end

-- Render the DPS section (Normal weapons)
local function renderNormalDPSSection(tbl, name, stat, baseDamages, chargeLevels, firerate, perk, opts)
	local perkDamages = applyPerkToDamages(baseDamages, perk, opts.round)
	
	-- Normal
	if not (opts.special and #opts.hopups > 0 and opts.hopups[1].key == 'disruptor_rounds') then
		local dps = calcDPSs(stat, empty, perkDamages, firerate)
		renderDPSRow(tbl, name, dps, opts.structure.root, opts)
	end
	
	-- Altfire
	if opts.flags.mode_altfire then
		local label = opts.cfg.altfire
		local firerate = stat.altfire.firerate
		local dps      = calcDPSs(stat.altfire, stat, perkDamages, firerate)
		renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
	end
	
	-- Amped mode
	if opts.flags.mode_amped then
		local label = iu.item('シールドセル')
		local baseDamages = calcBaseDamages(stat.energized, stat, chargeRate)
		local perkDamages = applyPerkToDamages(baseDamages, perk, opts.round)
		local dps         = calcDPSs(stat.energized, stat, perkDamages, firerate)
		renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
	end
	
	-- Revved Up mode
	if opts.flags.mode_revvedup then
		local label = iu.grenade('テルミットグレネード')
		local firerate = stat.firerate_revvedup
		local dps      = calcDPSs(stat, empty, perkDamages, firerate)
		renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
	end
	
	-- Chargefire
	if chargeLevels == 2 then
		for _, chargeRate in ipairs(steplessChargeRates) do
			local label
			if chargeRate >= 1 then
				label = opts.cfg.charge_stepless_max
			elseif chargeRate == 0.5 then
				label = opts.cfg.charge_stepless_half
			else
				label = string.format(opts.cfg.charge_stepless_format, 100 * chargeRate)
			end
			local baseDamages = calcBaseDamages(stat, empty, chargeRate)
			local perkDamages = applyPerkToDamages(baseDamages, perk, opts.round)
			local firerate    = calcFirerate(stat, empty, chargeRate)
			local dps         = calcDPSs(stat, empty, perkDamages, firerate)
			renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
		end
	elseif chargeLevels > 2 then
		for chargeLevel = 1, chargeLevels - 1 do
			local chargeRate = chargeLevel / (chargeLevels - 1)
			local label    = aw.getQuantityString(opts.cfg.charge_step_quantity, chargeLevel)
			local firerate = calcFirerate(stat, empty, chargeRate)
			local dps      = calcDPSs(stat, empty, perkDamages, firerate)
			renderDPSRow(tbl, label, dps, opts.structure.extra, opts)
		end
	end
	
	-- Hop-ups
	for _, metadata in ipairs(opts.hopups) do
		local label = iu.hopup(metadata.key)
		local hopupStat    = stat[metadata.key]
		local baseDamages  = calcBaseDamages(hopupStat, stat)
		local perkDamages  = applyPerkToDamages(baseDamages, perk, opts.round)
		local chargeLevels = getProperty(hopupStat, stat, 'charge_levels', 1)
		local firerate     = calcFirerate(hopupStat, stat)
		
		-- Hammerpoint Rounds / Disruptor Rounds
		if metadata.key == 'hammerpoint_rounds' or metadata.key == 'disruptor_rounds' then
			local item, root, next
			if opts.special then
				item = name
				root = opts.structure.root
				next = opts.structure.level2
			else
				item = label
				root = opts.structure.level2
				next = opts.structure.level3
			end
			renderShieldDPSSection(tbl, item, hopupStat, stat, baseDamages, firerate, perk, root, next, opts)
		
		-- Other
		else
			local dps = calcDPSs(hopupStat, stat, perkDamages, firerate)
			renderDPSRow(tbl, label, dps, opts.structure.level2, opts)
		
			-- Amped mode
			if opts.flags.mode_amped then
				local label = iu.item('シールドセル')
				local mergedStat  = aw.mergeTable(hopupStat, stat.energized)
				local baseDamages = calcBaseDamages(mergedStat, stat, chargeRate)
				local perkDamages = applyPerkToDamages(baseDamages, perk, opts.round)
				local dps         = calcDPSs(mergedStat, stat, perkDamages, firerate)
				renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
			end
			
			-- Chargefire
			if chargeLevels == 2 then
				for _, chargeRate in ipairs(steplessChargeRates) do
					local label
					if chargeRate >= 1 then
						label = opts.cfg.charge_stepless_max
					elseif chargeRate == 0.5 then
						label = opts.cfg.charge_stepless_half
					else
						label = string.format(opts.cfg.charge_stepless_format, 100 * chargeRate)
					end
					local baseDamages  = calcBaseDamages(hopupStat, stat, chargeRate)
					local perkDamages  = applyPerkToDamages(baseDamages, perk, opts.round)
					local firerate     = calcFirerate(hopupStat, stat, chargeRate)
					local dps          = calcDPSs(hopupStat, stat, perkDamages, firerate)
					renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
				end
			elseif chargeLevels > 2 then
				for chargeLevel = 1, chargeLevels - 1 do
					local chargeRate = chargeLevel / (chargeLevels - 1)
					local label    = aw.getQuantityString(opts.cfg.charge_step_quantity, chargeLevel)
					local firerate = calcFirerate(hopupStat, stat, chargeRate)
					local dps      = calcDPSs(hopupStat, stat, perkDamages, firerate)
					renderDPSRow(tbl, label, dps, opts.structure.level3, opts)
				end
			end
		end
	end
end

local function numformat(num)
	if num >= 100 then
		num = aw.roundx(num, 1)
		local int = math.floor(num)
		return string.format('%d<span class="text-smaller text-secondary">.%01.0f</span>', int, 10 * (num - int))
	elseif num >= 10 then
		num = aw.roundx(num, 2)
		local int = math.floor(num)
		return string.format('%d<span class="text-smaller text-secondary">.%02.0f</span>', int, 100 * (num - int))
	else
		num = aw.roundx(num, 3)
		local int = math.floor(num)
		return string.format('%d<span class="text-smaller text-secondary">.%03.0f</span>', int, 1000 * (num - int))
	end
end

local function numformatmin(num)
	if num >= 100 then
		num = aw.round(num)
		local int = math.floor(num)
		return string.format('%d', int)
	elseif num >= 10 then
		num = aw.roundx(num, 1)
		local int = math.floor(num)
		return string.format('%d<span class="text-smaller text-secondary">.%01.0f</span>', int, 10 * (num - int))
	else
		num = aw.roundx(num, 2)
		local int = math.floor(num)
		return string.format('%d<span class="text-smaller text-secondary">.%02.0f</span>', int, 100 * (num - int))
	end
end

function p.renderDPS(stat, shieldinfo, lang)
	local cfg  = cfg[lang].dps
	local opts = {
		cfg     = cfg,
		flags   = {},
		format  = function(max, min)
			if max ~= min then
				return string.format(cfg.complex, numformatmin(min), numformat(max))
			else
				return string.format(cfg.simple,  numformat(max))
			end
		end,
		hopups  = {},
		legsoff = (stat.damage_legs_scale or 1) == 1,
		round   = aw.getAsBoolean(stat.damage.round, false),
		special = aw.stringstarts(stat.ammo, 'special_'),
	}
	local baseDamages  = calcBaseDamages(stat, empty)
	local chargeLevels = getProperty(stat, empty, 'charge_levels', 1)
	local tbl = mw.html.create('table'):addClass('intable')
	
	-- Shotgun
	if stat.ammo == 'shotgun' then
		local firerates = calcFirerates(stat, empty)
		
		local level = 2
		if (Hopup.double_tap_trigger.enabled or opts.special) and proto.validateTypes(stat.double_tap_trigger, MultipleBurstProto) then
			level = math.max(level, 3)
			table.insert(opts.hopups, {
				key = 'double_tap_trigger',
			})
		end
		opts.structure = structures[level]
		
		renderHeader(tbl, opts)
		renderShotgunDPSSection(tbl, opts.cfg.perk.normal, stat, baseDamages, chargeLevels, firerates, 1, opts)
		
		-- Low Profile
		if aw.isNumber(shieldinfo.lowprofile) and shieldinfo.lowprofile > 1 then
			renderShotgunDPSSection(tbl, opts.cfg.perk.lowprofile, stat, baseDamages, chargeLevels, firerates, shieldinfo.lowprofile, opts)
		end
		
		-- Fortified
		if aw.isNumber(shieldinfo.fortified) and shieldinfo.fortified < 1 then
			renderShotgunDPSSection(tbl, opts.cfg.perk.fortified, stat, baseDamages, chargeLevels, firerates, shieldinfo.fortified, opts)
		end
	
	-- Other
	else
		local firerate = calcFirerate(stat, empty)
		
		local level = 1
		if proto.validateTypes(stat, AltfireProto) then
			level = math.max(level, 2)
			opts.flags.mode_altfire = true
		end
		if proto.validateTypes(stat, RevvedUpProto) then
			level = math.max(level, 2)
			opts.flags.mode_revvedup = true
		end
		if (Hopup.selectfire_receiver.enabled or opts.special) and type(stat.selectfire_receiver) == 'table' then
			level = math.max(level, 2)
			table.insert(opts.hopups, {
				key = 'selectfire_receiver',
			})
		end
		if Hopup.hammerpoint_rounds.enabled and proto.validateTypes(stat.hammerpoint_rounds, HammerpointRoundsProto) then
			level = math.max(level, 3)
			table.insert(opts.hopups, {
				key = 'hammerpoint_rounds',
			})
		end
		if (Hopup.disruptor_rounds.enabled or opts.special) and proto.validateTypes(stat.disruptor_rounds, DisruptorRoundsProto) then
			if opts.special then
				level = math.max(level, 2)
			else
				level = math.max(level, 3)
			end
			table.insert(opts.hopups, {
				key = 'disruptor_rounds',
			})
		end
		if Hopup.anvil_receiver.enabled and type(stat.anvil_receiver) == 'table' then
			level = math.max(level, 2)
			table.insert(opts.hopups, {
				key = 'anvil_receiver',
			})
		end
		if (Hopup.double_tap_trigger.enabled or opts.special) and proto.validateTypes(stat.double_tap_trigger, BurstProto) then
			level = math.max(level, 2)
			table.insert(opts.hopups, {
				key = 'double_tap_trigger',
			})
		end
		if Hopup.shatter_caps.enabled and proto.validateTypes(stat.shatter_caps, ShatterCapsProto) then
			level = math.max(level, 2)
			table.insert(opts.hopups, {
				key = 'shatter_caps',
			})
		end
		if Hopup.deadeyes_tempo.enabled and type(stat.deadeyes_tempo) == 'table' then
			level = math.max(level, 2)
			table.insert(opts.hopups, {
				key = 'deadeyes_tempo',
			})
		end
		if proto.validateTypes(stat.energized, DamageProto) then
			level = level + 1
			opts.flags.mode_amped = true
		elseif chargeLevels >= 2 then
			level = level + 1
		end
		opts.structure = structures[level]
	
		renderHeader(tbl, opts)
		renderNormalDPSSection(tbl, opts.cfg.perk.normal, stat, baseDamages, chargeLevels, firerate, 1, opts)
		
		-- Low Profile
		if aw.isNumber(shieldinfo.lowprofile) and shieldinfo.lowprofile > 1 then
			renderNormalDPSSection(tbl, opts.cfg.perk.lowprofile, stat, baseDamages, chargeLevels, firerate, shieldinfo.lowprofile, opts)
		end
		
		-- Fortified
		if aw.isNumber(shieldinfo.fortified) and shieldinfo.fortified < 1 then
			renderNormalDPSSection(tbl, opts.cfg.perk.fortified, stat, baseDamages, chargeLevels, firerate, shieldinfo.fortified, opts)
		end
	end
	
	return tbl
end

function p._main(name, lang)
	lang = lang or 'ja'
	
	local stat = mw.loadData('Module:Stat/Weapon')[name]
	local sld  = require('Module:Stat/Shield')['reinforcehelmets']
	local node = p.renderDPS(stat, sld, lang)
	
	local div = mw.html.create('div')
		:addClass('tpl-infobox-right')
		:addClass('tpl-weapon')
	if aw.stringstarts(stat.ammo, "special_") then
		div:addClass('tpl-weapon-special')
	else
		div:addClass('tpl-weapon-' .. stat.ammo)
	end
	div
		:tag('div')
			:addClass('tpl-weapon-header')
			:wikitext(name .. iu.ammo(stat.ammo, { size = 40 }))
			:done()
		:tag('table')
			:tag('tr')
				:tag('th'):wikitext('DPS'):done()
				:tag('td'):done()
			:tag('tr')
				:tag('td'):attr('colspan', 2):node(node)
	return tostring(div)
end

return p