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

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

提供:Apex Data
ナビゲーションに移動 検索に移動
(物資投下武器のライトマシンガンでランパート使用中の予備弾数をリロード後の数にするように変更)
(射撃モードやレートのデータ構造変更による改変)
10行目: 10行目:
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)
168行目: 158行目:
'', ' - ')
'', ' - ')
renderRow(tbl, cfg.name, cfg.header .. text)
renderRow(tbl, cfg.name, cfg.header .. text)
end
local function appendMode(builder, cfg, stat, base)
if stat.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
end


local function renderMode(tbl, cfg, stat)
local function renderMode(tbl, cfg, stat)
local builder = require('Module:Utility/StringBuilder').new()
local builder = require('Module:Utility/StringBuilder').new()
if stat.burst > 1 then
appendMode(builder, cfg, stat)
builder:appendFormat(cfg.burst, stat.burst)
if stat.altfire then
builder:append(cfg.burst_category)
builder:append(cfg.separator)
appendMode(builder, cfg, stat.altfire, stat)
end
end
if stat.selectfire_receiver then
if stat.auto then
builder:append(cfg.separator, iu.hopup('selectfire_receiver'))
if not builder:isEmpty() then
appendMode(builder, cfg, stat.selectfire_receiver, stat)
builder:append(cfg.separator)
end
builder:append(cfg.auto, cfg.auto_category)
end
end
if stat.double_tap_trigger then
if stat.single then
builder:append(cfg.separator, iu.hopup('double_tap_trigger'))
if not builder:isEmpty() then
appendMode(builder, cfg, stat.double_tap_trigger, stat)
builder:append(cfg.separator)
end
builder:append(cfg.single, cfg.single_category)
end
end
renderRow(tbl, cfg.name, tostring(builder))
renderRow(tbl, cfg.name, tostring(builder))
end
end
348行目: 346行目:
end
end


local function renderMultipleFirerateRow(intbl, name, cfg, rps, opts)
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(' → ')
: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(' → ')
: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 function renderFirerate(tbl, cfg, stat)
local firerate = stat.firerate
local node, newcell = require('Module:WeaponInfobox/Firerate').renderFirerate(stat, lang)
if firerate == nil then
if newcell then
return
tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
end
local mode = stat.mode
local cell = createCellInRow(tbl, cfg.name)
local rps
if mode.burst > 1 and not mode.single 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' })
if firerate.auto then
renderMultipleFirerateRow(intbl, cfg.auto,          cfg, firerate.auto, { rpmClass = 'no-list-style' })
end
else
local intbl = cell:tag('table')
:addClass('condensedtable')
local average = calcBurstAverageRPS(stat)
if firerate.auto then
intbl:addClass('listtable')
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)
else
renderFirerateRow(intbl, '',                cfg, firerate.burst)
renderFirerateRow(intbl, cfg.burst_average, cfg, average, { class = 'no-list-style' })
end
end
return
elseif mode.auto then
if mode.single then
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 aw.isNumberAndGreaterThanZero(firerate.auto_start) then
local intbl = cell:tag('table')
:addClass('condensedtable')
renderVariableFirerateRow(intbl, '', cfg, firerate.auto_start, firerate.auto)
if stat.turbocharger and stat.turbocharger.firerate and aw.isNumberAndGreaterThanZero(stat.turbocharger.firerate.auto_start) then
intbl:addClass('raritytable')
renderVariableFirerateRow(
intbl,
iu.hopup('turbocharger') .. '&nbsp;',
cfg,
stat.turbocharger.firerate.auto_start,
stat.turbocharger.firerate.auto or stat.firerate.auto,
{ rarity = 'legendary' })
end
return
-- Amped firerate
elseif aw.isNumberAndGreaterThanZero(firerate.auto_amped) then
local intbl = cell:tag('table')
:addClass('condensedtable')
:addClass('raritytable')
renderFirerateRow(intbl, '',                                            cfg, firerate.auto)
renderFirerateRow(intbl, iu.grenade('テルミットグレネード') .. '&nbsp;', cfg, firerate.auto_amped, { rarity = 'common' })
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
createCellInRow(tbl, cfg.name):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
714行目: 400行目:


local function renderDPS(tbl, cfg, stat, lang)
local function renderDPS(tbl, cfg, stat, lang)
if stat.firerate == nil then
return
end
local sld  = require('Module:Stat/Shield')['removelowprofile']
local sld  = require('Module:Stat/Shield')['removelowprofile']
local node = require('Module:WeaponInfobox/DPS').renderDPS(stat, sld, lang)
local node = require('Module:WeaponInfobox/DPS').renderDPS(stat, sld, lang)
760行目: 442行目:
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 function renderMagazine(tbl, cfg, stat, isSpecial, isModdedLoaderAttachable)
local magazine = stat.magazine
local magazine = stat.magazine
1,038行目: 726行目:
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)
renderDamage(tbl, cfg.damage, stat)
renderDamage(tbl, cfg.damage, stat)
renderFirerate(tbl, cfg.firerate, stat)
if stat.firerate then
renderFirerate(tbl, cfg.firerate, stat)
end
renderProjectileSpeed(tbl, cfg.projectilespeed, stat.projectile_speed, stat.projectile_speed_charged)
renderProjectileSpeed(tbl, cfg.projectilespeed, stat.projectile_speed, stat.projectile_speed_charged)
renderMoveSpeed(tbl, cfg.movespeed, stat.move_speed, stat.move_speed_charged)
renderMoveSpeed(tbl, cfg.movespeed, stat.move_speed, stat.move_speed_charged)
renderMagazine(tbl, cfg.magazine, stat, isSpecial, isModdedLoaderAttachable)
renderMagazine(tbl, cfg.magazine, stat, isSpecial, isModdedLoaderAttachable)
renderDPS(tbl, cfg.dps, stat, lang)
if stat.firerate then
renderDPS(tbl, cfg.dps, stat, lang)
end
if stat.time then
if stat.time then
renderDuration(tbl, cfg.duration, stat, lang)
renderDuration(tbl, cfg.duration, stat, lang)

2021年8月22日 (日) 15:29時点における版

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

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

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

local aw = require('Module:Utility/Library')
local iu = require('Module:Utility/Image')
local nu = require('Module:Utility/Name')
local proto = require('Module:Utility/Prototypes')
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)
	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)
		:addClass('cell-ammo')
		:wikitext(item)
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 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)
	local builder = require('Module:Utility/StringBuilder').new()
	appendMode(builder, cfg, stat)
	if stat.altfire then
		builder:append(cfg.separator)
		appendMode(builder, cfg, stat.altfire, stat)
	end
	if stat.selectfire_receiver then
		builder:append(cfg.separator, iu.hopup('selectfire_receiver'))
		appendMode(builder, cfg, stat.selectfire_receiver, stat)
	end
	if stat.double_tap_trigger then
		builder:append(cfg.separator, iu.hopup('double_tap_trigger'))
		appendMode(builder, cfg, stat.double_tap_trigger, stat)
	end
	renderRow(tbl, cfg.name, tostring(builder))
end

local function getDamageText(stat)
	if aw.isNumber(stat.pellet) then
		if aw.isNumberAndGreaterThanZero(stat.damage.charged) then
			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,
				stat.pellet,
				stat.damage.charged,
				stat.pellet)
		else
			return string.format(
				'<span class="text-type-number">%s</span>&thinsp;&times;&thinsp;%d',
				stat.damage.base,
				stat.pellet)
		end
	else
		if aw.isNumberAndGreaterThanZero(stat.damage.amped) then
			return string.format(
				'<span class="text-type-number">%s</span> <span class="text-separator">/</span> %s <span class="text-type-number">%s</span>',
				stat.damage.base,
				iu.item('シールドセル'),
				stat.damage.amped)
		elseif aw.isNumberAndGreaterThanZero(stat.damage.charged) then
			return string.format(
				'<span class="text-type-number">%s</span> → <span class="text-type-number">%s</span>',
				stat.damage.base,
				stat.damage.charged)
		else
			return string.format('<span class="text-type-number">%s</span>', stat.damage.base)
		end
	end
end

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 }
	if nolist then
		opts.class = 'no-list-style'
		opts.headerAlign = 'right'
	end
	renderFormatRow(
		tbl,
		name,
		nil, hlm1, hlm2, hlm3,
		opts)
end

local function renderLegsRow(tbl, cfg, stat)
	local text
	if aw.isNumberAndGreaterThanZero(stat.legshot_charged) then
		text = string.format(
			'<span class="text-type-number">%s</span> → <span class="text-type-number">%s</span>%s',
			stat.legshot,
			stat.legshot_charged,
			cfg.unit)
	else
		text = string.format(
			'<span class="text-type-number">%s</span>%s',
			stat.legshot,
			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),
		headshot = proto.NumberRange(1),
		legshot  = proto.NumberRange(0, 1),
	},
}
local ShatterCapsProto = {
	damage = {
		base     = proto.NumberRange(1),
		headshot = proto.NumberRange(1),
		legshot  = proto.NumberRange(0, 1),
	},
	pellet = proto.NumberRange(1),
}
local damageHopups = {
	{
		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)
	if not proto.validateTypes(stat, DamageProto) then
		return
	end
	
	local cattext = string.format(cfg.head_category, stat.damage.headshot_charged or stat.damage.headshot)
	local cell = createCellInRow(tbl, cfg.name)
	cell:wikitext(cattext)
		:wikitext(getDamageText(stat))
	
	-- Headshot
	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
		local nameS = iu.hopup('skullpiercer_rifling') .. '&nbsp;'
		renderHeadRow(intable, nameS, sprm, cfg, true)
	elseif hlmc > 1 then
		local nameC = '→&nbsp;'
		renderHeadRow(intable, nameC, hlmc, cfg, true)
	end
	
	-- Legsshot
	renderLegsRow(intable, cfg, stat.damage)
	
	-- [Hop-Up] Anvil Receiver & Shatter Caps
	for _, v in ipairs(damageHopups) do
		if proto.validateTypes(stat[v.name], v.proto) then
			local intblS = cell:tag('div')
				:addClass('tpl-weapon-inbox')
				:addClass(v.dispclass)
				:wikitext(iu.hopup(v.name) .. '&nbsp;')
				:tag('span')
					:addClass('text-rarity')
					:addClass(v.textclass)
					:wikitext(getDamageText(stat[v.name]))
					:done()
				:tag('table')
					:addClass('condensedtable')
					:addClass('listtable')
			renderHeadRow(intblS, cfg.head, stat[v.name].damage.headshot, cfg)
			renderLegsRow(intblS, cfg, stat[v.name].damage)
		end
	end
end

local function renderFirerate(tbl, cfg, stat)
	local node, newcell = require('Module:WeaponInfobox/Firerate').renderFirerate(stat, lang)
	if newcell then
		tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
	else
		createCellInRow(tbl, cfg.name):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 function renderMoveSpeed(tbl, cfg, move_speed, move_speed_charged)
	if not aw.isNumberAndGreaterThanZero(move_speed) then
		return
	end
	
	local text
	if 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, 100 * (move_speed - 1)),
			string.format(cfg.format, 100 * (move_speed_charged - 1)),
			cfg.unit)
	else
		text = string.format(
			'<span class="text-type-number">%s</span>%s',
			string.format(cfg.format, 100 * (move_speed - 1)),
			cfg.unit)
	end
	renderRow(tbl, cfg.name, text)
end

local function renderDPS(tbl, cfg, stat, lang)
	local sld  = require('Module:Stat/Shield')['removelowprofile']
	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 = aw.round(1.15 * 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
			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 })
			renderFormatRow(
				intable,
				iu.passive('modded_loader') .. '&nbsp;',
				aw.round(1.15 * magazine[1]),
				aw.round(1.15 * magazine[2]),
				aw.round(1.15 * magazine[3]),
				aw.round(1.15 * magazine[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 stat.firerate and stat.firerate.auto 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.auto),
						calcVirtualMagSize(stat.overheat[2], stat.firerate.auto),
						calcVirtualMagSize(stat.overheat[3], stat.firerate.auto),
						calcVirtualMagSize(stat.overheat[4], stat.firerate.auto),
						{ 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 all-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.auto, true),
							calcVirtualMagSize(stat.overheat[2], stat.firerate.auto, true),
							calcVirtualMagSize(stat.overheat[3], stat.firerate.auto, true),
							calcVirtualMagSize(stat.overheat[4], stat.firerate.auto, 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 all-secondary text-smaller', headerAlign = 'right', separator = '&thinsp;-&thinsp;', footer = cfg.seconds.unit })
					end
					return
				elseif aw.isNumber(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.auto),
						cfg.unit,
						iu.passive('modded_loader'),
						1 + math.floor(1.15 * stat.overheat * stat.firerate.auto),
						cfg.unit)
				else
					text = cfg.infinity
				end
			else
				text = cfg.infinity
			end
			
		elseif aw.isNumber(stat.ammo_per_shot) and 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.quickdraw_holster and stat.quickdraw_holster.spread, lang)
	local cell = createCellInRow(tbl, cfg.name)
	if node.tagName == 'span' then
		cell:node(node)
	elseif stat.anvil_receiver and stat.anvil_receiver.spread then
		local node2 = Spread.renderSpread(stat.anvil_receiver.spread, 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
	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, 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)
	renderDamage(tbl, cfg.damage, stat)
	if stat.firerate then
		renderFirerate(tbl, cfg.firerate, stat)
	end
	renderProjectileSpeed(tbl, cfg.projectilespeed, stat.projectile_speed, stat.projectile_speed_charged)
	renderMoveSpeed(tbl, cfg.movespeed, stat.move_speed, stat.move_speed_charged)
	renderMagazine(tbl, cfg.magazine, stat, isSpecial, isModdedLoaderAttachable)
	if stat.firerate 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, 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