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

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

提供:Apex Data
ナビゲーションに移動 検索に移動
(変数名の変更)
(アンビルレシーバーが出現しない場合にアンビルレシーバー用の拡散を非表示するように対処)
 
(同じ利用者による、間の186版が非表示)
2行目: 2行目:


local p = {}
local p = {}
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 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 formatter -- lazily initialized
local getArgs -- lazily initialized
local getArgs -- lazily initialized
local function stringStarts(str, start)
return string.sub(str, 1, string.len(start)) == start
end


local function createCellInRow(tbl, name)
local function createCellInRow(tbl, name)
28行目: 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
35行目: 39行目:
:wikitext(name)
:wikitext(name)
end
end
 
row:tag('td')
local first = true
:addClass('cell-type-number')
if default ~= nil then
:wikitext(default)
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 common ~= nil then
row:tag('td'):wikitext(opts.separator):done()
if not first then
:tag('td')
row:tag('td'):wikitext(opts.separator)
:addClass('cell-type-number')
end
:wikitext(formatter:common(common))
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
end
if rare ~= nil then
if rare ~= nil then
row:tag('td'):wikitext(opts.separator):done()
if not first then
:tag('td')
row:tag('td'):wikitext(opts.separator)
:addClass('cell-type-number')
end
:wikitext(formatter:rare(rare))
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
end
if epic ~= nil then
if epic ~= nil then
row:tag('td'):wikitext(opts.separator):done()
if not first then
:tag('td')
row:tag('td'):wikitext(opts.separator)
:addClass('cell-type-number')
end
:wikitext(formatter:epic(epic))
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
end
if opts.footer ~= nil then
if opts.footer ~= nil then
row:tag('td'):wikitext(opts.footer)
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
end
return row
return row
end
end


local CategoryDict = {
local function renderReleaseDate(tbl, cfg, release)
assault_rifle = "アサルトライフル",
local r = os.date("*t", release)
sub_machine_gun = "サブマシンガン",
local releaseText = string.format(
light_machine_gun = "ライトマシンガン",
'<time datetime="%04d-%02d-%02dT%02d:00:00.000+0900">%d年%d月%d日 %d時</time>~',
sniper = "スナイパーライフル",
r.year, r.month, r.day, r.hour, r.year, r.month, r.day, r.hour)
shotgun = "ショットガン",
renderRow(tbl, cfg.name, releaseText)
pistol = "ピストル",
end
}
 
local function renderCategory(tbl, name, stat)
local function renderCategory(tbl, cfg, stat, lang)
local category = CategoryDict[stat]
local category = nu.type(stat, lang, 1)
local item = string.format('[[武器#%s|%s]][[Category:%s]]', category, category, category)
local item
renderRow(tbl, name, 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
end


local AmmoDict = {
local function renderAmmo(tbl, cfg, stat, lang)
light = "ライトアモ",
createCellInRow(tbl, cfg.name)
heavy = "ヘビーアモ",
:addClass('cell-ammo')
energy = "エネルギーアモ",
:wikitext(inu.ammo(stat, { lang = lang, size = 24 }))
sniper = "スナイパーアモ",
shotgun = "ショットガンアモ",
special_light = "専用ライトアモ",
special_heavy = "専用ヘビーアモ",
special_energy = "専用エネルギーアモ",
special_sniper = "専用スナイパーアモ",
special_shotgun = "専用ショットガンアモ",
}
local function renderAmmo(tbl, name, stat)
local ammo = AmmoDict[stat]
local page = '弾薬#' .. ammo
local item = formatter:ammo(ammo, 24, page)
.. string.format(' [[%s|%s]][[Category:%s]]', page, ammo, ammo)
renderRow(tbl, name, item)
end
end


local function convertDecimalToFractional(num)
local function renderCost(tbl, cfg, stat, ammo)
local integer = math.floor(num)
if stat == nil or aw.stringstarts(ammo, 'special_') then
local decimal = tonumber(string.format("%.3f", num - integer))
return
local decimalText
if decimal == 0.125 then
decimalText = ''
elseif decimal == 0.2 then
decimalText = '⅕'
elseif decimal == 0.25 then
decimalText = '¼'
elseif decimal == 1/3 then
decimalText = '⅓'
elseif decimal == 0.375 then
decimalText = '⅜'
elseif decimal == 0.4 then
decimalText = '⅖'
elseif decimal == 0.5 then
decimalText = '½'
elseif decimal == 0.6 then
decimalText = '⅗'
elseif decimal == 0.625 then
decimalText = '⅝'
elseif decimal == 2/3 then
decimalText = '⅔'
elseif decimal == 0.75 then
decimalText = '¾'
elseif decimal == 0.8 then
decimalText = '⅘'
elseif decimal == 0.875 then
decimalText = '⅞'
else
decimalText = ''
end
end
if decimalText == '' then
local text = formatter:format(
return tostring(num)
aw.comma(stat[1]),
elseif integer == 0 then
aw.comma(stat[2]),
return decimalText
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
else
return tostring(integer) .. decimalText
builder:append(cfg.auto, cfg.auto_category)
end
end
end
end


local function renderMode(tbl, name, stat)
local function renderMode(tbl, cfg, stat, isSpecial)
local mode = nil
local builder = StringBuilder.new()
if stat.burst > 1 then
appendMode(builder, cfg, stat)
mode = stat.burst .. '点バースト'
if stat.altfire then
builder:append(cfg.separator)
appendMode(builder, cfg, stat.altfire, stat)
end
end
if (Hopup.selectfire_receiver.enabled or isSpecial) and stat.selectfire_receiver then
if stat.auto then
builder:append(cfg.separator, '<span class="block-rarity disp-rarity-epic">', iu.hopup('selectfire_receiver'), ' ')
if mode == nil then
appendMode(builder, cfg, stat.selectfire_receiver, stat)
mode = 'オート'
builder:append('</span>')
else
mode = mode .. '・オート'
end
end
end
if (Hopup.double_tap_trigger.enabled or isSpecial) and stat.double_tap_trigger then
if stat.single then
builder:append(cfg.separator, '<span class="block-rarity disp-rarity-epic">', iu.hopup('double_tap_trigger'), ' ')
if mode == nil then
appendMode(builder, cfg, stat.double_tap_trigger, stat)
mode = '単発'
builder:append('</span>')
else
mode = mode .. '・単発'
end
end
end
renderRow(tbl, cfg.name, tostring(builder))
renderRow(tbl, name, mode)
end
end


local function renderDamage(tbl, name, stat, ammo, pellet)
local HammerpointRoundsProto = {
local base = stat.base
hammerpoint_rounds = {
local typename = type(base)
damage_unshielded_scale = proto.NumberRange(1),
if pellet ~= nil then
},
local item = tostring(base) .. ' × ' .. tostring(pellet) .. '片'
}
renderRow(tbl, name, item)
local DisruptorRoundsProto = {
elseif typename == 'table' then
disruptor_rounds = {
renderRow(tbl, name, table.concat(base, ' - '))
damage_shield_scale = proto.NumberRange(1),
elseif typename == 'number' then
},
local damageText
}
if stat.anvil_receiver ~= nil then
local function getDamageText(stat, root, isSpecial)
damageText = base .. ' - '
local builder = StringBuilder.new()
.. iu.hopup('アンビルレシーバー')
local rarity  = isSpecial and 'heirloom'
.. '&nbsp;' .. formatter:legendary(stat.anvil_receiver.base)
local damage, pellet
else
if root then
damageText = tostring(base)
damage = stat.damage or root.damage
end
pellet = stat.pellet or root.pellet or 1
renderRow(tbl, name, damageText)
else
else
return
damage = stat.damage
pellet = stat.pellet or 1
end
end
if aw.isNumberAndGreaterThanOrEqualToX(pellet, 2) then
local head = convertDecimalToFractional(stat.headshot)
builder:appendFormat(
local hlm1 = convertDecimalToFractional(0.2 + 0.8 * stat.headshot)
'<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d',
local hlm2 = convertDecimalToFractional(0.4 + 0.6 * stat.headshot)
damage.base,
local hlm3 = convertDecimalToFractional(0.5 + 0.5 * stat.headshot)
pellet)
local leg = convertDecimalToFractional(stat.legshot)
local sprm = stat.skullpiercer_rifling or 1
local cattext = '[[Category:ヘッドショット倍率が' .. stat.headshot .. '倍の武器]]'
local ul = mw.html.create('ul')
if sprm > 1 then
local spr0 = convertDecimalToFractional(sprm)
local spr1 = convertDecimalToFractional(0.2 + 0.8 * sprm)
local spr2 = convertDecimalToFractional(0.4 + 0.6 * sprm)
local spr3 = convertDecimalToFractional(0.5 + 0.5 * sprm)
local intable = ul:tag('li')
if aw.isNumberAndGreaterThanZero(damage.charged) then
:tag('table')
builder:appendFormat(
:addClass('condensedtable')
'&thinsp;&rarr;&thinsp;<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d',
renderFormatRow(
damage.charged,
intable,
pellet)
'頭',
elseif proto.validateTypes(stat, HammerpointRoundsProto) then
head, hlm1, hlm2, hlm3,
if not rarity then
{ footer = '倍' .. cattext })
rarity = Hopup.hammerpoint_rounds.rarity
renderFormatRow(
end
intable,
builder:appendFormat(
iu.hopup('スカルピアサーライフリング') .. '&nbsp;',
'&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>',
spr0, spr1, spr2, spr3,
rarity,
{ footer = '倍' })
rarity,
iu.hopup('hammerpoint_rounds', { rarity = rarity }),
math.floor(damage.base * stat.hammerpoint_rounds.damage_unshielded_scale),
pellet)
end
else
else
local headText = '' .. formatter:format(head, hlm1, hlm2, hlm3, '', ' - ') .. cattext
if aw.isNumberAndGreaterThanZero(damage.amped) then
ul:tag('li'):wikitext(headText)
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
end
return tostring(builder)
local legText
end
if stat.anvil_receiver ~= nil then
 
local anvilleg = convertDecimalToFractional(stat.anvil_receiver.legshot)
local function renderHeadRow(tbl, name, head, cfg, nolist)
legText = '脚 ' .. leg .. ' - '
local opts = { align = 'left', footer = cfg.unit }
.. iu.hopup('アンビルレシーバー')
if nolist then
.. '&nbsp;' .. formatter:legendary(anvilleg) .. '倍'
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
else
legText = '' .. leg .. '倍'
text = string.format(
'<span class="text-type-number">%s</span>%s',
legs,
cfg.unit)
end
end
ul:tag('li'):wikitext(legText)
tbl:tag('tr')
tbl:tag('tr')
:tag('th')
:wikitext(cfg.legs)
:done()
:tag('td')
:tag('td')
:attr('colspan', 2)
:attr('align', 'left')
:node(ul)
:attr('colspan', 6)
:wikitext(text)
end
end


local rpsFormat = '%.2f'
local DamageProto = {
local rpmFormat = '%.0f'
damage = {
local function renderFirerate(tbl, name, firerate, mode)
base    = proto.NumberRange(1),
if firerate == nil then
},
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
return
end
end
local cell = createCellInRow(tbl, name)
local cattext = string.format(cfg.head_category, stat.damage.headshot_charged or stat.damage_head_scale or 1)
local rps
local cell = createCellInRow(tbl, cfg.name)
if mode.auto then
cell:wikitext(cattext .. getDamageText(stat, nil, isSpecial))
if mode.single then
if firerate.anvil_receiver then
-- Headshot
local ul = tbl:tag('tr'):tag('td'):attr('colspan', 2):tag('ul')
local head = stat.damage_head_scale or 1
ul:tag('li'):wikitext('オート: ' .. firerate.auto .. ' rps <small>(' .. (60 * firerate.auto) .. ' rpm)</small>')
local intable = cell:tag('table')
:addClass('condensedtable')
if firerate.auto ~= firerate.single then
:addClass('listtable')
ul:tag('li'):wikitext('単発: ' .. firerate.single .. ' rps <small>(' .. (60 * firerate.single) .. ' rpm)</small>')
renderHeadRow(intable, cfg.head, head, cfg)
end
local hlmc = stat.damage.headshot_charged or 1
if firerate.anvil_receiver then
if hlmc > 1 then
ul:tag('li')
local nameC = '→&nbsp;'
:wikitext(
renderHeadRow(intable, nameC, hlmc, cfg, true)
iu.hopup('アンビルレシーバー')
.. ': ' .. firerate.anvil_receiver .. ' rps <small>(' .. (60 * firerate.anvil_receiver) .. ' rpm)</small>')
end
elseif firerate.auto ~= firerate.single then
tbl:tag('tr')
:tag('td')
:attr('colspan', 2)
:tag('ul')
:tag('li')
:wikitext('オート: ' .. firerate.auto .. ' rps <small>(' .. (60 * firerate.auto) .. ' rpm)</small>')
:done()
:tag('li')
:wikitext('単発: ' .. firerate.single .. ' rps <small>(' .. (60 * firerate.single) .. ' rpm)</small>')
else
local rps = firerate.auto
cell:wikitext(rps .. ' rps <small>(' .. (60 * rps) .. ' rpm)</small>')
end
return
else
rps = firerate.auto
end
elseif mode.single then
rps = firerate.single
else
return
end
end
if type(rps) == 'table' then
local sprm = stat.damage.skullpiercer_rifling or 1
local intable = cell:tag('table'):addClass('condensedtable')
if sprm > 1 then
renderFormatRow(
local nameS = iu.hopup('skullpiercer_rifling') .. '&nbsp;'
intable,
renderHeadRow(intable, nameS, sprm, cfg, true)
'',
string.format(rpsFormat, rps[1]),
string.format(rpsFormat, rps[2]),
string.format(rpsFormat, rps[3]),
string.format(rpsFormat, rps[4]),
{ separator = '&thinsp;-&thinsp;', footer = '&thinsp;rps&nbsp;' })
renderFormatRow(
intable,
'(',
string.format(rpmFormat, 60 * rps[1]),
string.format(rpmFormat, 60 * rps[2]),
string.format(rpmFormat, 60 * rps[3]),
string.format(rpmFormat, 60 * rps[4]),
{ separator = '&thinsp;-&thinsp;', footer = '&thinsp;rpm)' })
else
cell:wikitext(string.format(
rpsFormat .. '&thinsp;rps <small>(' .. rpmFormat .. '&thinsp;rpm)</small>',
rps,
60 * rps))
end
end
end
renderHeadEffectiveRange(intable, cfg, stat, stat)
 
local function renderRowDPS(tbl, name, leg, body, head, skullpiercer, tag)
-- Legsshot
tag = tag or 'td'
renderLegsRow(intable, cfg, stat, stat)
local row = tbl:tag('tr')
-- [Hop-Up] Selectfire Receiver, Anvil Receiver & Shatter Caps
row:tag('th'):wikitext(name)
for _, v in ipairs(damageHopups) do
if leg == '' then
local hopupStat = stat[v.name]
row:tag(tag)
if (Hopup[v.name].enabled or isSpecial) and proto.validateTypes(hopupStat, v.proto) then
:attr('align', 'center')
local intblS = cell:tag('div')
:addClass('disabled')
:addClass('tpl-weapon-inbox')
:wikitext('–')
:addClass(specialOnly and 'disp-rarity-heirloom' or v.dispclass)
elseif leg ~= nil and leg ~= body then
:wikitext(iu.hopup(v.name) .. '&nbsp;')
row:tag(tag):wikitext(leg)
:tag('span')
end
:addClass('text-rarity')
row
:addClass(specialOnly and 'text-rarity-heirloom' or v.textclass)
:tag(tag):wikitext(body):done()
:wikitext(getDamageText(hopupStat, stat))
:tag(tag):wikitext(head)
:done()
if skullpiercer ~= nil then
:tag('table')
row:tag(tag):wikitext(skullpiercer)
: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
end
end


local function toDPSText(dps)
local function renderFirerate(tbl, cfg, stat, lang)
return string.format("%.1f", dps)
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
end


local function renderDPSTable(cell, name, pellet, base, damages, firerate, useRound)
local function renderProjectileSpeed(tbl, cfg, projectile_speed, projectile_speed_charged)
local headDamage = aw.round(damages.headshot * base)
if not aw.isNumberAndGreaterThanZero(projectile_speed) then
local legDamage = aw.round(damages.legshot * base)
return
local skullpiercer = damages.skullpiercer_rifling
local skullpiercerHeader = nil
local commonSkullpiercerDPS = nil
local lowprofileSkullpiercerDPS = nil
local fortifiedSkullpiercerDPS = nil
if skullpiercer ~= nil then
skullpiercerHeader = iu.hopup('スカルピアサーライフリング')
local skullpiercerDamage = aw.round(skullpiercer * base)
commonSkullpiercerDPS = toDPSText(pellet * skullpiercerDamage * firerate)
lowprofileSkullpiercerDPS = toDPSText(pellet * aw.selectiveRound(1.05 * skullpiercerDamage, useRound) * firerate)
fortifiedSkullpiercerDPS = toDPSText(pellet * aw.round(0.85 * skullpiercerDamage) * firerate)
end
if type(firerate) == 'table' then
firerate = firerate[1]
end
end
local intable = cell:tag('table')
local text
:addClass('intable')
if projectile_speed == math.huge then
:addClass('numbertable')
text = cfg.hitscan
local lowprofileLegDamagePlaceholder
elseif aw.isNumberAndGreaterThanZero(move_speed_charged) then
if damages.legshot == 1 then
text = string.format(
lowprofileLegDamagePlaceholder = nil
'<span class="text-type-number">%s</span> → <span class="text-type-number">%s</span>%s',
renderRowDPS(intable, name, nil, '胴', '頭', skullpiercerHeader, 'th')
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
else
lowprofileLegDamagePlaceholder = ''
text = string.format(
renderRowDPS(intable, name, '脚', '胴', '頭', skullpiercerHeader, 'th')
'<span class="text-type-number">%s</span>%s',
string.format(cfg.format, aw.comma(aw.roundx(0.0254 * projectile_speed, 2))),
cfg.unit)
end
end
renderRowDPS(
renderRow(tbl, cfg.name, text)
intable,
'通常',
toDPSText(pellet * legDamage * firerate),
toDPSText(pellet * base * firerate),
toDPSText(pellet * headDamage * firerate),
commonSkullpiercerDPS)
renderRowDPS(
intable,
'小柄',
lowprofileLegDamagePlaceholder,
toDPSText(pellet * aw.selectiveRound(1.05 * base, useRound) * firerate),
toDPSText(pellet * aw.selectiveRound(1.05 * headDamage, useRound) * firerate),
lowprofileSkullpiercerDPS)
renderRowDPS(
intable,
'鉄壁',
toDPSText(pellet * aw.round(0.85 * legDamage) * firerate),
toDPSText(pellet * aw.round(0.85 * base) * firerate),
toDPSText(pellet * aw.round(0.85 * headDamage) * firerate),
fortifiedSkullpiercerDPS)
end
end


local function renderDPS(tbl, name, stat)
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 damage = stat.damage.base
local text
renderRow(tbl, name, '')
local pellet = stat.pellet or 1
-- [Hop-up] Shatter Caps
local useRound = stat.damage.round or false
if proto.validateTypes(stat, ShatterCapsMoveSpeedProto) and stat.move_speed ~= stat.shatter_caps.move_speed then
local cell = tbl:tag('tr'):tag('td'):attr('colspan', 2)
text = string.format(
'%s%s<span class="block-rarity disp-rarity-epic text-rarity text-rarity-epic">%s %s</span>',
if type(damage) == 'table' then
getMoveSpeedText(cfg, stat.move_speed),
for _, damage in ipairs(damage) do
cfg.separator,
if stat.mode.single then
iu.hopup('shatter_caps'),
renderDPSTable(
getMoveSpeedText(cfg, stat.shatter_caps.move_speed))
cell,
elseif aw.isNumberAndGreaterThanZero(stat.move_speed_charged) then
'',
text = string.format(
pellet,
'%s → %s',
damage,
getMoveSpeedText(cfg, stat.move_speed),
stat.damage,
getMoveSpeedText(cfg, stat.move_speed_charged),
stat.firerate.single,
cfg.unit)
useRound)
end
end
else
else
if stat.mode.auto then
text = getMoveSpeedText(cfg, stat.move_speed)
renderDPSTable(
cell,
'',
pellet,
stat.damage.base,
stat.damage,
stat.firerate.auto,
useRound)
end
if stat.mode.single and stat.firerate.auto ~= stat.firerate.single then
renderDPSTable(
cell,
'',
pellet,
stat.damage.base,
stat.damage,
stat.firerate.single,
useRound)
end
if stat.damage.anvil_receiver and stat.firerate.anvil_receiver then
renderDPSTable(
cell,
iu.hopup('アンビルレシーバー'),
pellet,
stat.damage.anvil_receiver.base,
stat.damage.anvil_receiver,
stat.firerate.anvil_receiver,
useRound)
end
end
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
end


local function renderMagazine(table, name, stat, islmg)
local function calcVirtualMagSize(overheat, firerate, moddedLoader)
local typename = type(stat)
if moddedLoader then
if typename == 'table' then
return math.ceil(1.15 * overheat * firerate)
if islmg then
local intable = createCellInRow(table, name)
:tag('table')
:addClass('condensedtable')
renderFormatRow(
intable,
'',
stat[1], stat[2], stat[3], stat[4], { footer = '&thinsp;発' })
renderFormatRow(
intable,
formatter:hopup('改造ローダー') .. '&nbsp;',
aw.round(1.15 * stat[1]),
aw.round(1.15 * stat[2]),
aw.round(1.15 * stat[3]),
aw.round(1.15 * stat[4]),
{ footer = '&thinsp;発' })
else
local text = formatter:format(stat[1], stat[2], stat[3], stat[4], '&thinsp;発', ' - ')
renderRow(table, name, text)
end
elseif typename == 'number' then
if stat == math.huge then
renderRow(table, name, '∞')
else
renderRow(table, name, stat)
end
else
else
return
return math.ceil(overheat * firerate)
end
end
end
end


local reloadFormat = '%.2f'
local MultipleNumberProto = {
local function renderReload(tbl, name, reload, attachments, ammo, islmg)
proto.NumberRange(0),
if reload.full == nil then
proto.NumberRange(0),
return
proto.NumberRange(0),
end
proto.NumberRange(0),
}
local function renderMagazine(tbl, cfg, stat, isSpecial, isModdedLoaderAttachable)
local magazine = stat.magazine
local typename = type(magazine)
local text
local muls
-- 物資投下武器
if ammo == 'heavy' or ammo == 'special_heavy' then
if isSpecial or stat.ammo == 'minigun' then
muls = { level2 = 0.92, level3 = 0.87 }
local magazine2
else
if typename == 'table' then
muls = { level2 = 0.95, level3 = 0.9 }
magazine2 = magazine[4]
end
else
magazine2 = magazine
local incompatible = not attachments.extended_mag_or_shotgun_bolt
end
if incompatible or ammo == 'shotgun' or stringStarts(ammo, "special_") then
if reload.tactical ~= nil and reload.tactical ~= reload.full then
if aw.isNumberAndGreaterThanZero(stat.magazine_reserve) then
local tacticalText, fullText
if isModdedLoaderAttachable then
if incompatible or ammo == 'special_sniper' then
local moddedLoaderMagazine2 = apex.calcMagazineWithModdedLoader(magazine2)
tacticalText = tostring(reload.tactical)
local moddedLoaderReserve  = stat.magazine_reserve - (moddedLoaderMagazine2 - magazine2)
fullText    = tostring(reload.full)
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
else
tacticalText = tostring(muls.level3 * reload.tactical)
text = string.format(
fullText    = tostring(muls.level3 * reload.full)
'<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
end
else
local intable = createCellInRow(tbl, name)
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')
:tag('table')
:addClass('condensedtable')
:addClass('condensedtable')
:addClass('listtable')
renderFormatRow(
renderFormatRow(
intable,
intable,
'タクティカル&nbsp;',
'',
string.format(reloadFormat, reload.tactical),
magazine[1], magazine[2], magazine[3], magazine[4],
nil, nil, nil, { footer = '&thinsp;秒' })
{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
if islmg then
local moddedLoaderMagazine = apex.calcMagazinesWithModdedLoader(magazine)
renderFormatRow(
intable,
formatter:hopup('改造ローダー') .. '&nbsp;',
string.format(reloadFormat, 0.75 * reload.tactical),
nil, nil, nil, { class = 'no-list-style', headerAlign = 'right', footer = '&thinsp;秒' })
end
renderFormatRow(
renderFormatRow(
intable,
intable,
'フル',
iu.passive('modded_loader') .. '&nbsp;',
string.format(reloadFormat, reload.full),
moddedLoaderMagazine[1],
nil, nil, nil, { footer = '&thinsp;秒' })
moddedLoaderMagazine[2],
if islmg then
moddedLoaderMagazine[3],
renderFormatRow(
moddedLoaderMagazine[4],
intable,
{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
formatter:hopup('改造ローダー') .. '&nbsp;',
return
string.format(reloadFormat, 0.75 * reload.full),
nil, nil, nil, { class = 'no-list-style', headerAlign = 'right', footer = '&thinsp;秒' })
end
elseif islmg then
local text = string.format(
reloadFormat .. '&thinsp;秒 <span class="text-separator">/</span> %s ' .. reloadFormat .. '&thinsp;秒',
muls.level3 * reload.full,
formatter:hopup('改造ローダー'),
0.75 * muls.level3 * reload.full)
renderRow(tbl, name, text)
else
local time
if incompatible or ammo == 'special_sniper' then
time = reload.full
else
time = muls.level3 * reload.full
end
renderRow(tbl, name, string.format(reloadFormat .. '&thinsp;', time))
end
elseif reload.tactical ~= nil and reload.tactical ~= reload.full then
renderRow(tbl, name, '')
local intable = tbl:tag('tr')
-- Boosted Loader
:tag('td')
elseif stat.boosted_loader and proto.validateTypes(stat.boosted_loader.magazine, MultipleNumberProto) then
:attr('colspan', 2)
local intable = createCellInRow(tbl, cfg.name)
:tag('table')
:tag('table')
:addClass('condensedtable')
:addClass('condensedtable')
:addClass('listtable')
:addClass('raritytable')
renderFormatRow(
intable,
'タクティカル&nbsp;',
string.format(reloadFormat, reload.tactical),
nil,
string.format(reloadFormat, muls.level2 * reload.tactical),
string.format(reloadFormat, muls.level3 * reload.tactical),
{ footer = '&thinsp;秒' })
if islmg then
renderFormatRow(
renderFormatRow(
intable,
intable,
formatter:hopup('改造ローダー') .. '&nbsp;',
'',
string.format(reloadFormat, 0.75 * reload.tactical),
magazine[1], magazine[2], magazine[3], magazine[4],
nil,
{ separator = '&thinsp;-&thinsp;', footer = cfg.unit })
string.format(reloadFormat, 0.75 * muls.level2 * reload.tactical),
string.format(reloadFormat, 0.75 * muls.level3 * reload.tactical),
{ class = 'no-list-style', headerAlign = 'right', footer = '&thinsp;' })
end
renderFormatRow(
intable,
'フル',
string.format(reloadFormat, reload.full),
nil,
string.format(reloadFormat, muls.level2 * reload.full),
string.format(reloadFormat, muls.level3 * reload.full),
{ footer = '&thinsp;秒' })
if islmg then
renderFormatRow(
renderFormatRow(
intable,
intable,
formatter:hopup('改造ローダー') .. '&nbsp;',
iu.hopup('boosted_loader') .. '&nbsp;',
string.format(reloadFormat, 0.75 * reload.full),
stat.boosted_loader.magazine[1],
nil,
stat.boosted_loader.magazine[2],
string.format(reloadFormat, 0.75 * muls.level2 * reload.full),
stat.boosted_loader.magazine[3],
string.format(reloadFormat, 0.75 * muls.level3 * reload.full),
stat.boosted_loader.magazine[4],
{ class = 'no-list-style', headerAlign = 'right', footer = '&thinsp;' })
{ 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
else
local text = formatter:format(
string.format(reloadFormat, reload.full),
nil,
string.format(reloadFormat, muls.level2 * reload.full),
string.format(reloadFormat, muls.level3 * reload.full),
'&thinsp;秒', ' - ')
renderRow(tbl, name, text)
end
end
renderRow(tbl, cfg.name, text)
end
end


local drawFormat = '%.2f'
local function renderDuration(tbl, cfg, stat, lang)
local function renderDraw(tbl, name, draw, quickdraw_holster, attachments, ammo)
local node, isSimple = require('Module:WeaponInfobox/Duration').renderDuration(stat, lang)
if draw == nil then
local cell = createCellInRow(tbl, cfg.name)
return
if isSimple then
end
cell:node(node)
local muls
if ammo == 'heavy' or ammo == 'special_heavy' then
muls = { level2 = 0.92, level3 = 0.87 }
else
else
muls = { level2 = 0.95, level3 = 0.9 }
tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
end
end
end
local incompatible = not attachments.stock
 
if incompatible or stringStarts(ammo, "special_") then
local function renderSpread(tbl, cfg, stat, lang, frame)
local time
local Spread = require('Module:WeaponInfobox/Spread')
if incompatible or ammo == 'special_sniper' then
local node = Spread.renderSpread(stat.spread, stat.attachments.laser_sight, stat.quickdraw_holster and stat.quickdraw_holster.spread, lang)
time = draw
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
else
time = 0.75 * draw
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
end
elseif stat.spread_charged then
local text
local node2 = Spread.renderSpread(stat.spread_charged , nil, lang)
if quickdraw_holster > 0 then
if frame then
text = string.format(
local tabContent = string.format(
drawFormat .. '&thinsp;秒 <span class="text-separator">/</span> %s ' .. formatter:epic(drawFormat .. '&thinsp;秒'),
'{{#tab:%s|%s}}{{#tab:%s|%s}}',
time,
cfg.normal, tostring(node),
iu.hopup('クイックドローホルスター'),
cfg.full_charge, tostring(node2))
quickdraw_holster * time)
tbl:tag('tr')
:tag('td')
:attr('colspan', 2)
:wikitext(frame:extensionTag { name = 'tabs', content = tabContent, args = { class = 'tabs-intable' }})
else
else
text = string.format(drawFormat .. '&thinsp;秒', time)
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
end
renderRow(tbl, name, text)
elseif quickdraw_holster > 0 then
local intable = createCellInRow(tbl, name)
:tag('table')
:addClass('condensedtable')
renderFormatRow(
intable,
'',
string.format(reloadFormat, draw),
string.format(reloadFormat, 0.85 * draw),
string.format(reloadFormat, 0.80 * draw),
string.format(reloadFormat, 0.75 * draw),
{ footer = '&thinsp;秒' })
renderFormatRow(
intable,
iu.hopup('クイックドローホルスター') .. '&nbsp;',
string.format(reloadFormat, quickdraw_holster * draw),
string.format(reloadFormat, 0.85 * quickdraw_holster * draw),
string.format(reloadFormat, 0.80 * quickdraw_holster * draw),
string.format(reloadFormat, 0.75 * quickdraw_holster * draw),
{ footer = '&thinsp;秒' })
else
else
local text = formatter:format(
tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
string.format(drawFormat, draw),
string.format(drawFormat, 0.85 * draw),
string.format(drawFormat, 0.80 * draw),
string.format(drawFormat, 0.75 * draw),
'&thinsp;秒', ' - ')
renderRow(tbl, name, text)
end
end
end
end


local function renderReleaseDate(tbl, name, release)
local function renderSpreadDecayRow(intbl, name, cfg, spread, opts)
local r = os.date("*t", release)
opts = opts or {}
local releaseText = string.format(
intbl:tag('tr')
'<time datetime="%04d-%02d-%02dT%02d:00:00.000+0900">%d年%d月%d日 %d時</time>~',
:addClassIf(type(opts.rarity) == 'string', 'row-rarity-one disp-rarity-' .. (opts.rarity or 'common'))
r.year, r.month, r.day, r.hour, r.year, r.month, r.day, r.hour)
:tag('th')
renderRow(tbl, name, releaseText)
: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
end


local function renderTable(args, stat)
local function renderSpreadDecay(tbl, cfg, stat)
stat = stat or mw.loadData('Module:Stat/Weapon')[args.name]
if not (aw.isNumberAndGreaterThanZero(stat.spread.decay_rate) and aw.isNumberAndGreaterThanZero(stat.spread.decay_delay)) then
return
end
local category = stat.category
local cell = createCellInRow(tbl, cfg.name)
local islmg = category == 'light_machine_gun'
-- [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')
local tbl = mw.html.create('table')
renderReleaseDate(tbl, 'リリース日', stat.release)
renderReleaseDate(tbl, cfg.release, stat.release)
renderCategory(tbl, '種類', category)
renderCategory(tbl, cfg.category, stat.category, lang)
renderAmmo(tbl, '弾薬', stat.ammo)
renderAmmo(tbl, cfg.ammo, stat.ammo, lang)
renderMode(tbl, '射撃モード', stat.mode)
renderCost(tbl, cfg.cost, stat.cost, stat.ammo)
renderDamage(tbl, 'ダメージ', stat.damage, stat.ammo, stat.pellet)
renderMode(tbl, cfg.mode, stat, isSpecial)
renderDPS(tbl, 'DPS', stat)
renderDamage(tbl, cfg.damage, stat, isSpecial)
renderFirerate(tbl, '射撃速度', stat.firerate, stat.mode)
if stat.firerate then
renderMagazine(tbl, '装填数', stat.magazine, islmg)
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
if stat.time then
if stat.time.draw then
renderDuration(tbl, cfg.duration, stat, lang)
renderDraw(tbl, '据銃時間', 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, 'リロード', stat.time.reload, stat.attachments, stat.ammo, islmg)
end
end
end
return tbl
return tbl
end
end


function p.getNode(stat, formatter2)
local function renderInfobox(args, frame)
formatter = formatter2 or require('Module:Utility/Formatter').new()
local lang = args and args.lang or 'ja'
return renderTable(nil, stat)
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
end


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(renderTable(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