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

モジュール:WeaponInfobox

提供:Apex Data
2021年8月13日 (金) 15:13時点におけるMntone (トーク | 投稿記録)による版 (ブーステッドローダーの装弾数に対応)
ナビゲーションに移動 検索に移動

このモジュールについての説明文ページを モジュール: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 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)
	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 ' - '
	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 renderMode(tbl, cfg, stat)
	local builder = require('Module:Utility/StringBuilder').new()
	if stat.burst > 1 then
		builder:appendFormat(cfg.burst, stat.burst)
		builder:append(cfg.burst_category)
	end
	
	if stat.auto then
		if not builder:isEmpty() then
			builder:append(cfg.separator)
		end
		builder:append(cfg.auto, cfg.auto_category)
	end
	
	if stat.single then
		if not builder:isEmpty() then
			builder:append(cfg.separator)
		end
		builder:append(cfg.single, cfg.single_category)
	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 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('&nbsp;→&nbsp;')
				:done()
			:tag('td')
				:addClass('cell-type-number')
				:attr('align', 'right')
				:wikitext(string.format(cfg.rps.format, rps2))
				:done()
			:tag('td')
				:wikitext(cfg.rps.unit)
				:done()
			:done()
		:tag('tr')
			:addClassIf(type(opts.rarity) == 'string', 'row-rarity-last disp-rarity-' .. (opts.rarity or 'common'))
			:addClass('no-list-style')
			:addClass('text-secondary')
			:addClass('text-smaller')
			:tag('th')
				:attr('align', 'right')
				:wikitext(cfg.rpm.name)
				:done()
			:tag('td')
				:addClass('cell-type-number')
				:wikitext(aw.comma(aw.roundx(60 * rps1, 1)))
				:done()
			:tag('td')
				:wikitext('&nbsp;→&nbsp;')
				:done()
			:tag('td')
				:addClass('cell-type-number')
				:wikitext(aw.comma(aw.roundx(60 * rps2, 1)))
				:done()
			:tag('td')
				:wikitext(cfg.rpm.unit)
end

local function renderFirerateRow(intbl, name, cfg, rps, opts)
	opts = opts or {}
	intbl:tag('tr')
		:addClassIf(type(opts.rarity) == 'string', 'row-rarity-one disp-rarity-' .. (opts.rarity or 'common'))
		:addClassIf(opts.class and type(opts.class) == 'string', opts.class)
		:tag('th')
			:wikitext(name)
			:done()
		:tag('td')
			:addClass('cell-type-number')
			:attr('align', 'right')
			:wikitext(string.format(cfg.rps.format, rps))
			:done()
		:tag('td')
			:wikitext(cfg.rps.unit)
			:done()
		:tag('td')
			:tag('small')
				:addClass('text-secondary')
				:wikitext(cfg.rpm.name)
				:done()
			:done()
		:tag('td')
			:addClass('cell-type-number')
			:attr('align', 'right')
			:tag('small')
				:addClass('text-secondary')
				:wikitext(aw.comma(0.1 * aw.round(600 * rps)))
				:done()
			:done()
		:tag('td')
			:tag('small')
				:addClass('text-secondary')
				:wikitext(cfg.rpm.unit)
end

local function renderFirerateCell(cell, cfg, rps, opts)
	opts = opts or {}
	opts.header = opts.header or ''
	cell:wikitext(string.format(
		opts.header .. '<span class="text-type-number">' .. cfg.rps.format .. '</span>' .. cfg.rps.unit .. '<span class="text-secondary"><small>(<span class="text-type-number">%s</span>' .. cfg.rpm.unit .. '</small></span>',
		rps,
		aw.comma(0.1 * aw.round(600 * rps))))
end

local MultipleNumberProto = {
	proto.NumberRange(0),
	proto.NumberRange(0),
	proto.NumberRange(0),
	proto.NumberRange(0),
}
local AnvilReceiverProto = {
	firerate = {
		single = proto.NumberRange(0),
	},
}
local DeadeyesTempoWithChargeProto = {
	firerate = {
		single_charged = proto.NumberRange(0),
	},
}
local DeadeyesTempoWithRechamberProto = {
	firerate = {
		single_rechamber = proto.NumberRange(0),
	},
}
local function renderFirerate(tbl, cfg, stat)
	local firerate = stat.firerate
	if firerate == nil then
		return
	end
	
	local mode = stat.mode
	local cell = createCellInRow(tbl, cfg.name)
	local rps
	if 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 mode.burst > 1 then
			if type(firerate.auto) == 'table' then
				local newcell = tbl:tag('tr')
					:tag('td')
						:attr('colspan', 2)
				renderFirerateCell(newcell:tag('ul'):tag('li'), cfg, firerate.burst, { header = cfg.burst })
				
				local intbl = newcell:tag('table')
					:addClass('condensedtable')
					:addClass('listtable')
				local average = {
					calcBurstAverageRPS(stat, firerate.burst_delay[1]),
					calcBurstAverageRPS(stat, firerate.burst_delay[2]),
					calcBurstAverageRPS(stat, firerate.burst_delay[3]),
					calcBurstAverageRPS(stat, firerate.burst_delay[4]),
				}
				renderMultipleFirerateRow(intbl, cfg.burst_average, cfg, average, { rpsClass = 'no-list-style', rpmClass = 'no-list-style' })
				renderMultipleFirerateRow(intbl, cfg.auto,          cfg, firerate.auto, { rpmClass = 'no-list-style' })
			else
				local intbl = cell:tag('table')
					:addClass('condensedtable')
					:addClass('listtable')
				local average = calcBurstAverageRPS(stat)
				renderFirerateRow(intbl, cfg.burst,         cfg, firerate.burst)
				renderFirerateRow(intbl, cfg.burst_average, cfg, average, { class = 'no-list-style' })
				renderFirerateRow(intbl, cfg.auto,          cfg, firerate.auto)
			end
			return
		elseif aw.isNumberAndGreaterThanZero(firerate.auto_start) then
			local intbl = cell:tag('table')
				:addClass('condensedtable')
			renderVariableFirerateRow(intbl, '', cfg, firerate.auto_start, firerate.auto)
			
			if aw.isNumberAndGreaterThanZero(firerate.auto_start_turbocharger) then
				intbl:addClass('raritytable')
				renderVariableFirerateRow(intbl, iu.hopup('turbocharger') .. '&nbsp;', cfg, firerate.auto_start_turbocharger, firerate.auto, { rarity = 'legendary' })
			end
			return
		
		-- 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
		return
	end
	
	if type(rps) == 'table' then
		local intbl = cell:tag('table'):addClass('condensedtable')
		renderMultipleFirerateRow(intbl, '', cfg, rps)
	else
		renderFirerateCell(cell, cfg, rps)
	end
end

local function renderProjectileSpeed(tbl, cfg, projectile_speed, projectile_speed_charged)
	if projectile_speed == nil then
		return
	end
	
	local text
	if projectile_speed == math.huge then
		text = cfg.hitscan
	elseif projectile_speed_charged ~= nil 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 renderDPS(tbl, cfg, stat, lang)
	if stat.firerate == nil then
		return
	end
	
	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 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 intbl = createCellInRow(tbl, cfg.name)
					:tag('table')
						:addClass('condensedtable')
				renderMagazineRow(intbl, '', magazine2, stat.magazine_reserve, cfg)
				renderMagazineRow(intbl, iu.passive('modded_loader') .. '&nbsp;', aw.round(1.15 * magazine2), stat.magazine_reserve, cfg)
				return
			else
				text = string.format(
					'<span class="text-type-number">%d</span>%s <span class="text-separator">/</span> <span class="text-type-number">%s</span>%s',
					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 stat.rounds_per_shot and stat.rounds_per_shot.single and stat.rounds_per_shot.single > 1 then
			local times = magazine / stat.rounds_per_shot.single
			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)
	local node = require('Module:WeaponInfobox/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)
	else
		tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
	end
end

local function createTable(cfg, stat, lang)
	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.mode)
	renderDamage(tbl, cfg.damage, stat)
	renderFirerate(tbl, cfg.firerate, stat)
	renderProjectileSpeed(tbl, cfg.projectilespeed, stat.projectile_speed, stat.projectile_speed_charged)
	renderMagazine(tbl, cfg.magazine, stat, isSpecial, isModdedLoaderAttachable)
	renderDPS(tbl, cfg.dps, stat, lang)
	if stat.time then
		renderDuration(tbl, cfg.duration, stat, lang)
	end
	renderSpread(tbl, cfg.spread, stat, lang)
	return tbl
end

local function renderInfobox(args)
	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))
	return div
end

function p._main(args, frame)
	formatter = require('Module:Utility/Formatter').new(frame)
	return tostring(renderInfobox(args))
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