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

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

提供:Apex Data
ナビゲーションに移動 検索に移動
(拡散のデータがテキストのみの場合の表示を改善)
(アンビルレシーバーが出現しない場合にアンビルレシーバー用の拡散を非表示するように対処)
 
(同じ利用者による、間の71版が非表示)
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 iu = require('Module:Utility/Image')
local apex  = require('Module:Utility/ApexLibrary')
local nu = require('Module:Utility/Name')
local iu   = require('Module:Utility/Image')
local inu  = require('Module:Utility/ImageWithName')
local nu   = require('Module:Utility/Name')
local proto = require('Module:Utility/Prototypes')
local proto = require('Module:Utility/Prototypes')
local StringBuilder = require('Module:Utility/StringBuilder')
local formatter -- lazily initialized
local formatter -- lazily initialized
local getArgs -- lazily initialized
local getArgs -- lazily initialized
local function calcBurstAverageRPS(stat, delay)
delay = delay or stat.firerate.burst_delay
return stat.mode.burst / ((stat.mode.burst - 1) / stat.firerate.burst + delay)
end
local function calcRechamberRPS(firerate, rechamber)
rechamber = rechamber or firerate.single_rechamber
return 1 / (1 / firerate.single + rechamber)
end


local function createCellInRow(tbl, name)
local function createCellInRow(tbl, name)
37行目: 31行目:
opts.separator = opts.separator or ' - '
opts.separator = opts.separator or ' - '
local row = tbl:tag('tr')
local row = tbl:tag('tr')
:addClassIf(opts.class and type(opts.class) == 'string', opts.class)
:addClassIf(type(opts.class)  == 'string', opts.class)
:addClassIf(type(opts.rarity) == 'string', 'row-rarity-one disp-rarity-' .. (opts.rarity or 'common'))
if name ~= nil then
if name ~= nil then
62行目: 57行目:
:addClass('cell-type-number')
:addClass('cell-type-number')
:attrIf(opts.align and type(opts.align) == 'string', { align = opts.align })
:attrIf(opts.align and type(opts.align) == 'string', { align = opts.align })
:wikitext(formatter:common(common))
:tag('span')
:addClass('text-rarity')
:addClass('text-rarity-common')
:wikitext(common)
end
end
73行目: 71行目:
:addClass('cell-type-number')
:addClass('cell-type-number')
:attrIf(opts.align and type(opts.align) == 'string', { align = opts.align })
:attrIf(opts.align and type(opts.align) == 'string', { align = opts.align })
:wikitext(formatter:rare(rare))
:tag('span')
:addClass('text-rarity')
:addClass('text-rarity-rare')
:wikitext(rare)
end
end
84行目: 85行目:
:addClass('cell-type-number')
:addClass('cell-type-number')
:attrIf(opts.align and type(opts.align) == 'string', { align = opts.align })
:attrIf(opts.align and type(opts.align) == 'string', { align = opts.align })
:wikitext(formatter:epic(epic))
:tag('span')
:addClass('text-rarity')
:addClass('text-rarity-epic')
:wikitext(epic)
end
end
116行目: 120行目:


local function renderAmmo(tbl, cfg, stat, lang)
local function renderAmmo(tbl, cfg, stat, lang)
local ammo = nu.ammo(stat, lang)
local classSuffix
if aw.stringstarts(stat, 'special_') then
classSuffix = 'special'
else
classSuffix = stat
end
local item
if lang == 'ja' then
item = string.format(
'%s [[弾薬#%s|<span class="text-ammo text-ammo-%s">%s</span>]][[Category:%s]]',
iu.ammo(stat, { size = 24 }),
ammo,
classSuffix,
ammo,
ammo)
else
item = string.format(
'%s <span class="text-ammo text-ammo-%s">%s</span>[[Category:%s]]',
iu.ammo(stat, { size = 24 }),
classSuffix,
ammo,
ammo)
end
createCellInRow(tbl, cfg.name)
createCellInRow(tbl, cfg.name)
:addClass('cell-ammo')
:addClass('cell-ammo')
:wikitext(item)
:wikitext(inu.ammo(stat, { lang = lang, size = 24 }))
end
end


160行目: 139行目:
end
end


local function renderMode(tbl, cfg, stat)
local function appendMode(builder, cfg, stat, base)
local builder = require('Module:Utility/StringBuilder').new()
if stat.is_semi_auto or (stat.is_semi_auto == nil and base.is_semi_auto) then
if stat.burst > 1 then
local burstCount = stat.burst_count or (base and base.burst_count) or 1
builder:appendFormat(cfg.burst, stat.burst)
if aw.isNumberAndGreaterThanOrEqualToX(burstCount, 2) then
builder:append(cfg.burst_category)
builder:appendFormat(cfg.burst, burstCount)
end
builder:append(cfg.burst_category)
else
if stat.auto then
builder:append(cfg.single, cfg.single_category)
if not builder:isEmpty() then
builder:append(cfg.separator)
end
end
else
builder:append(cfg.auto, cfg.auto_category)
builder:append(cfg.auto, cfg.auto_category)
end
end
end
if stat.single then
 
if not builder:isEmpty() then
local function renderMode(tbl, cfg, stat, isSpecial)
builder:append(cfg.separator)
local builder = StringBuilder.new()
end
appendMode(builder, cfg, stat)
builder:append(cfg.single, cfg.single_category)
if stat.altfire then
builder:append(cfg.separator)
appendMode(builder, cfg, stat.altfire, stat)
end
if (Hopup.selectfire_receiver.enabled or isSpecial) and stat.selectfire_receiver then
builder:append(cfg.separator, '<span class="block-rarity disp-rarity-epic">', iu.hopup('selectfire_receiver'), ' ')
appendMode(builder, cfg, stat.selectfire_receiver, stat)
builder:append('</span>')
end
if (Hopup.double_tap_trigger.enabled or isSpecial) and stat.double_tap_trigger then
builder:append(cfg.separator, '<span class="block-rarity disp-rarity-epic">', iu.hopup('double_tap_trigger'), ' ')
appendMode(builder, cfg, stat.double_tap_trigger, stat)
builder:append('</span>')
end
end
renderRow(tbl, cfg.name, tostring(builder))
renderRow(tbl, cfg.name, tostring(builder))
end
end


local function getDamageText(stat)
local HammerpointRoundsProto = {
if aw.isNumber(stat.pellet) then
hammerpoint_rounds = {
if aw.isNumberAndGreaterThanZero(stat.damage.charged) then
damage_unshielded_scale = proto.NumberRange(1),
return string.format(
},
'<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d <span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d',
}
stat.damage.base,
local DisruptorRoundsProto = {
stat.pellet,
disruptor_rounds = {
stat.damage.charged,
damage_shield_scale = proto.NumberRange(1),
stat.pellet)
},
else
}
return string.format(
local function getDamageText(stat, root, isSpecial)
'<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d',
local builder = StringBuilder.new()
stat.damage.base,
local rarity  = isSpecial and 'heirloom'
stat.pellet)
local damage, pellet
if root then
damage = stat.damage or root.damage
pellet = stat.pellet or root.pellet or 1
else
damage = stat.damage
pellet = stat.pellet or 1
end
if aw.isNumberAndGreaterThanOrEqualToX(pellet, 2) then
builder:appendFormat(
'<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d',
damage.base,
pellet)
if aw.isNumberAndGreaterThanZero(damage.charged) then
builder:appendFormat(
'&thinsp;&rarr;&thinsp;<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d',
damage.charged,
pellet)
elseif proto.validateTypes(stat, HammerpointRoundsProto) then
if not rarity then
rarity = Hopup.hammerpoint_rounds.rarity
end
builder:appendFormat(
'&thinsp;&rarr;&thinsp;<span class="block-rarity disp-rarity-%s text-rarity text-rarity-%s">%s \'\'\'<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d\'\'\'</span>',
rarity,
rarity,
iu.hopup('hammerpoint_rounds', { rarity = rarity }),
math.floor(damage.base * stat.hammerpoint_rounds.damage_unshielded_scale),
pellet)
end
end
else
else
if aw.isNumberAndGreaterThanZero(stat.damage.amped) then
if aw.isNumberAndGreaterThanZero(damage.amped) then
return string.format(
builder:appendFormat(
'<span class="text-type-number">%s</span> <span class="text-separator">/</span> %s <span class="text-type-number">%s</span>',
'<span class="text-type-number">%s</span> <span class="text-separator">/</span> %s&nbsp;<span class="text-type-number">%s</span>',
stat.damage.base,
damage.base,
iu.item('シールドセル'),
iu.item('シールドセル'),
stat.damage.amped)
damage.amped)
elseif aw.isNumberAndGreaterThanZero(stat.damage.charged) then
elseif aw.isNumberAndGreaterThanZero(damage.charged) then
return string.format(
builder:appendFormat(
'<span class="text-type-number">%s</span> <span class="text-type-number">%s</span>',
'<span class="text-type-number">%s</span>&thinsp;&rarr;&thinsp;<span class="text-type-number">%s</span>',
stat.damage.base,
damage.base,
stat.damage.charged)
damage.charged)
elseif proto.validateTypes(stat, HammerpointRoundsProto) then
if not rarity then
rarity = Hopup.hammerpoint_rounds.rarity
end
builder:appendFormat(
'<span class="text-type-number">%s</span>&thinsp;&rarr;&thinsp;<span class="block-rarity disp-rarity-%s text-rarity text-rarity-%s">%s \'\'\'<span class="text-type-number">%s</span>\'\'\'</span>',
damage.base,
rarity,
rarity,
iu.hopup('hammerpoint_rounds', { rarity = rarity }),
math.floor(damage.base * stat.hammerpoint_rounds.damage_unshielded_scale))
elseif proto.validateTypes(stat, DisruptorRoundsProto) then
if not rarity then
rarity = Hopup.disruptor_rounds.rarity
end
builder:appendFormat(
'<span class="block-rarity disp-rarity-%s text-rarity text-rarity-%s">%s \'\'\'<span class="text-type-number">%s</span>\'\'\'</span>&thinsp;&rarr;&thinsp;<span class="text-type-number">%s</span>',
rarity,
rarity,
iu.hopup('disruptor_rounds', { rarity = rarity }),
math.floor(damage.base * stat.disruptor_rounds.damage_shield_scale),
damage.base)
else
else
return string.format('<span class="text-type-number">%s</span>', stat.damage.base)
builder:appendFormat('<span class="text-type-number">%s</span>', damage.base)
end
end
end
end
return tostring(builder)
end
end


local function renderHeadRow(tbl, name, head, cfg, nolist)
local function renderHeadRow(tbl, name, head, cfg, nolist)
local hlm1 = 0.2 + 0.8 * head
local hlm2 = 0.4 + 0.6 * head
local hlm3 = 0.5 + 0.5 * head
local opts = { align = 'left', footer = cfg.unit }
local opts = { align = 'left', footer = cfg.unit }
if nolist then
if nolist then
229行目: 267行目:
tbl,
tbl,
name,
name,
nil, hlm1, hlm2, hlm3,
nil,
apex.calcHeadshotMultiplier(head, apex.HEAD_HLMLV1),
apex.calcHeadshotMultiplier(head, apex.HEAD_HLMLV2),
apex.calcHeadshotMultiplier(head, apex.HEAD_HLMLV3),
opts)
opts)
end
end


local function renderLegsRow(tbl, cfg, stat)
local function renderHeadEffectiveRange(tbl, cfg, stat, root)
local headDist = stat.damage_head_distance or root.damage_head_distance
if aw.isNumberAndGreaterThanZero(headDist) then
local text = string.format(cfg.head_effective_range, 0.0254 * headDist)
tbl:tag('tr')
:addClass('no-list-style')
:tag('td')
:attr('align', 'left')
:attr('colspan', 7)
:wikitext(text)
end
end
 
local function renderLegsRow(tbl, cfg, stat, root)
local legs = stat.damage_legs_scale or root.damage_legs_scale
local text
local text
if aw.isNumberAndGreaterThanZero(stat.legshot_charged) then
if aw.isNumberAndGreaterThanZero(stat.legshot_charged) then
text = string.format(
text = string.format(
'<span class="text-type-number">%s</span> <span class="text-type-number">%s</span>%s',
'<span class="text-type-number">%s</span> &rarr; <span class="text-type-number">%s</span>%s',
stat.legshot,
legs,
stat.legshot_charged,
stat.legshot_charged,
cfg.unit)
cfg.unit)
244行目: 299行目:
text = string.format(
text = string.format(
'<span class="text-type-number">%s</span>%s',
'<span class="text-type-number">%s</span>%s',
stat.legshot,
legs,
cfg.unit)
cfg.unit)
end
end
260行目: 315行目:
damage = {
damage = {
base    = proto.NumberRange(1),
base    = proto.NumberRange(1),
headshot = proto.NumberRange(1),
legshot  = proto.NumberRange(0, 1),
},
},
damage_head_scale = proto.NumberRange(1),
damage_legs_scale = proto.NumberRange(0, 1),
}
}
local ShatterCapsProto = {
local ShatterCapsProto = {
damage = {
damage = {
base    = proto.NumberRange(1),
base    = proto.NumberRange(1),
headshot = proto.NumberRange(1),
legshot  = proto.NumberRange(0, 1),
},
},
damage_head_scale = proto.NumberRange(1),
damage_legs_scale = proto.NumberRange(0, 1),
pellet = proto.NumberRange(1),
pellet = proto.NumberRange(1),
}
}
local damageHopups = {
local damageHopups = {
{
name = 'selectfire_receiver',
proto = DamageProto,
textclass = 'text-rarity-epic',
dispclass = 'disp-rarity-epic',
},
{
{
name = 'anvil_receiver',
name = 'anvil_receiver',
286行目: 347行目:
},
},
}
}
local function renderDamage(tbl, cfg, stat)
local function renderDamage(tbl, cfg, stat, isSpecial)
if not proto.validateTypes(stat, DamageProto) then
if not proto.validateTypes(stat, DamageProto) then
return
return
end
end
local cattext = string.format(cfg.head_category, stat.damage.headshot_charged or stat.damage.headshot)
local cattext = string.format(cfg.head_category, stat.damage.headshot_charged or stat.damage_head_scale or 1)
local cell = createCellInRow(tbl, cfg.name)
local cell = createCellInRow(tbl, cfg.name)
cell:wikitext(cattext)
cell:wikitext(cattext .. getDamageText(stat, nil, isSpecial))
:wikitext(getDamageText(stat))
-- Headshot
local head = stat.damage_head_scale or 1
local intable = cell:tag('table')
:addClass('condensedtable')
:addClass('listtable')
renderHeadRow(intable, cfg.head, head, cfg)
local hlmc = stat.damage.headshot_charged or 1
if hlmc > 1 then
local nameC = '→&nbsp;'
renderHeadRow(intable, nameC, hlmc, cfg, true)
end
-- Headshot
local sprm = stat.damage.skullpiercer_rifling or 1
local sprm = stat.damage.skullpiercer_rifling or 1
local hlmc = stat.damage.headshot_charged or 1
local intable =
--tbl:tag('tr'):tag('td'):attr('colspan', 2)
cell
:tag('table')
:addClass('condensedtable')
:addClass('listtable')
renderHeadRow(intable, cfg.head, stat.damage.headshot, cfg)
if sprm > 1 then
if sprm > 1 then
local nameS = iu.hopup('skullpiercer_rifling') .. '&nbsp;'
local nameS = iu.hopup('skullpiercer_rifling') .. '&nbsp;'
renderHeadRow(intable, nameS, sprm, cfg, true)
renderHeadRow(intable, nameS, sprm, cfg, true)
elseif hlmc > 1 then
local nameC = '→&nbsp;'
renderHeadRow(intable, nameC, hlmc, cfg, true)
end
end
renderHeadEffectiveRange(intable, cfg, stat, stat)
-- Legsshot
-- Legsshot
renderLegsRow(intable, cfg, stat.damage)
renderLegsRow(intable, cfg, stat, stat)
-- [Hop-Up] Anvil Receiver & Shatter Caps
-- [Hop-Up] Selectfire Receiver, Anvil Receiver & Shatter Caps
for _, v in ipairs(damageHopups) do
for _, v in ipairs(damageHopups) do
if proto.validateTypes(stat[v.name], v.proto) then
local hopupStat = stat[v.name]
if (Hopup[v.name].enabled or isSpecial) and proto.validateTypes(hopupStat, v.proto) then
local intblS = cell:tag('div')
local intblS = cell:tag('div')
:addClass('tpl-weapon-inbox')
:addClass('tpl-weapon-inbox')
:addClass(v.dispclass)
:addClass(specialOnly and 'disp-rarity-heirloom' or v.dispclass)
:wikitext(iu.hopup(v.name) .. '&nbsp;')
:wikitext(iu.hopup(v.name) .. '&nbsp;')
:tag('span')
:tag('span')
:addClass('text-rarity')
:addClass('text-rarity')
:addClass(v.textclass)
:addClass(specialOnly and 'text-rarity-heirloom' or v.textclass)
:wikitext(getDamageText(stat[v.name]))
:wikitext(getDamageText(hopupStat, stat))
:done()
:done()
:tag('table')
:tag('table')
:addClass('condensedtable')
:addClass('condensedtable')
:addClass('listtable')
:addClass('listtable')
renderHeadRow(intblS, cfg.head, stat[v.name].damage.headshot, cfg)
renderLegsRow(intblS, cfg, stat[v.name].damage)
local head = hopupStat.damage_head_scale or stat.damage_head_scale or 1
renderHeadRow(intblS, cfg.head, head, cfg)
renderHeadEffectiveRange(intblS, cfg, hopupStat, stat)
renderLegsRow           (intblS, cfg, hopupStat, stat)
end
end
end
end
end
end


local function renderMultipleFirerateRow(intbl, name, cfg, rps, opts)
local function renderFirerate(tbl, cfg, stat, lang)
opts = opts or {}
renderFormatRow(
intbl,
name .. cfg.rps.name,
string.format(cfg.rps.format, aw.roundx(rps[1], 2)),
string.format(cfg.rps.format, aw.roundx(rps[2], 2)),
string.format(cfg.rps.format, aw.roundx(rps[3], 2)),
string.format(cfg.rps.format, aw.roundx(rps[4], 2)),
{ class = opts.rpsClass or '', separator = cfg.rps.separator, footer = cfg.rps.unit })
renderFormatRow(
intbl,
cfg.rpm.name,
string.format(cfg.rpm.format, aw.comma(aw.roundx(60 * rps[1], 1))),
string.format(cfg.rpm.format, aw.comma(aw.roundx(60 * rps[2], 1))),
string.format(cfg.rpm.format, aw.comma(aw.roundx(60 * rps[3], 1))),
string.format(cfg.rpm.format, aw.comma(aw.roundx(60 * rps[4], 1))),
{ class = opts.rpmClass and 'all-secondary text-smaller ' .. opts.rpmClass or 'all-secondary text-smaller', headerAlign = 'right', separator = cfg.rpm.separator, footer = cfg.rpm.unit })
end
 
local function renderVariableFirerateRow(intbl, name, cfg, rps1, rps2, opts)
opts = opts or {}
intbl
:tag('tr')
:addClassIf(type(opts.rarity) == 'string', 'row-rarity-first disp-rarity-' .. (opts.rarity or 'common'))
:addClassIf(type(opts.class) == 'string',  opts.class)
:tag('th')
:wikitext(name)
:done()
:tag('td')
:addClass('cell-type-number')
:attr('align', 'right')
:wikitext(string.format(cfg.rps.format, rps1))
:done()
:tag('td')
:wikitext('&nbsp;→&nbsp;')
:done()
:tag('td')
:addClass('cell-type-number')
:attr('align', 'right')
:wikitext(string.format(cfg.rps.format, rps2))
:done()
:tag('td')
:wikitext(cfg.rps.unit)
:done()
:done()
:tag('tr')
:addClassIf(type(opts.rarity) == 'string', 'row-rarity-last disp-rarity-' .. (opts.rarity or 'common'))
:addClass('no-list-style')
:addClass('text-secondary')
:addClass('text-smaller')
:tag('th')
:attr('align', 'right')
:wikitext(cfg.rpm.name)
:done()
:tag('td')
:addClass('cell-type-number')
:wikitext(aw.comma(aw.roundx(60 * rps1, 1)))
:done()
:tag('td')
:wikitext('&nbsp;→&nbsp;')
:done()
:tag('td')
:addClass('cell-type-number')
:wikitext(aw.comma(aw.roundx(60 * rps2, 1)))
:done()
:tag('td')
:wikitext(cfg.rpm.unit)
end
 
local function renderFirerateRow(intbl, name, cfg, rps, opts)
opts = opts or {}
intbl:tag('tr')
:addClassIf(type(opts.rarity) == 'string', 'row-rarity-one disp-rarity-' .. (opts.rarity or 'common'))
:addClassIf(opts.class and type(opts.class) == 'string', opts.class)
:tag('th')
:wikitext(name)
:done()
:tag('td')
:addClass('cell-type-number')
:attr('align', 'right')
:wikitext(string.format(cfg.rps.format, rps))
:done()
:tag('td')
:wikitext(cfg.rps.unit)
:done()
:tag('td')
:tag('small')
:addClass('text-secondary')
:wikitext(cfg.rpm.name)
:done()
:done()
:tag('td')
:addClass('cell-type-number')
:attr('align', 'right')
:tag('small')
:addClass('text-secondary')
:wikitext(aw.comma(0.1 * aw.round(600 * rps)))
:done()
:done()
:tag('td')
:tag('small')
:addClass('text-secondary')
:wikitext(cfg.rpm.unit)
end
 
local function renderFirerateCell(cell, cfg, rps, opts)
opts = opts or {}
opts.header = opts.header or ''
cell:wikitext(string.format(
opts.header .. '<span class="text-type-number">' .. cfg.rps.format .. '</span>' .. cfg.rps.unit .. '<span class="text-secondary"><small>(<span class="text-type-number">%s</span>' .. cfg.rpm.unit .. '</small></span>',
rps,
aw.comma(0.1 * aw.round(600 * rps))))
end
 
local MultipleNumberProto = {
proto.NumberRange(0),
proto.NumberRange(0),
proto.NumberRange(0),
proto.NumberRange(0),
}
local AnvilReceiverProto = {
firerate = {
single = proto.NumberRange(0),
},
}
local DeadeyesTempoWithChargeProto = {
firerate = {
single_charged = proto.NumberRange(0),
},
}
local DeadeyesTempoWithRechamberProto = {
firerate = {
single_rechamber = proto.NumberRange(0),
},
}
local function renderFirerate(tbl, cfg, stat)
local firerate = stat.firerate
if firerate == nil then
return
end
local mode = stat.mode
local cell = createCellInRow(tbl, cfg.name)
local cell = createCellInRow(tbl, cfg.name)
local rps
local node, newcell = require('Module:WeaponInfobox/Firerate').renderFirerate(stat, lang)
if mode.auto then
if newcell then
if mode.single then
tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
local hasAnvil = proto.validateTypes(stat.anvil_receiver, AnvilReceiverProto)
if hasAnvil or firerate.auto ~= firerate.single then
local intbl
if firerate.auto ~= firerate.single then
intbl = cell:tag('table')
:addClass('condensedtable')
:addClass('listtable')
local singleRPS
if aw.isNumberAndGreaterThanZero(firerate.single_rechamber) then
singleRPS = calcRechamberRPS(firerate)
else
singleRPS = firerate.single
end
renderFirerateRow(intbl, cfg.auto, cfg, firerate.auto)
renderFirerateRow(intbl, cfg.single, cfg, singleRPS)
else
intbl = cell:tag('table')
:addClass('condensedtable')
renderFirerateRow(intbl, '', cfg, firerate.auto)
end
if hasAnvil then
intbl:addClass('raritytable')
renderFirerateRow(intbl, iu.hopup('anvil_receiver') .. '&nbsp;', cfg, stat.anvil_receiver.firerate.single, { rarity = 'legendary' })
end
return
end
elseif mode.burst > 1 then
if type(firerate.auto) == 'table' then
local newcell = tbl:tag('tr')
:tag('td')
:attr('colspan', 2)
renderFirerateCell(newcell:tag('ul'):tag('li'), cfg, firerate.burst, { header = cfg.burst })
local intbl = newcell:tag('table')
:addClass('condensedtable')
:addClass('listtable')
local average = {
calcBurstAverageRPS(stat, firerate.burst_delay[1]),
calcBurstAverageRPS(stat, firerate.burst_delay[2]),
calcBurstAverageRPS(stat, firerate.burst_delay[3]),
calcBurstAverageRPS(stat, firerate.burst_delay[4]),
}
renderMultipleFirerateRow(intbl, cfg.burst_average, cfg, average, { rpsClass = 'no-list-style', rpmClass = 'no-list-style' })
renderMultipleFirerateRow(intbl, cfg.auto,          cfg, firerate.auto, { rpmClass = 'no-list-style' })
else
local intbl = cell:tag('table')
:addClass('condensedtable')
:addClass('listtable')
local average = calcBurstAverageRPS(stat)
renderFirerateRow(intbl, cfg.burst,        cfg, firerate.burst)
renderFirerateRow(intbl, cfg.burst_average, cfg, average, { class = 'no-list-style' })
renderFirerateRow(intbl, cfg.auto,          cfg, firerate.auto)
end
return
elseif aw.isNumberAndGreaterThanZero(firerate.auto_start) then
local intbl = cell:tag('table')
:addClass('condensedtable')
renderVariableFirerateRow(intbl, '', cfg, firerate.auto_start, firerate.auto)
if aw.isNumberAndGreaterThanZero(firerate.auto_start_turbocharger) then
intbl:addClass('raritytable')
renderVariableFirerateRow(intbl, iu.hopup('turbocharger') .. '&nbsp;', cfg, firerate.auto_start_turbocharger, firerate.auto, { rarity = 'legendary' })
end
return
end
rps = firerate.auto
elseif mode.single then
if mode.burst > 1 then
local intbl = cell:tag('table')
:addClass('condensedtable')
:addClass('listtable')
local average = calcBurstAverageRPS(stat)
renderFirerateRow(intbl, cfg.burst,        cfg, firerate.burst)
renderFirerateRow(intbl, cfg.burst_average, cfg, average, { class = 'no-list-style' })
local singleRPS
if aw.isNumberAndGreaterThanZero(firerate.single_rechamber) then
singleRPS = calcRechamberRPS(firerate)
else
singleRPS = firerate.single
end
renderFirerateRow(intbl, cfg.single, cfg, singleRPS)
return
end
if proto.validateTypes(firerate.single_rechamber, MultipleNumberProto) then
local calcRPS = {
calcRechamberRPS(firerate, firerate.single_rechamber[1]),
calcRechamberRPS(firerate, firerate.single_rechamber[2]),
calcRechamberRPS(firerate, firerate.single_rechamber[3]),
calcRechamberRPS(firerate, firerate.single_rechamber[4]),
}
local intbl = cell:tag('table'):addClass('condensedtable')
renderMultipleFirerateRow(intbl, '', cfg, calcRPS)
return
elseif aw.isNumberAndGreaterThanZero(firerate.single_charged) then
local minimum
local maximum
if aw.isNumberAndGreaterThanZero(firerate.single_charged_minimum) then
minimum = calcRechamberRPS(firerate, firerate.single_charged_minimum)
maximum = calcRechamberRPS(firerate, firerate.single_charged)
elseif aw.isNumberAndGreaterThanZero(firerate.single_rechamber) then
minimum = calcRechamberRPS(firerate, firerate.single_rechamber)
maximum = calcRechamberRPS(firerate, firerate.single_rechamber + firerate.single_charged)
else
minimum = firerate
maximum = calcRechamberRPS(firerate, firerate.single_charged)
end
local intbl = cell:tag('table'):addClass('condensedtable')
renderVariableFirerateRow(intbl, '', cfg, minimum, maximum)
if proto.validateTypes(stat.deadeyes_tempo, DeadeyesTempoWithChargeProto) then
intbl:addClass('raritytable')
local mintempo = calcRechamberRPS(firerate, aw.getAsNumber(stat.deadeyes_tempo.firerate.single_charged_minimum, 0))
local maxtempo = calcRechamberRPS(firerate, stat.deadeyes_tempo.firerate.single_charged)
renderVariableFirerateRow(intbl, iu.hopup('deadeyes_tempo') .. '&nbsp;', cfg, mintempo, maxtempo, { rarity = 'epic' })
end
return
elseif aw.isNumberAndGreaterThanZero(firerate.single_rechamber) then
if proto.validateTypes(stat.deadeyes_tempo, DeadeyesTempoWithRechamberProto) then
local normal = calcRechamberRPS(firerate, firerate.single_rechamber)
local tempo  = calcRechamberRPS(firerate, stat.deadeyes_tempo.firerate.single_rechamber)
local intbl = cell:tag('table')
:addClass('condensedtable')
:addClass('raritytable')
renderFirerateRow(intbl, '',                                    cfg, normal)
renderFirerateRow(intbl, iu.hopup('deadeyes_tempo') .. '&nbsp;', cfg, tempo, { rarity = 'epic' })
return
else
rps = calcRechamberRPS(firerate)
end
else
rps = firerate.single
end
else
else
return
cell:node(node)
end
if type(rps) == 'table' then
local intbl = cell:tag('table'):addClass('condensedtable')
renderMultipleFirerateRow(intbl, '', cfg, rps)
else
renderFirerateCell(cell, cfg, rps)
end
end
end
end


local function renderProjectileSpeed(tbl, cfg, projectile_speed, projectile_speed_charged)
local function renderProjectileSpeed(tbl, cfg, projectile_speed, projectile_speed_charged)
if projectile_speed == nil then
if not aw.isNumberAndGreaterThanZero(projectile_speed) then
return
return
end
end
644行目: 422行目:
if projectile_speed == math.huge then
if projectile_speed == math.huge then
text = cfg.hitscan
text = cfg.hitscan
elseif projectile_speed_charged ~= nil then
elseif aw.isNumberAndGreaterThanZero(move_speed_charged) then
text = string.format(
text = string.format(
'<span class="text-type-number">%s</span> → <span class="text-type-number">%s</span>%s',
'<span class="text-type-number">%s</span> → <span class="text-type-number">%s</span>%s',
659行目: 437行目:
end
end


local function renderDPS(tbl, cfg, stat, lang)
local ShatterCapsMoveSpeedProto = {
if stat.firerate == nil then
shatter_caps = {
move_speed = proto.NumberRange(0, 1),
},
}
local function getMoveSpeedText(cfg, moveSpeed)
return string.format(
'<span class="text-type-number">%s</span>%s',
string.format(cfg.format, 100 * (moveSpeed - 1)),
cfg.unit)
end
local function renderMoveSpeed(tbl, cfg, stat)
if not aw.isNumberAndGreaterThanZero(stat.move_speed) then
return
return
end
end
local sld  = require('Module:Stat/Shield')['removelowprofile']
local text
-- [Hop-up] Shatter Caps
if proto.validateTypes(stat, ShatterCapsMoveSpeedProto) and stat.move_speed ~= stat.shatter_caps.move_speed then
text = string.format(
'%s%s<span class="block-rarity disp-rarity-epic text-rarity text-rarity-epic">%s %s</span>',
getMoveSpeedText(cfg, stat.move_speed),
cfg.separator,
iu.hopup('shatter_caps'),
getMoveSpeedText(cfg, stat.shatter_caps.move_speed))
elseif aw.isNumberAndGreaterThanZero(stat.move_speed_charged) then
text = string.format(
'%s → %s',
getMoveSpeedText(cfg, stat.move_speed),
getMoveSpeedText(cfg, stat.move_speed_charged),
cfg.unit)
else
text = getMoveSpeedText(cfg, stat.move_speed)
end
renderRow(tbl, cfg.name, text)
end
 
local function renderDPS(tbl, cfg, stat, lang)
local sld  = require('Module:Stat/Shield')['reinforcehelmets']
local node = require('Module:WeaponInfobox/DPS').renderDPS(stat, sld, lang)
local node = require('Module:WeaponInfobox/DPS').renderDPS(stat, sld, lang)
createCellInRow(tbl, cfg.name)
createCellInRow(tbl, cfg.name)
700行目: 512行目:
local function calcVirtualMagSize(overheat, firerate, moddedLoader)
local function calcVirtualMagSize(overheat, firerate, moddedLoader)
if moddedLoader then
if moddedLoader then
return 1 + math.floor(1.15 * overheat * firerate)
return math.ceil(1.15 * overheat * firerate)
else
else
return 1 + math.floor(overheat * firerate)
return math.ceil(overheat * firerate)
end
end
end
end


local function renderMagazine(tbl, cfg, stat, isModdedLoaderAttachable)
local MultipleNumberProto = {
proto.NumberRange(0),
proto.NumberRange(0),
proto.NumberRange(0),
proto.NumberRange(0),
}
local function renderMagazine(tbl, cfg, stat, isSpecial, isModdedLoaderAttachable)
local magazine = stat.magazine
local magazine = stat.magazine
local typename = type(magazine)
local typename = type(magazine)
if typename == 'table' then
local text
-- 物資投下武器
if isSpecial or stat.ammo == 'minigun' then
local magazine2
if typename == 'table' then
magazine2 = magazine[4]
else
magazine2 = magazine
end
if aw.isNumberAndGreaterThanZero(stat.magazine_reserve) then
if isModdedLoaderAttachable then
local moddedLoaderMagazine2 = apex.calcMagazineWithModdedLoader(magazine2)
local moddedLoaderReserve  = stat.magazine_reserve - (moddedLoaderMagazine2 - magazine2)
local intbl = createCellInRow(tbl, cfg.name)
:tag('table')
:addClass('condensedtable')
renderMagazineRow(intbl, '', magazine2, stat.magazine_reserve, cfg)
renderMagazineRow(intbl, iu.passive('modded_loader') .. '&nbsp;', moddedLoaderMagazine2, moddedLoaderReserve, cfg)
return
elseif magazine2 == math.huge then
text = string.format(
'%s <span class="text-separator">/</span> <span class="text-type-number">%s</span>%s',
cfg.infinity,
stat.magazine_reserve == math.huge and cfg.infinity or stat.magazine_reserve,
cfg.unit)
else
text = string.format(
'<span class="text-type-number">%d</span>%s <span class="text-separator">/</span> <span class="text-type-number">%s</span>%s',
magazine2,
cfg.unit,
stat.magazine_reserve == math.huge and cfg.infinity or stat.magazine_reserve,
cfg.unit)
end
else
text = string.format('<span class="text-type-number">%d</span>%s', magazine2, cfg.unit)
end
-- 拡張マガジンあり
elseif typename == 'table' then
if isModdedLoaderAttachable then
if isModdedLoaderAttachable then
local intable = createCellInRow(tbl, cfg.name)
local intable = createCellInRow(tbl, cfg.name)
717行目: 575行目:
intable,
intable,
'',
'',
magazine[1], magazine[2], magazine[3], magazine[4], { footer = cfg.unit })
magazine[1], magazine[2], magazine[3], magazine[4],
{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
local moddedLoaderMagazine = apex.calcMagazinesWithModdedLoader(magazine)
renderFormatRow(
renderFormatRow(
intable,
intable,
iu.passive('modded_loader') .. '&nbsp;',
iu.passive('modded_loader') .. '&nbsp;',
aw.round(1.15 * magazine[1]),
moddedLoaderMagazine[1],
aw.round(1.15 * magazine[2]),
moddedLoaderMagazine[2],
aw.round(1.15 * magazine[3]),
moddedLoaderMagazine[3],
aw.round(1.15 * magazine[4]),
moddedLoaderMagazine[4],
{ footer = cfg.unit })
{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
return
-- Boosted Loader
elseif stat.boosted_loader and proto.validateTypes(stat.boosted_loader.magazine, MultipleNumberProto) then
local intable = createCellInRow(tbl, cfg.name)
:tag('table')
:addClass('condensedtable')
:addClass('raritytable')
renderFormatRow(
intable,
'',
magazine[1], magazine[2], magazine[3], magazine[4],
{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
renderFormatRow(
intable,
iu.hopup('boosted_loader') .. '&nbsp;',
stat.boosted_loader.magazine[1],
stat.boosted_loader.magazine[2],
stat.boosted_loader.magazine[3],
stat.boosted_loader.magazine[4],
{ rarity = 'legendary', separator = '&thinsp;-&thinsp;', footer = cfg.unit })
return
else
else
local text = formatter:format(magazine[1], magazine[2], magazine[3], magazine[4], cfg.unit, ' - ')
text = formatter:format(magazine[1], magazine[2], magazine[3], magazine[4], cfg.unit, ' - ')
renderRow(tbl, cfg.name, text)
end
end
-- 拡張マガジンなし
elseif typename == 'number' then
elseif typename == 'number' then
local text
if magazine == math.huge then
if magazine == math.huge then
if stat.firerate and stat.firerate.auto then
if aw.isNumberAndGreaterThanZero(stat.firerate) then
if proto.validateTypes(stat.overheat, MultipleNumberProto) then
if proto.validateTypes(stat.overheat, MultipleNumberProto) then
local intable = createCellInRow(tbl, cfg.name)
local intable = createCellInRow(tbl, cfg.name):tag('table'):addClass('condensedtable')
:tag('table')
:addClass('condensedtable')
renderFormatRow(
renderFormatRow(
intable,
intable,
'',
'',
calcVirtualMagSize(stat.overheat[1], stat.firerate.auto),
calcVirtualMagSize(stat.overheat[1], stat.firerate),
calcVirtualMagSize(stat.overheat[2], stat.firerate.auto),
calcVirtualMagSize(stat.overheat[2], stat.firerate),
calcVirtualMagSize(stat.overheat[3], stat.firerate.auto),
calcVirtualMagSize(stat.overheat[3], stat.firerate),
calcVirtualMagSize(stat.overheat[4], stat.firerate.auto),
calcVirtualMagSize(stat.overheat[4], stat.firerate),
{ footer = cfg.unit })
{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
renderFormatRow(
renderFormatRow(
intable,
intable,
753行目: 634行目:
string.format(cfg.seconds.format, stat.overheat[3]),
string.format(cfg.seconds.format, stat.overheat[3]),
string.format(cfg.seconds.format, stat.overheat[4]),
string.format(cfg.seconds.format, stat.overheat[4]),
{ class = 'no-list-style all-secondary text-smaller', headerAlign = 'right', footer = cfg.seconds.unit })
{ class = 'no-list-style text-secondary text-smaller', headerAlign = 'right', separator = '&thinsp;-&thinsp;', footer = cfg.seconds.unit })
if isModdedLoaderAttachable then
if isModdedLoaderAttachable then
renderFormatRow(
renderFormatRow(
intable,
intable,
iu.passive('modded_loader') .. '&nbsp;',
iu.passive('modded_loader') .. '&nbsp;',
calcVirtualMagSize(stat.overheat[1], stat.firerate.auto, true),
calcVirtualMagSize(stat.overheat[1], stat.firerate, true),
calcVirtualMagSize(stat.overheat[2], stat.firerate.auto, true),
calcVirtualMagSize(stat.overheat[2], stat.firerate, true),
calcVirtualMagSize(stat.overheat[3], stat.firerate.auto, true),
calcVirtualMagSize(stat.overheat[3], stat.firerate, true),
calcVirtualMagSize(stat.overheat[4], stat.firerate.auto, true),
calcVirtualMagSize(stat.overheat[4], stat.firerate, true),
{ footer = cfg.unit })
{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
renderFormatRow(
renderFormatRow(
intable,
intable,
770行目: 651行目:
string.format(cfg.seconds.format, 1.15 * stat.overheat[3]),
string.format(cfg.seconds.format, 1.15 * stat.overheat[3]),
string.format(cfg.seconds.format, 1.15 * stat.overheat[4]),
string.format(cfg.seconds.format, 1.15 * stat.overheat[4]),
{ class = 'no-list-style all-secondary text-smaller', headerAlign = 'right', footer = cfg.seconds.unit })
{ class = 'no-list-style text-secondary text-smaller', headerAlign = 'right', separator = '&thinsp;-&thinsp;', footer = cfg.seconds.unit })
end
end
return
return
elseif aw.isNumber(stat.overheat) then
elseif aw.isNumberAndGreaterThanZero(stat.overheat) then
text = string.format(
text = string.format(
'%s (<span class="text-type-number">%d</span>%s <span class="text-separator">/</span> %s <span class="text-type-number">%d</span>%s)',
'%s (<span class="text-type-number">%d</span>%s <span class="text-separator">/</span> %s <span class="text-type-number">%d</span>%s)',
cfg.infinity,
cfg.infinity,
1 + math.floor(stat.overheat * stat.firerate.auto),
1 + math.floor(stat.overheat * stat.firerate),
cfg.unit,
cfg.unit,
iu.passive('modded_loader'),
iu.passive('modded_loader'),
1 + math.floor(1.15 * stat.overheat * stat.firerate.auto),
1 + math.floor(1.15 * stat.overheat * stat.firerate),
cfg.unit)
cfg.unit)
else
else
789行目: 670行目:
end
end
elseif stat.rounds_per_shot and stat.rounds_per_shot.single and stat.rounds_per_shot.single > 1 then
elseif aw.isNumberAndGreaterThanX(stat.ammo_per_shot, 1) then
local times = magazine / stat.rounds_per_shot.single
local times = magazine / stat.ammo_per_shot
local timesText
local timesText
if type(cfg.times.format) == 'table' then
if type(cfg.times.format) == 'table' then
805行目: 686行目:
'<span class="text-type-number">%d</span>%s <span class="text-secondary"><small>(%s)</small></span>',
'<span class="text-type-number">%d</span>%s <span class="text-secondary"><small>(%s)</small></span>',
magazine, cfg.unit, timesText)
magazine, cfg.unit, timesText)
elseif aw.isNumber(stat.magazine_reserve) then
if isModdedLoaderAttachable then
local intbl = createCellInRow(tbl, cfg.name)
:tag('table')
:addClass('condensedtable')
renderMagazineRow(intbl, '', magazine, stat.magazine_reserve, cfg)
renderMagazineRow(intbl, iu.passive('modded_loader') .. '&nbsp;', aw.round(1.15 * magazine), stat.magazine_reserve, cfg)
return
else
text = string.format(
'<span class="text-type-number">%d</span>%s <span class="text-separator">/</span> <span class="text-type-number">%s</span>%s',
magazine,
cfg.unit,
stat.magazine_reserve == math.huge and cfg.infinity or stat.magazine_reserve,
cfg.unit)
end
else
else
text = string.format('<span class="text-type-number">%d</span>%s', magazine, cfg.unit)
text = string.format('<span class="text-type-number">%d</span>%s', magazine, cfg.unit)
end
end
renderRow(tbl, cfg.name, text)
end
end
renderRow(tbl, cfg.name, text)
end
end


local function renderReload(tbl, cfg, reload, attachments, ammo, isModdedLoaderAttachable)
local function renderDuration(tbl, cfg, stat, lang)
if reload.full == nil then
local node, isSimple = require('Module:WeaponInfobox/Duration').renderDuration(stat, lang)
return
local cell = createCellInRow(tbl, cfg.name)
if isSimple then
cell:node(node)
else
tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
end
end
end
local muls = { level1 = 0.963, level2 = 0.933, level3 = 0.9 }
 
--if ammo == 'heavy' or ammo == 'special_heavy' then
local function renderSpread(tbl, cfg, stat, lang, frame)
-- muls = { level1 = 1, level2 = 0.92, level3 = 0.87 }
local Spread = require('Module:WeaponInfobox/Spread')
--else
local node = Spread.renderSpread(stat.spread, stat.attachments.laser_sight, stat.quickdraw_holster and stat.quickdraw_holster.spread, lang)
-- muls = { level1 = 1, level2 = 0.95, level3 = 0.9 }
local cell = createCellInRow(tbl, cfg.name)
--end
if node.tagName == 'span' then
cell:node(node)
local incompatible = not attachments.stock
elseif Hopup.anvil_receiver.enabled and stat.anvil_receiver and stat.anvil_receiver.spread then
if incompatible or ammo == 'shotgun' then
local node2 = Spread.renderSpread(stat.anvil_receiver.spread, stat.attachments.laser_sight, nil, lang)
if reload.tactical ~= nil and reload.tactical ~= reload.full then
if frame then
local tacticalText, fullText
local tabContent = string.format(
if incompatible then
'{{#tab:%s|%s}}{{#tab:%s|%s}}',
tacticalText = tostring(reload.tactical)
cfg.normal, tostring(node),
fullText    = tostring(reload.full)
cfg.anvil_receiver, tostring(node2))
else
tbl:tag('tr')
tacticalText = tostring(muls.level3 * reload.tactical)
:tag('td')
fullText    = tostring(muls.level3 * reload.full)
:attr('colspan', 2)
end
:wikitext(frame:extensionTag { name = 'tabs', content = tabContent, args = { class = 'tabs-intable' }})
local intable = createCellInRow(tbl, cfg.name)
:tag('table')
:addClass('condensedtable')
:addClass('listtable')
renderFormatRow(
intable,
cfg.tactical.name .. '&nbsp;',
string.format(cfg.tactical.format, reload.tactical),
nil, nil, nil, { footer = cfg.tactical.unit })
if isModdedLoaderAttachable then
renderFormatRow(
intable,
iu.passive('modded_loader') .. '&nbsp;',
string.format(cfg.tactical.format, 0.75 * reload.tactical),
nil, nil, nil, { class = 'no-list-style', headerAlign = 'right', footer = cfg.tactical.unit })
end
renderFormatRow(
intable,
cfg.full.name .. '&nbsp;',
string.format(cfg.full.format, reload.full),
nil, nil, nil, { footer = cfg.full.unit })
if isModdedLoaderAttachable then
renderFormatRow(
intable,
iu.passive('modded_loader') .. '&nbsp;',
string.format(cfg.full.format, 0.75 * reload.full),
nil, nil, nil, { class = 'no-list-style', headerAlign = 'right', footer = cfg.full.unit })
end
elseif isModdedLoaderAttachable then
local time
if incompatible then
time = reload.full
else
time = muls.level3 * reload.full
end
local text = string.format(
cfg.full.format .. cfg.full.unit .. ' <span class="text-separator">/</span> %s ' .. cfg.full.format .. cfg.full.unit,
time,
iu.passive('modded_loader'),
0.75 * time)
renderRow(tbl, cfg.name, text)
else
else
local time
tbl:tag('tr')
if incompatible then
:tag('td')
time = reload.full
:attr('colspan', 2)
else
:tag('b')
time = muls.level3 * reload.full
:wikitext(cfg.normal)
end
:done()
renderRow(tbl, cfg.name, string.format(cfg.full.format, time) .. cfg.full.unit)
:node(node)
:tag('b')
:wikitext(cfg.anvil_receiver)
:done()
:node(node2)
end
end
elseif reload.tactical ~= nil and reload.full ~= nil then
elseif stat.spread_charged then
renderRow(tbl, cfg.name, '')
local node2 = Spread.renderSpread(stat.spread_charged , nil, lang)
if frame then
local intable = tbl:tag('tr')
local tabContent = string.format(
:tag('td')
'{{#tab:%s|%s}}{{#tab:%s|%s}}',
:attr('colspan', 2)
cfg.normal, tostring(node),
:tag('table')
cfg.full_charge, tostring(node2))
:addClass('condensedtable')
tbl:tag('tr')
:addClass('listtable')
:tag('td')
local level1
:attr('colspan', 2)
if muls.level1 < 1 then
:wikitext(frame:extensionTag { name = 'tabs', content = tabContent, args = { class = 'tabs-intable' }})
level1 = string.format(cfg.tactical.format, muls.level1 * reload.tactical)
else
else
level1 = nil
tbl:tag('tr')
end
:tag('td')
renderFormatRow(
:attr('colspan', 2)
intable,
:tag('b')
cfg.tactical.name .. '&nbsp;',
:wikitext(cfg.normal)
string.format(cfg.tactical.format, reload.tactical),
:done()
level1,
:node(node)
string.format(cfg.tactical.format, muls.level2 * reload.tactical),
:tag('b')
string.format(cfg.tactical.format, muls.level3 * reload.tactical),
:wikitext(cfg.full_charge)
{ footer = cfg.tactical.unit })
:done()
if isModdedLoaderAttachable then
:node(node2)
local level1modded
if muls.level1 < 1 then
level1modded = string.format(cfg.tactical.format, 0.75 * muls.level1 * reload.tactical)
else
level1modded = nil
end
renderFormatRow(
intable,
iu.passive('modded_loader') .. '&nbsp;',
string.format(cfg.tactical.format, 0.75 * reload.tactical),
level1modded,
string.format(cfg.tactical.format, 0.75 * muls.level2 * reload.tactical),
string.format(cfg.tactical.format, 0.75 * muls.level3 * reload.tactical),
{ class = 'no-list-style', headerAlign = 'right', footer = cfg.tactical.unit })
end
local level1empty
if muls.level1 < 1 then
level1empty = string.format(cfg.tactical.format, muls.level1 * reload.full)
else
level1empty = nil
end
renderFormatRow(
intable,
cfg.full.name .. '&nbsp;',
string.format(cfg.full.format, reload.full),
level1empty,
string.format(cfg.full.format, muls.level2 * reload.full),
string.format(cfg.full.format, muls.level3 * reload.full),
{ footer = cfg.full.unit })
if isModdedLoaderAttachable then
local level1emptymodded
if muls.level1 ~= 1 then
level1emptymodded = string.format(cfg.tactical.format, 0.75 * muls.level1 * reload.full)
else
level1emptymodded = nil
end
renderFormatRow(
intable,
iu.passive('modded_loader') .. '&nbsp;',
string.format(cfg.full.format, 0.75 * reload.full),
level1emptymodded,
string.format(cfg.full.format, 0.75 * muls.level2 * reload.full),
string.format(cfg.full.format, 0.75 * muls.level3 * reload.full),
{ class = 'no-list-style', headerAlign = 'right', footer = cfg.full.unit })
end
end
else
tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
end
end
end
end


local function renderDraw(tbl, cfg, draw, quickdraw_holster, attachments, ammo)
local function renderSpreadDecayRow(intbl, name, cfg, spread, opts)
if draw == nil then
opts = opts or {}
intbl:tag('tr')
:addClassIf(type(opts.rarity) == 'string', 'row-rarity-one disp-rarity-' .. (opts.rarity or 'common'))
:tag('th')
:wikitext(name)
:done()
:tag('td')
:addClass('cell-type-number')
:attr('align', 'right')
:wikitext(string.format(cfg.rate.format, spread.decay_rate))
:done()
:tag('td')
:wikitext(cfg.rate.unit)
:done()
:tag('td')
:addClass('text-smaller')
:wikitext(cfg.delay.name)
:done()
:tag('td')
:addClass('cell-type-number')
:addClass('text-smaller')
:attr('align', 'right')
:wikitext(string.format(cfg.delay.format, spread.decay_delay))
:done()
:tag('td')
:addClass('text-smaller')
:wikitext(cfg.delay.unit)
end
 
local function renderSpreadDecay(tbl, cfg, stat)
if not (aw.isNumberAndGreaterThanZero(stat.spread.decay_rate) and aw.isNumberAndGreaterThanZero(stat.spread.decay_delay)) then
return
return
end
end
local muls
local cell = createCellInRow(tbl, cfg.name)
if ammo == 'heavy' or ammo == 'special_heavy' then
muls = { level2 = 0.92, level3 = 0.87 }
else
muls = { level2 = 0.95, level3 = 0.9 }
end
local incompatible = not attachments.stock
-- [Hop-up] Anvil Receiver
if incompatible or aw.stringstarts(ammo, "special_") then
if stat.anvil_receiver and stat.anvil_receiver.spread and aw.isNumberAndGreaterThanZero(stat.anvil_receiver.spread.decay_rate) and aw.isNumberAndGreaterThanZero(stat.anvil_receiver.spread.decay_delay) then
local time
local intbl = cell:tag('table')
if incompatible or ammo == 'special_sniper' then
:addClass('condensedtable')
time = draw
:addClass('raritytable')
else
renderSpreadDecayRow(
time = 0.75 * draw
intbl,
end
local text
if quickdraw_holster > 0 then
text = string.format(
cfg.format .. cfg.unit .. ' <span class="text-separator">/</span> %s ' .. formatter:epic(cfg.format .. cfg.unit),
time,
iu.hopup('quickdraw_holster'),
quickdraw_holster * time)
else
text = string.format(cfg.format, time) .. cfg.unit
end
renderRow(tbl, cfg.name, text)
elseif quickdraw_holster > 0 then
local intable = createCellInRow(tbl, cfg.name)
:tag('table')
:addClass('condensedtable')
renderFormatRow(
intable,
'',
'',
string.format(cfg.format, draw),
cfg,
string.format(cfg.format, 0.85 * draw),
stat.spread)
string.format(cfg.format, 0.80 * draw),
renderSpreadDecayRow(
string.format(cfg.format, 0.75 * draw),
intbl,
{ footer = cfg.unit })
iu.hopup('anvil_receiver') .. '&nbsp;',
renderFormatRow(
cfg,
intable,
stat.anvil_receiver.spread,
iu.hopup('quickdraw_holster') .. '&nbsp;',
{ rarity = 'legendary' })
string.format(cfg.format, quickdraw_holster * draw),
string.format(cfg.format, 0.85 * quickdraw_holster * draw),
string.format(cfg.format, 0.80 * quickdraw_holster * draw),
string.format(cfg.format, 0.75 * quickdraw_holster * draw),
{ footer = cfg.unit })
else
else
local text = formatter:format(
local text = string.format(cfg.format, stat.spread.decay_rate, stat.spread.decay_delay)
string.format(cfg.format, draw),
cell:wikitext(text)
string.format(cfg.format, 0.85 * draw),
string.format(cfg.format, 0.80 * draw),
string.format(cfg.format, 0.75 * draw),
cfg.unit, ' - ')
renderRow(tbl, cfg.name, text)
end
end
end
end


local function renderSpread(tbl, cfg, stat, lang)
local function createTable(cfg, name, stat, lang, frame)
local node = require('Module:WeaponInfobox/Spread').renderSpread(stat.spread, stat.quickdraw_holster and stat.quickdraw_holster.spread, lang)
local isSpecial = aw.stringstarts(stat.ammo, "special_")
local cell = createCellInRow(tbl, cfg.name)
if node.tagName == 'span' then
cell:node(node)
else
tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
end
end
 
local function createTable(cfg, stat, lang)
local isModdedLoaderAttachable = stat.category == 'light_machine_gun' or stat.ammo == 'minigun'
local isModdedLoaderAttachable = stat.category == 'light_machine_gun' or stat.ammo == 'minigun'
local tbl = mw.html.create('table')
local tbl = mw.html.create('table')
1,058行目: 831行目:
renderAmmo(tbl, cfg.ammo, stat.ammo, lang)
renderAmmo(tbl, cfg.ammo, stat.ammo, lang)
renderCost(tbl, cfg.cost, stat.cost, stat.ammo)
renderCost(tbl, cfg.cost, stat.cost, stat.ammo)
renderMode(tbl, cfg.mode, stat.mode)
renderMode(tbl, cfg.mode, stat, isSpecial)
renderDamage(tbl, cfg.damage, stat)
renderDamage(tbl, cfg.damage, stat, isSpecial)
renderFirerate(tbl, cfg.firerate, stat)
if stat.firerate then
renderFirerate(tbl, cfg.firerate, stat, lang)
end
renderProjectileSpeed(tbl, cfg.projectilespeed, stat.projectile_speed, stat.projectile_speed_charged)
renderProjectileSpeed(tbl, cfg.projectilespeed, stat.projectile_speed, stat.projectile_speed_charged)
renderMagazine(tbl, cfg.magazine, stat, isModdedLoaderAttachable)
renderMoveSpeed(tbl, cfg.movespeed, stat, stat)
renderMagazine(tbl, cfg.magazine, stat, isSpecial, isModdedLoaderAttachable)
if stat.firerate and name ~= 'ボセックコンパウンドボウ' then
renderDPS(tbl, cfg.dps, stat, lang)
end
if stat.time then
if stat.time then
if stat.time.draw then
renderDuration(tbl, cfg.duration, stat, lang)
renderDraw(tbl, cfg.draw, stat.time.draw, stat.time.quickdraw_holster or 0, stat.attachments, stat.ammo)
end
end
if stat.spread then
renderSpread(tbl, cfg.spread, stat, lang, frame)
if stat.time.reload then
renderSpreadDecay(tbl, cfg.spreaddecay, stat)
renderReload(tbl, cfg.reload, stat.time.reload, stat.attachments, stat.ammo, isModdedLoaderAttachable)
end
end
end
renderDPS(tbl, cfg.dps, stat, lang)
renderSpread(tbl, cfg.spread, stat, lang)
return tbl
return tbl
end
end


local function renderInfobox(args)
local function renderInfobox(args, frame)
local lang = args and args.lang or 'ja'
local lang = args and args.lang or 'ja'
local cfglang = cfg[lang]
local cfglang = cfg[lang]
1,093行目: 868行目:
:addClass('tpl-weapon-header')
:addClass('tpl-weapon-header')
:wikitext(args.name .. iu.ammo(stat.ammo, { size = 40 }))
:wikitext(args.name .. iu.ammo(stat.ammo, { size = 40 }))
div:node(createTable(cfglang, stat, lang))
div:node(createTable(cfglang, args.name, stat, lang, frame))
return div
return div
end
end
1,099行目: 874行目:
function p._main(args, frame)
function p._main(args, frame)
formatter = require('Module:Utility/Formatter').new(frame)
formatter = require('Module:Utility/Formatter').new(frame)
return tostring(renderInfobox(args))
return tostring(renderInfobox(args, frame))
end
end



2022年8月13日 (土) 12:12時点における最新版

このモジュールについての説明文ページを モジュール:WeaponInfobox/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 inu   = require('Module:Utility/ImageWithName')
local nu    = require('Module:Utility/Name')
local proto = require('Module:Utility/Prototypes')
local StringBuilder = require('Module:Utility/StringBuilder')
local formatter -- lazily initialized
local getArgs -- lazily initialized

local function createCellInRow(tbl, name)
	return tbl:tag('tr')
		:tag('th')
			:wikitext(name)
			:done()
		:tag('td')
end

local function renderRow(tbl, name, item)
	createCellInRow(tbl, name):wikitext(item)
end

local function renderFormatRow(tbl, name, default, common, rare, epic, opts)
	opts = opts or {}
	opts.separator = opts.separator or '&nbsp;-&nbsp;'
	local row = tbl:tag('tr')
		:addClassIf(type(opts.class)  == 'string', opts.class)
		:addClassIf(type(opts.rarity) == 'string', 'row-rarity-one disp-rarity-' .. (opts.rarity or 'common'))
	
	if name ~= nil then
		row:tag('th')
			:attrIf(opts.headerAlign and type(opts.headerAlign) == 'string', { align = opts.headerAlign })
			:wikitext(name)
	end

	local first = true
	if default ~= nil then
		first = false
		row:tag('td')
			:addClass('cell-type-number')
			:attrIf(opts.align and type(opts.align) == 'string', { align = opts.align })
			:wikitext(default)
	end
	
	if common ~= nil then
		if not first then
			row:tag('td'):wikitext(opts.separator)
		end
		first = false
		row:tag('td')
			:addClass('cell-type-number')
			:attrIf(opts.align and type(opts.align) == 'string', { align = opts.align })
			:tag('span')
				:addClass('text-rarity')
				:addClass('text-rarity-common')
				:wikitext(common)
	end
	
	if rare ~= nil then
		if not first then
			row:tag('td'):wikitext(opts.separator)
		end
		first = false
		row:tag('td')
			:addClass('cell-type-number')
			:attrIf(opts.align and type(opts.align) == 'string', { align = opts.align })
			:tag('span')
				:addClass('text-rarity')
				:addClass('text-rarity-rare')
				:wikitext(rare)
	end
	
	if epic ~= nil then
		if not first then
			row:tag('td'):wikitext(opts.separator)
		end
		first = false
		row:tag('td')
			:addClass('cell-type-number')
			:attrIf(opts.align and type(opts.align) == 'string', { align = opts.align })
			:tag('span')
				:addClass('text-rarity')
				:addClass('text-rarity-epic')
				:wikitext(epic)
	end
	
	if opts.footer ~= nil then
		row:tag('td')
			:attrIf(opts.footerAlign   and type(opts.footerAlign) == 'string',   { align   = opts.footerAlign }, { align = 'left' })
			:attrIf(opts.footerColspan and type(opts.footerColspan) == 'number', { colspan = opts.footerColspan })
			:wikitext(opts.footer)
	end
	return row
end

local function renderReleaseDate(tbl, cfg, release)
	local r = os.date("*t", release)
	local releaseText = string.format(
		'<time datetime="%04d-%02d-%02dT%02d:00:00.000+0900">%d年%d月%d日 %d時</time>~',
		r.year, r.month, r.day, r.hour, r.year, r.month, r.day, r.hour)
	renderRow(tbl, cfg.name, releaseText)
end

local function renderCategory(tbl, cfg, stat, lang)
	local category = nu.type(stat, lang, 1)
	local item
	if lang == 'ja' then
		item = string.format('[[武器#%s|%s]][[Category:%s]]', category, category, category)
	else
		item = string.format('%s[[Category:%s]]', category, category)
	end
	renderRow(tbl, cfg.name, item)
end

local function renderAmmo(tbl, cfg, stat, lang)
	createCellInRow(tbl, cfg.name)
		:addClass('cell-ammo')
		:wikitext(inu.ammo(stat, { lang = lang, size = 24 }))
end

local function renderCost(tbl, cfg, stat, ammo)
	if stat == nil or aw.stringstarts(ammo, 'special_') then
		return
	end
	
	local text = formatter:format(
		aw.comma(stat[1]),
		aw.comma(stat[2]),
		aw.comma(stat[3]),
		aw.comma(stat[4]),
		'', ' - ')
	renderRow(tbl, cfg.name, cfg.header .. text)
end

local function appendMode(builder, cfg, stat, base)
	if stat.is_semi_auto or (stat.is_semi_auto == nil and base.is_semi_auto) then
		local burstCount = stat.burst_count or (base and base.burst_count) or 1
		if aw.isNumberAndGreaterThanOrEqualToX(burstCount, 2) then
			builder:appendFormat(cfg.burst, burstCount)
			builder:append(cfg.burst_category)
		else
			builder:append(cfg.single, cfg.single_category)
		end
	else
		builder:append(cfg.auto, cfg.auto_category)
	end
end

local function renderMode(tbl, cfg, stat, isSpecial)
	local builder = StringBuilder.new()
	appendMode(builder, cfg, stat)
	if stat.altfire then
		builder:append(cfg.separator)
		appendMode(builder, cfg, stat.altfire, stat)
	end
	if (Hopup.selectfire_receiver.enabled or isSpecial) and stat.selectfire_receiver then
		builder:append(cfg.separator, '<span class="block-rarity disp-rarity-epic">', iu.hopup('selectfire_receiver'), ' ')
		appendMode(builder, cfg, stat.selectfire_receiver, stat)
		builder:append('</span>')
	end
	if (Hopup.double_tap_trigger.enabled or isSpecial) and stat.double_tap_trigger then
		builder:append(cfg.separator, '<span class="block-rarity disp-rarity-epic">', iu.hopup('double_tap_trigger'), ' ')
		appendMode(builder, cfg, stat.double_tap_trigger, stat)
		builder:append('</span>')
	end
	renderRow(tbl, cfg.name, tostring(builder))
end

local HammerpointRoundsProto = {
	hammerpoint_rounds = {
		damage_unshielded_scale = proto.NumberRange(1),
	},
}
local DisruptorRoundsProto = {
	disruptor_rounds = {
		damage_shield_scale = proto.NumberRange(1),
	},
}
local function getDamageText(stat, root, isSpecial)
	local builder = StringBuilder.new()
	local rarity  = isSpecial and 'heirloom'
	local damage, pellet
	if root then
		damage = stat.damage or root.damage
		pellet = stat.pellet or root.pellet or 1
	else
		damage = stat.damage
		pellet = stat.pellet or 1
	end
	if aw.isNumberAndGreaterThanOrEqualToX(pellet, 2) then
		builder:appendFormat(
			'<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d',
			damage.base,
			pellet)
		
		if aw.isNumberAndGreaterThanZero(damage.charged) then
			builder:appendFormat(
				'&thinsp;&rarr;&thinsp;<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d',
				damage.charged,
				pellet)
		elseif proto.validateTypes(stat, HammerpointRoundsProto) then
			if not rarity then
				rarity = Hopup.hammerpoint_rounds.rarity
			end
			builder:appendFormat(
				'&thinsp;&rarr;&thinsp;<span class="block-rarity disp-rarity-%s text-rarity text-rarity-%s">%s \'\'\'<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d\'\'\'</span>',
				rarity,
				rarity,
				iu.hopup('hammerpoint_rounds', { rarity = rarity }),
				math.floor(damage.base * stat.hammerpoint_rounds.damage_unshielded_scale),
				pellet)
		end
	else
		if aw.isNumberAndGreaterThanZero(damage.amped) then
			builder:appendFormat(
				'<span class="text-type-number">%s</span> <span class="text-separator">/</span> %s&nbsp;<span class="text-type-number">%s</span>',
				damage.base,
				iu.item('シールドセル'),
				damage.amped)
		elseif aw.isNumberAndGreaterThanZero(damage.charged) then
			builder:appendFormat(
				'<span class="text-type-number">%s</span>&thinsp;&rarr;&thinsp;<span class="text-type-number">%s</span>',
				damage.base,
				damage.charged)
		elseif proto.validateTypes(stat, HammerpointRoundsProto) then
			if not rarity then
				rarity = Hopup.hammerpoint_rounds.rarity
			end
			builder:appendFormat(
				'<span class="text-type-number">%s</span>&thinsp;&rarr;&thinsp;<span class="block-rarity disp-rarity-%s text-rarity text-rarity-%s">%s \'\'\'<span class="text-type-number">%s</span>\'\'\'</span>',
				damage.base,
				rarity,
				rarity,
				iu.hopup('hammerpoint_rounds', { rarity = rarity }),
				math.floor(damage.base * stat.hammerpoint_rounds.damage_unshielded_scale))
		elseif proto.validateTypes(stat, DisruptorRoundsProto) then
			if not rarity then
				rarity = Hopup.disruptor_rounds.rarity
			end
			builder:appendFormat(
				'<span class="block-rarity disp-rarity-%s text-rarity text-rarity-%s">%s \'\'\'<span class="text-type-number">%s</span>\'\'\'</span>&thinsp;&rarr;&thinsp;<span class="text-type-number">%s</span>',
				rarity,
				rarity,
				iu.hopup('disruptor_rounds', { rarity = rarity }),
				math.floor(damage.base * stat.disruptor_rounds.damage_shield_scale),
				damage.base)
		else
			builder:appendFormat('<span class="text-type-number">%s</span>', damage.base)
		end
	end
	return tostring(builder)
end

local function renderHeadRow(tbl, name, head, cfg, nolist)
	local opts = { align = 'left', footer = cfg.unit }
	if nolist then
		opts.class = 'no-list-style'
		opts.headerAlign = 'right'
	end
	renderFormatRow(
		tbl,
		name,
		nil,
		apex.calcHeadshotMultiplier(head, apex.HEAD_HLMLV1),
		apex.calcHeadshotMultiplier(head, apex.HEAD_HLMLV2),
		apex.calcHeadshotMultiplier(head, apex.HEAD_HLMLV3),
		opts)
end

local function renderHeadEffectiveRange(tbl, cfg, stat, root)
	local headDist = stat.damage_head_distance or root.damage_head_distance
	if aw.isNumberAndGreaterThanZero(headDist) then
		local text = string.format(cfg.head_effective_range, 0.0254 * headDist)
		tbl:tag('tr')
			:addClass('no-list-style')
			:tag('td')
				:attr('align', 'left')
				:attr('colspan', 7)
				:wikitext(text)
	end
end

local function renderLegsRow(tbl, cfg, stat, root)
	local legs = stat.damage_legs_scale or root.damage_legs_scale
	local text
	if aw.isNumberAndGreaterThanZero(stat.legshot_charged) then
		text = string.format(
			'<span class="text-type-number">%s</span> &rarr; <span class="text-type-number">%s</span>%s',
			legs,
			stat.legshot_charged,
			cfg.unit)
	else
		text = string.format(
			'<span class="text-type-number">%s</span>%s',
			legs,
			cfg.unit)
	end
	tbl:tag('tr')
		:tag('th')
			:wikitext(cfg.legs)
			:done()
		:tag('td')
			:attr('align', 'left')
			:attr('colspan', 6)
			:wikitext(text)
end

local DamageProto = {
	damage = {
		base     = proto.NumberRange(1),
	},
	damage_head_scale = proto.NumberRange(1),
	damage_legs_scale = proto.NumberRange(0, 1),
}
local ShatterCapsProto = {
	damage = {
		base     = proto.NumberRange(1),
	},
	damage_head_scale = proto.NumberRange(1),
	damage_legs_scale = proto.NumberRange(0, 1),
	pellet = proto.NumberRange(1),
}
local damageHopups = {
	{
		name = 'selectfire_receiver',
		proto = DamageProto,
		textclass = 'text-rarity-epic',
		dispclass = 'disp-rarity-epic',
	},
	{
		name = 'anvil_receiver',
		proto = DamageProto,
		textclass = 'text-rarity-legendary',
		dispclass = 'disp-rarity-legendary',
	},
	{
		name = 'shatter_caps',
		proto = ShatterCapsProto,
		textclass = 'text-rarity-epic',
		dispclass = 'disp-rarity-epic',
	},
}
local function renderDamage(tbl, cfg, stat, isSpecial)
	if not proto.validateTypes(stat, DamageProto) then
		return
	end
	
	local cattext = string.format(cfg.head_category, stat.damage.headshot_charged or stat.damage_head_scale or 1)
	local cell = createCellInRow(tbl, cfg.name)
	cell:wikitext(cattext .. getDamageText(stat, nil, isSpecial))
		
	-- Headshot
	local head = stat.damage_head_scale or 1
	local intable = cell:tag('table')
		:addClass('condensedtable')
		:addClass('listtable')
	renderHeadRow(intable, cfg.head, head, cfg)
	
	local hlmc = stat.damage.headshot_charged or 1
	if hlmc > 1 then
		local nameC = '→&nbsp;'
		renderHeadRow(intable, nameC, hlmc, cfg, true)
	end
	
	local sprm = stat.damage.skullpiercer_rifling or 1
	if sprm > 1 then
		local nameS = iu.hopup('skullpiercer_rifling') .. '&nbsp;'
		renderHeadRow(intable, nameS, sprm, cfg, true)
	end
	renderHeadEffectiveRange(intable, cfg, stat, stat)
	
	-- Legsshot
	renderLegsRow(intable, cfg, stat, stat)
	
	-- [Hop-Up] Selectfire Receiver, Anvil Receiver & Shatter Caps
	for _, v in ipairs(damageHopups) do
		local hopupStat = stat[v.name]
		if (Hopup[v.name].enabled or isSpecial) and proto.validateTypes(hopupStat, v.proto) then
			local intblS = cell:tag('div')
				:addClass('tpl-weapon-inbox')
				:addClass(specialOnly and 'disp-rarity-heirloom' or v.dispclass)
				:wikitext(iu.hopup(v.name) .. '&nbsp;')
				:tag('span')
					:addClass('text-rarity')
					:addClass(specialOnly and 'text-rarity-heirloom' or v.textclass)
					:wikitext(getDamageText(hopupStat, stat))
					:done()
				:tag('table')
					:addClass('condensedtable')
					:addClass('listtable')
			
			local head = hopupStat.damage_head_scale or stat.damage_head_scale or 1
			renderHeadRow(intblS, cfg.head, head, cfg)
			renderHeadEffectiveRange(intblS, cfg, hopupStat, stat)
			renderLegsRow           (intblS, cfg, hopupStat, stat)
		end
	end
end

local function renderFirerate(tbl, cfg, stat, lang)
	local cell = createCellInRow(tbl, cfg.name)
	local node, newcell = require('Module:WeaponInfobox/Firerate').renderFirerate(stat, lang)
	if newcell then
		tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
	else
		cell:node(node)
	end
end

local function renderProjectileSpeed(tbl, cfg, projectile_speed, projectile_speed_charged)
	if not aw.isNumberAndGreaterThanZero(projectile_speed) then
		return
	end
	
	local text
	if projectile_speed == math.huge then
		text = cfg.hitscan
	elseif aw.isNumberAndGreaterThanZero(move_speed_charged) then
		text = string.format(
			'<span class="text-type-number">%s</span> → <span class="text-type-number">%s</span>%s',
			string.format(cfg.format, aw.comma(aw.roundx(0.0254 * projectile_speed, 2))),
			string.format(cfg.format, aw.comma(aw.roundx(0.0254 * projectile_speed_charged, 2))),
			cfg.unit)
	else
		text = string.format(
			'<span class="text-type-number">%s</span>%s',
			string.format(cfg.format, aw.comma(aw.roundx(0.0254 * projectile_speed, 2))),
			cfg.unit)
	end
	renderRow(tbl, cfg.name, text)
end

local ShatterCapsMoveSpeedProto = {
	shatter_caps = {
		move_speed = proto.NumberRange(0, 1),
	},
}
local function getMoveSpeedText(cfg, moveSpeed)
	return string.format(
		'<span class="text-type-number">%s</span>%s',
		string.format(cfg.format, 100 * (moveSpeed - 1)),
		cfg.unit)
end
local function renderMoveSpeed(tbl, cfg, stat)
	if not aw.isNumberAndGreaterThanZero(stat.move_speed) then
		return
	end
	
	local text
	
	-- [Hop-up] Shatter Caps
	if proto.validateTypes(stat, ShatterCapsMoveSpeedProto) and stat.move_speed ~= stat.shatter_caps.move_speed then
		text = string.format(
			'%s%s<span class="block-rarity disp-rarity-epic text-rarity text-rarity-epic">%s %s</span>',
			getMoveSpeedText(cfg, stat.move_speed),
			cfg.separator,
			iu.hopup('shatter_caps'),
			getMoveSpeedText(cfg, stat.shatter_caps.move_speed))
	elseif aw.isNumberAndGreaterThanZero(stat.move_speed_charged) then
		text = string.format(
			'%s → %s',
			getMoveSpeedText(cfg, stat.move_speed),
			getMoveSpeedText(cfg, stat.move_speed_charged),
			cfg.unit)
	else
		text = getMoveSpeedText(cfg, stat.move_speed)
	end
	renderRow(tbl, cfg.name, text)
end

local function renderDPS(tbl, cfg, stat, lang)
	local sld  = require('Module:Stat/Shield')['reinforcehelmets']
	local node = require('Module:WeaponInfobox/DPS').renderDPS(stat, sld, lang)
	createCellInRow(tbl, cfg.name)
	tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
end

local function renderMagazineRow(intbl, name, mag, rsvmag, cfg, opts)
	opts = opts or {}
	intbl:tag('tr')
		:addClassIf(opts.class and type(opts.class) == 'string', opts.class)
		:tag('th')
			:wikitext(name)
			:done()
		:tag('td')
			:addClass('cell-type-number')
			:attr('align', 'right')
			:wikitext(mag)
			:done()
		:tag('td')
			:wikitext(cfg.unit)
			:done()
		:tag('td')
			:addClass('text-secondary')
			:wikitext('&nbsp;/&nbsp;')
			:done()
		:tag('td')
			:addClass('cell-type-number')
			:attr('align', 'right')
			:wikitext(rsvmag == math.huge and cfg.infinity or rsvmag)
			:done()
		:tag('td')
			:wikitext(cfg.unit)
end

local function calcVirtualMagSize(overheat, firerate, moddedLoader)
	if moddedLoader then
		return math.ceil(1.15 * overheat * firerate)
	else
		return math.ceil(overheat * firerate)
	end
end

local MultipleNumberProto = {
	proto.NumberRange(0),
	proto.NumberRange(0),
	proto.NumberRange(0),
	proto.NumberRange(0),
}
local function renderMagazine(tbl, cfg, stat, isSpecial, isModdedLoaderAttachable)
	local magazine = stat.magazine
	local typename = type(magazine)
	local text
	
	-- 物資投下武器
	if isSpecial or stat.ammo == 'minigun' then
		local magazine2
		if typename == 'table' then
			magazine2 = magazine[4]
		else
			magazine2 = magazine
		end
		
		if aw.isNumberAndGreaterThanZero(stat.magazine_reserve) then
			if isModdedLoaderAttachable then
				local moddedLoaderMagazine2 = apex.calcMagazineWithModdedLoader(magazine2)
				local moddedLoaderReserve   = stat.magazine_reserve - (moddedLoaderMagazine2 - magazine2)
				local intbl = createCellInRow(tbl, cfg.name)
					:tag('table')
						:addClass('condensedtable')
				renderMagazineRow(intbl, '', magazine2, stat.magazine_reserve, cfg)
				renderMagazineRow(intbl, iu.passive('modded_loader') .. '&nbsp;', moddedLoaderMagazine2, moddedLoaderReserve, cfg)
				return
			elseif magazine2 == math.huge then
				text = string.format(
					'%s <span class="text-separator">/</span> <span class="text-type-number">%s</span>%s',
					cfg.infinity,
					stat.magazine_reserve == math.huge and cfg.infinity or stat.magazine_reserve,
					cfg.unit)
			else
				text = string.format(
					'<span class="text-type-number">%d</span>%s <span class="text-separator">/</span> <span class="text-type-number">%s</span>%s',
					magazine2,
					cfg.unit,
					stat.magazine_reserve == math.huge and cfg.infinity or stat.magazine_reserve,
					cfg.unit)
			end
		else
			text = string.format('<span class="text-type-number">%d</span>%s', magazine2, cfg.unit)
		end
	
	-- 拡張マガジンあり
	elseif typename == 'table' then
		if isModdedLoaderAttachable then
			local intable = createCellInRow(tbl, cfg.name)
				:tag('table')
					:addClass('condensedtable')
			renderFormatRow(
				intable,
				'',
				magazine[1], magazine[2], magazine[3], magazine[4],
				{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
			local moddedLoaderMagazine = apex.calcMagazinesWithModdedLoader(magazine)
			renderFormatRow(
				intable,
				iu.passive('modded_loader') .. '&nbsp;',
				moddedLoaderMagazine[1],
				moddedLoaderMagazine[2],
				moddedLoaderMagazine[3],
				moddedLoaderMagazine[4],
				{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
			return
		
		-- Boosted Loader
		elseif stat.boosted_loader and proto.validateTypes(stat.boosted_loader.magazine, MultipleNumberProto) then
			local intable = createCellInRow(tbl, cfg.name)
				:tag('table')
					:addClass('condensedtable')
					:addClass('raritytable')
			renderFormatRow(
				intable,
				'',
				magazine[1], magazine[2], magazine[3], magazine[4],
				{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
			renderFormatRow(
				intable,
				iu.hopup('boosted_loader') .. '&nbsp;',
				stat.boosted_loader.magazine[1],
				stat.boosted_loader.magazine[2],
				stat.boosted_loader.magazine[3],
				stat.boosted_loader.magazine[4],
				{ rarity = 'legendary', separator = '&thinsp;-&thinsp;', footer = cfg.unit })
			return
		
		else
			text = formatter:format(magazine[1], magazine[2], magazine[3], magazine[4], cfg.unit, ' - ')
		end
	
	-- 拡張マガジンなし
	elseif typename == 'number' then
		if magazine == math.huge then
			if aw.isNumberAndGreaterThanZero(stat.firerate) then
				if proto.validateTypes(stat.overheat, MultipleNumberProto) then
					local intable = createCellInRow(tbl, cfg.name):tag('table'):addClass('condensedtable')
					renderFormatRow(
						intable,
						'',
						calcVirtualMagSize(stat.overheat[1], stat.firerate),
						calcVirtualMagSize(stat.overheat[2], stat.firerate),
						calcVirtualMagSize(stat.overheat[3], stat.firerate),
						calcVirtualMagSize(stat.overheat[4], stat.firerate),
						{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
					renderFormatRow(
						intable,
						cfg.seconds.name,
						string.format(cfg.seconds.format, stat.overheat[1]),
						string.format(cfg.seconds.format, stat.overheat[2]),
						string.format(cfg.seconds.format, stat.overheat[3]),
						string.format(cfg.seconds.format, stat.overheat[4]),
						{ class = 'no-list-style text-secondary text-smaller', headerAlign = 'right', separator = '&thinsp;-&thinsp;', footer = cfg.seconds.unit })
					if isModdedLoaderAttachable then
						renderFormatRow(
							intable,
							iu.passive('modded_loader') .. '&nbsp;',
							calcVirtualMagSize(stat.overheat[1], stat.firerate, true),
							calcVirtualMagSize(stat.overheat[2], stat.firerate, true),
							calcVirtualMagSize(stat.overheat[3], stat.firerate, true),
							calcVirtualMagSize(stat.overheat[4], stat.firerate, true),
							{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
						renderFormatRow(
							intable,
							cfg.seconds.name,
							string.format(cfg.seconds.format, 1.15 * stat.overheat[1]),
							string.format(cfg.seconds.format, 1.15 * stat.overheat[2]),
							string.format(cfg.seconds.format, 1.15 * stat.overheat[3]),
							string.format(cfg.seconds.format, 1.15 * stat.overheat[4]),
							{ class = 'no-list-style text-secondary text-smaller', headerAlign = 'right', separator = '&thinsp;-&thinsp;', footer = cfg.seconds.unit })
					end
					return
				elseif aw.isNumberAndGreaterThanZero(stat.overheat) then
					text = string.format(
						'%s (<span class="text-type-number">%d</span>%s <span class="text-separator">/</span> %s <span class="text-type-number">%d</span>%s)',
						cfg.infinity,
						1 + math.floor(stat.overheat * stat.firerate),
						cfg.unit,
						iu.passive('modded_loader'),
						1 + math.floor(1.15 * stat.overheat * stat.firerate),
						cfg.unit)
				else
					text = cfg.infinity
				end
			else
				text = cfg.infinity
			end
			
		elseif aw.isNumberAndGreaterThanX(stat.ammo_per_shot, 1) then
			local times = magazine / stat.ammo_per_shot
			local timesText
			if type(cfg.times.format) == 'table' then
				if times > 2 then
					timesText = string.format(cfg.times.format[3], times)
				else
					timesText = cfg.times.format[times]
				end
			else
				timesText = string.format(cfg.times.format, times)
			end
			
			text = string.format(
				'<span class="text-type-number">%d</span>%s <span class="text-secondary"><small>(%s)</small></span>',
				magazine, cfg.unit, timesText)
			
		else
			text = string.format('<span class="text-type-number">%d</span>%s', magazine, cfg.unit)
		end
	end
	
	renderRow(tbl, cfg.name, text)
end

local function renderDuration(tbl, cfg, stat, lang)
	local node, isSimple = require('Module:WeaponInfobox/Duration').renderDuration(stat, lang)
	local cell = createCellInRow(tbl, cfg.name)
	if isSimple then
		cell:node(node)
	else
		tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
	end
end

local function renderSpread(tbl, cfg, stat, lang, frame)
	local Spread = require('Module:WeaponInfobox/Spread')
	local node = Spread.renderSpread(stat.spread, stat.attachments.laser_sight, stat.quickdraw_holster and stat.quickdraw_holster.spread, lang)
	local cell = createCellInRow(tbl, cfg.name)
	if node.tagName == 'span' then
		cell:node(node)
	elseif Hopup.anvil_receiver.enabled and stat.anvil_receiver and stat.anvil_receiver.spread then
		local node2 = Spread.renderSpread(stat.anvil_receiver.spread, stat.attachments.laser_sight, nil, lang)
		if frame then
			local tabContent = string.format(
				'{{#tab:%s|%s}}{{#tab:%s|%s}}',
				cfg.normal, tostring(node),
				cfg.anvil_receiver, tostring(node2))
			tbl:tag('tr')
				:tag('td')
					:attr('colspan', 2)
					:wikitext(frame:extensionTag { name = 'tabs', content = tabContent, args = { class = 'tabs-intable' }})
		else
			tbl:tag('tr')
				:tag('td')
					:attr('colspan', 2)
					:tag('b')
						:wikitext(cfg.normal)
						:done()
					:node(node)
					:tag('b')
						:wikitext(cfg.anvil_receiver)
						:done()
					:node(node2)
		end
	elseif stat.spread_charged then
		local node2 = Spread.renderSpread(stat.spread_charged , nil, lang)
		if frame then
			local tabContent = string.format(
				'{{#tab:%s|%s}}{{#tab:%s|%s}}',
				cfg.normal, tostring(node),
				cfg.full_charge, tostring(node2))
			tbl:tag('tr')
				:tag('td')
					:attr('colspan', 2)
					:wikitext(frame:extensionTag { name = 'tabs', content = tabContent, args = { class = 'tabs-intable' }})
		else
			tbl:tag('tr')
				:tag('td')
					:attr('colspan', 2)
					:tag('b')
						:wikitext(cfg.normal)
						:done()
					:node(node)
					:tag('b')
						:wikitext(cfg.full_charge)
						:done()
					:node(node2)
		end
	else
		tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
	end
end

local function renderSpreadDecayRow(intbl, name, cfg, spread, opts)
	opts = opts or {}
	intbl:tag('tr')
		:addClassIf(type(opts.rarity) == 'string', 'row-rarity-one disp-rarity-' .. (opts.rarity or 'common'))
		:tag('th')
			:wikitext(name)
			:done()
		:tag('td')
			:addClass('cell-type-number')
			:attr('align', 'right')
			:wikitext(string.format(cfg.rate.format, spread.decay_rate))
			:done()
		:tag('td')
			:wikitext(cfg.rate.unit)
			:done()
		:tag('td')
			:addClass('text-smaller')
			:wikitext(cfg.delay.name)
			:done()
		:tag('td')
			:addClass('cell-type-number')
			:addClass('text-smaller')
			:attr('align', 'right')
			:wikitext(string.format(cfg.delay.format, spread.decay_delay))
			:done()
		:tag('td')
			:addClass('text-smaller')
			:wikitext(cfg.delay.unit)
end

local function renderSpreadDecay(tbl, cfg, stat)
	if not (aw.isNumberAndGreaterThanZero(stat.spread.decay_rate) and aw.isNumberAndGreaterThanZero(stat.spread.decay_delay)) then
		return
	end
	
	local cell = createCellInRow(tbl, cfg.name)
	
	-- [Hop-up] Anvil Receiver
	if stat.anvil_receiver and stat.anvil_receiver.spread and aw.isNumberAndGreaterThanZero(stat.anvil_receiver.spread.decay_rate) and aw.isNumberAndGreaterThanZero(stat.anvil_receiver.spread.decay_delay) then
		local intbl = cell:tag('table')
			:addClass('condensedtable')
			:addClass('raritytable')
		renderSpreadDecayRow(
			intbl,
			'',
			cfg,
			stat.spread)
		renderSpreadDecayRow(
			intbl,
			iu.hopup('anvil_receiver') .. '&nbsp;',
			cfg,
			stat.anvil_receiver.spread,
			{ rarity = 'legendary' })
	else
		local text = string.format(cfg.format, stat.spread.decay_rate, stat.spread.decay_delay)
		cell:wikitext(text)
	end
end

local function createTable(cfg, name, stat, lang, frame)
	local isSpecial = aw.stringstarts(stat.ammo, "special_")
	local isModdedLoaderAttachable = stat.category == 'light_machine_gun' or stat.ammo == 'minigun'
	local tbl = mw.html.create('table')
	renderReleaseDate(tbl, cfg.release, stat.release)
	renderCategory(tbl, cfg.category, stat.category, lang)
	renderAmmo(tbl, cfg.ammo, stat.ammo, lang)
	renderCost(tbl, cfg.cost, stat.cost, stat.ammo)
	renderMode(tbl, cfg.mode, stat, isSpecial)
	renderDamage(tbl, cfg.damage, stat, isSpecial)
	if stat.firerate then
		renderFirerate(tbl, cfg.firerate, stat, lang)
	end
	renderProjectileSpeed(tbl, cfg.projectilespeed, stat.projectile_speed, stat.projectile_speed_charged)
	renderMoveSpeed(tbl, cfg.movespeed, stat, stat)
	renderMagazine(tbl, cfg.magazine, stat, isSpecial, isModdedLoaderAttachable)
	if stat.firerate and name ~= 'ボセックコンパウンドボウ' then
		renderDPS(tbl, cfg.dps, stat, lang)
	end
	if stat.time then
		renderDuration(tbl, cfg.duration, stat, lang)
	end
	if stat.spread then
		renderSpread(tbl, cfg.spread, stat, lang, frame)
		renderSpreadDecay(tbl, cfg.spreaddecay, stat)
	end
	return tbl
end

local function renderInfobox(args, frame)
	local lang = args and args.lang or 'ja'
	local cfglang = cfg[lang]
	local stat = mw.loadData('Module:Stat/Weapon')[args.name]
	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(args.name .. iu.ammo(stat.ammo, { size = 40 }))
	div:node(createTable(cfglang, args.name, stat, lang, frame))
	return div
end

function p._main(args, frame)
	formatter = require('Module:Utility/Formatter').new(frame)
	return tostring(renderInfobox(args, frame))
end

function p.main(frame)
	if not getArgs then
		getArgs = require('Module:Arguments').getArgs
	end
	args = getArgs(frame)
	return p._main(args, frame)
end

return p