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

モジュール:WeaponInfobox

提供:Apex Data
2021年5月10日 (月) 23:56時点における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(opts.class and type(opts.class) == 'string', opts.class)
	
	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 })
			:wikitext(formatter:common(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 })
			:wikitext(formatter:rare(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 })
			:wikitext(formatter:epic(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)
	local category = nu.type(stat)
	local item = string.format('[[武器#%s|%s]][[Category:%s]]', category, category, category)
	renderRow(tbl, cfg.name, item)
end

local function renderAmmo(tbl, cfg, stat)
	local ammo = nu.ammo(stat)
	local classSuffix
	if aw.stringstarts(stat, 'special_') then
		classSuffix = 'special'
	else
		classSuffix = stat
	end
	local 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)
	renderRow(tbl, cfg.name, item)
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 renderDamage(tbl, cfg, stat, ammo, pellet)
	local base = stat.base
	local typename = type(base)
	local damageText
	if pellet ~= nil then
		damageText = string.format(cfg.shotgun, base, pellet)
	elseif typename == 'table' then
		damageText = table.concat(base, ' - ')
	elseif typename == 'number' then
		if stat.anvil_receiver ~= nil then
			damageText = string.format(
				'<span class="text-type-number">%s</span> <span class="text-separator">/</span> %s <span class="text-type-number">%s</span>',
				base,
				iu.hopup('anvil_receiver'),
				formatter:legendary(stat.anvil_receiver.base))
		elseif aw.isNumberAndGreaterThanZero(stat.amped) then
			damageText = string.format(
				'<span class="text-type-number">%s</span> <span class="text-separator">/</span> %s <span class="text-type-number">%s</span>',
				base,
				iu.item('シールドセル'),
				stat.amped)
		elseif aw.isNumberAndGreaterThanZero(stat.charged) then
			damageText = string.format(
				'<span class="text-type-number">%s</span> → <span class="text-type-number">%s</span>',
				base,
				stat.charged)
		else
			damageText = string.format('<span class="text-type-number">%s</span>', base)
		end
	else
		return
	end
	local cell = createCellInRow(tbl, cfg.name)
	cell:wikitext(damageText)
	
	local hlm1 = 0.2 + 0.8 * stat.headshot
	local hlm2 = 0.4 + 0.6 * stat.headshot
	local hlm3 = 0.5 + 0.5 * stat.headshot
	local leg = stat.legshot
	local sprm = stat.skullpiercer_rifling or 1
	local hlmc = stat.headshot_charged or 1
	
	local cattext = string.format(cfg.head_category, stat.headshot)
	local intable =
		--tbl:tag('tr'):tag('td'):attr('colspan', 2)
		cell
			:tag('table')
				:addClass('condensedtable')
				:addClass('listtable')
	renderFormatRow(
		intable,
		cfg.head,
		nil, hlm1, hlm2, hlm3,
		{ align = 'left', footer = cfg.unit .. cattext })
	if sprm > 1 then
		local spr1 = 0.2 + 0.8 * sprm
		local spr2 = 0.4 + 0.6 * sprm
		local spr3 = 0.5 + 0.5 * sprm
		renderFormatRow(
			intable,
			iu.hopup('skullpiercer_rifling') .. '&nbsp;',
			nil, spr1, spr2, spr3,
			{ class = 'no-list-style', align = 'left', footer = cfg.unit })
	elseif hlmc > 1 then
		local hlmc1 = 0.2 + 0.8 * hlmc
		local hlmc2 = 0.4 + 0.6 * hlmc
		local hlmc3 = 0.5 + 0.5 * hlmc
		renderFormatRow(
			intable,
			'→&nbsp;',
			nil, hlmc1, hlmc2, hlmc3,
			{ class = 'no-list-style', align = 'left', footer = cfg.unit })
	end
	
	local legtext
	if stat.anvil_receiver ~= nil then
		legtext = string.format(
			'<span class="text-type-number">%s</span>%s <span class="text-separator">/</span> %s %s',
			leg,
			cfg.unit,
			iu.hopup('anvil_receiver'),
			formatter:legendary('<span class="text-type-number">' .. stat.anvil_receiver.legshot .. '</span>' .. cfg.unit))
	elseif aw.isNumberAndGreaterThanZero(stat.legshot_charged) then
		legtext = string.format(
			'<span class="text-type-number">%s</span> → <span class="text-type-number">%s</span>%s',
			leg,
			stat.legshot_charged,
			cfg.unit)
	else
		legtext = string.format(
			'<span class="text-type-number">%s</span>%s',
			leg,
			cfg.unit)
	end
	
	intable:tag('tr')
		:tag('th')
			:wikitext(cfg.legs)
			:done()
		:tag('td')
			:attr('align', 'left')
			:attr('colspan', 8)
			:wikitext(legtext)
end

local function renderMultipleFirerateRow(intbl, name, cfg, rps, opts)
	opts = opts or {}
	renderFormatRow(
		intbl,
		name .. cfg.rps.name,
		string.format(cfg.rps.format, 0.01 * aw.round(100 * rps[1])),
		string.format(cfg.rps.format, 0.01 * aw.round(100 * rps[2])),
		string.format(cfg.rps.format, 0.01 * aw.round(100 * rps[3])),
		string.format(cfg.rps.format, 0.01 * aw.round(100 * rps[4])),
		{ class = opts.rpsClass or '', separator = cfg.rps.separator, footer = cfg.rps.unit })
	renderFormatRow(
		intbl,
		cfg.rpm.name,
		string.format(cfg.rpm.format, aw.comma(0.1 * aw.round(600 * rps[1]))),
		string.format(cfg.rpm.format, aw.comma(0.1 * aw.round(600 * rps[2]))),
		string.format(cfg.rpm.format, aw.comma(0.1 * aw.round(600 * rps[3]))),
		string.format(cfg.rpm.format, aw.comma(0.1 * aw.round(600 * rps[4]))),
		{ class = opts.rpmClass and 'all-secondary ' .. opts.rpmClass or 'all-secondary', 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(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, 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')
			:addClass('no-list-style')
			:tag('th')
				:attr('align', 'right')
				:tag('small')
					:addClass('text-secondary')
					:wikitext(cfg.rpm.name)
					:done()
				:done()
			:tag('td')
				:addClass('cell-type-number')
				:tag('small')
					:addClass('text-secondary')
					:wikitext(aw.comma(0.1 * aw.round(600 * rps1)))
					:done()
				:done()
			:tag('td')
				:attr('align', 'right')
				:tag('small')
					:addClass('text-secondary')
					:wikitext('&nbsp;→&nbsp;')
					:done()
				:done()
			:tag('td')
				:addClass('cell-type-number')
				:tag('small')
					:addClass('text-secondary')
					:wikitext(aw.comma(0.1 * aw.round(600 * rps2)))
					:done()
				:done()
			:tag('td')
				:tag('small')
					:addClass('text-secondary')
					:wikitext(cfg.rpm.unit)
end

local function renderFirerateRow(intbl, name, cfg, rps, 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(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 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
			if firerate.anvil_receiver or firerate.auto ~= firerate.single then
				local intbl = cell:tag('table')
								:addClass('condensedtable')
								:addClass('listtable')
				if firerate.auto ~= firerate.single then
					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
					renderFirerateRow(intbl, cfg.auto_single, cfg, firerate.auto)
				end
				
				if firerate.anvil_receiver then
					renderFirerateRow(intbl, iu.hopup('anvil_receiver'), cfg, firerate.anvil_receiver)
				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 })
				
				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('listtable')
				renderVariableFirerateRow(intbl, iu.hopup('turbocharger') .. '&nbsp;', cfg, firerate.auto_start_turbocharger, firerate.auto)
			end
			return
		end
		rps = firerate.auto
	elseif mode.single then
		if mode.burst > 1 then
			local intbl = cell:tag('table')
							:addClass('condensedtable')
							:addClass('listtable')
			local average = calcBurstAverageRPS(stat)
			renderFirerateRow(intbl, cfg.burst,         cfg, firerate.burst)
			renderFirerateRow(intbl, cfg.burst_average, cfg, average, { class = 'no-list-style' })
			
			local singleRPS
			if aw.isNumberAndGreaterThanZero(firerate.single_rechamber) then
				singleRPS = calcRechamberRPS(firerate)
			else
				singleRPS = firerate.single
			end
			renderFirerateRow(intbl, cfg.single, cfg, singleRPS)
			return
		end
		
		if proto.validateTypes(firerate.single_rechamber, MultipleNumberProto) then
			local calcRPS = {
				calcRechamberRPS(firerate, firerate.single_rechamber[1]),
				calcRechamberRPS(firerate, firerate.single_rechamber[2]),
				calcRechamberRPS(firerate, firerate.single_rechamber[3]),
				calcRechamberRPS(firerate, firerate.single_rechamber[4]),
			}
			
			local intbl = cell:tag('table'):addClass('condensedtable')
			renderMultipleFirerateRow(intbl, '', cfg, calcRPS)
			return
		elseif aw.isNumberAndGreaterThanZero(firerate.single_charged) then
			local maximum = calcRechamberRPS(firerate, firerate.single_charged)
			if aw.isNumberAndGreaterThanZero(firerate.single_charged_minimum) then
				local minimum = calcRechamberRPS(firerate, firerate.single_charged_minimum)
				local intbl = cell:tag('table'):addClass('condensedtable')
				renderVariableFirerateRow(intbl, '', cfg, minimum, maximum)
				return
			else
				rps = maximum
			end
		elseif aw.isNumberAndGreaterThanZero(firerate.single_rechamber) then
			rps = 1 / (1 / firerate.single + firerate.single_rechamber)
		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 renderRowDPS(tbl, name, leg, body, head, skullpiercer, tag)
	tag = tag or 'td'
	
	local row = tbl:tag('tr')
	row:tag('th'):wikitext(name)
	if leg == '' then
		row:tag(tag)
			:attr('align', 'center')
			:addClass('disabled')
			:wikitext('–')
	elseif leg ~= nil and leg ~= body then
		row:tag(tag):wikitext(leg)
	end
	row
		:tag(tag):wikitext(body):done()
		:tag(tag):wikitext(head)
	if skullpiercer ~= nil then
		row:tag(tag):wikitext(skullpiercer)
	end
end

local function toDPSText(dps)
	return string.format("%.1f", dps)
end

local function renderDPSTable(cell, name, pellet, base, damages, firerate, useRound)
	local headDamage = aw.round(damages.headshot * base)
	local legDamage = aw.round(damages.legshot * base)
	local skullpiercer = damages.skullpiercer_rifling
	local selectiveRound = useRound and aw.round or aw.roundover
	
	local skullpiercerHeader = nil
	local commonSkullpiercerDPS = nil
	local lowprofileSkullpiercerDPS = nil
	local fortifiedSkullpiercerDPS = nil
	if skullpiercer ~= nil then
		skullpiercerHeader = iu.hopup('skullpiercer_rifling')
		
		local skullpiercerDamage = aw.round(skullpiercer * base)
		commonSkullpiercerDPS = toDPSText(pellet * skullpiercerDamage * firerate)
		lowprofileSkullpiercerDPS = toDPSText(pellet * selectiveRound(1.05 * skullpiercerDamage) * firerate)
		fortifiedSkullpiercerDPS = toDPSText(pellet * aw.round(0.85 * skullpiercerDamage) * firerate)
	end
	
	if type(firerate) == 'table' then
		firerate = firerate[1]
	end
	
	local intable = cell:tag('table')
		:addClass('intable')
		:addClass('numbertable')
	local lowprofileLegDamagePlaceholder
	if damages.legshot == 1 then
		lowprofileLegDamagePlaceholder = nil
		renderRowDPS(intable, name, nil, '胴', '頭', skullpiercerHeader, 'th')
	else
		lowprofileLegDamagePlaceholder = ''
		renderRowDPS(intable, name, '脚', '胴', '頭', skullpiercerHeader, 'th')
	end
	renderRowDPS(
		intable,
		'通常',
		toDPSText(pellet * legDamage * firerate),
		toDPSText(pellet * base * firerate),
		toDPSText(pellet * headDamage * firerate),
		commonSkullpiercerDPS)
	renderRowDPS(
		intable,
		'小柄',
		lowprofileLegDamagePlaceholder,
		toDPSText(pellet * selectiveRound(1.05 * base, useRound) * firerate),
		toDPSText(pellet * 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

local function renderDPS(tbl, name, stat)
	if stat.firerate == nil then
		return
	end
	
	local damage = stat.damage.base
	renderRow(tbl, name, '')
	
	local pellet = stat.pellet or 1
	local useRound = stat.damage.round or false
	local cell = tbl:tag('tr'):tag('td'):attr('colspan', 2)
	
	if type(damage) == 'table' then
		for _, damage in ipairs(damage) do
			if stat.mode.single then
				renderDPSTable(
					cell,
					'',
					pellet,
					damage,
					stat.damage,
					stat.firerate.single,
					useRound)
			end
		end
	else
		if stat.mode.burst and stat.mode.burst > 1 then
			local average
			if type(stat.firerate.burst_delay) == 'table' then
				average = calcBurstAverageRPS(stat, stat.firerate.burst_delay[1])
			else
				average = calcBurstAverageRPS(stat)
			end
			renderDPSTable(
				cell,
				'',
				pellet,
				stat.damage.base,
				stat.damage,
				average,
				useRound)
		end
		
		if stat.mode.auto then
			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('anvil_receiver'),
				pellet,
				stat.damage.anvil_receiver.base,
				stat.damage.anvil_receiver,
				stat.firerate.anvil_receiver,
				useRound)
		end
	end
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 renderMagazine(tbl, cfg, stat, isModdedLoaderAttachable)
	local magazine = stat.magazine
	local typename = type(magazine)
	if 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], { footer = cfg.units })
			renderFormatRow(
				intable,
				formatter:hopup('改造ローダー') .. '&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]),
				{ footer = cfg.units })
		else
			local text = formatter:format(magazine[1], magazine[2], magazine[3], magazine[4], cfg.units, ' - ')
			renderRow(tbl, cfg.name, text)
		end
	elseif typename == 'number' then
		local text
		if magazine == math.huge then
			if aw.isNumber(stat.overheat) and stat.firerate and stat.firerate.auto 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,
					formatter:hopup('改造ローダー'),
					1 + math.floor(1.15 * stat.overheat * stat.firerate.auto),
					cfg.unit)
			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)
			
		elseif aw.isNumber(stat.magazine_reserve) then
			if isModdedLoaderAttachable then
				local intbl = createCellInRow(tbl, cfg.name)
					:tag('table')
						:addClass('condensedtable')
				renderMagazineRow(intbl, '', magazine, stat.magazine_reserve, cfg)
				renderMagazineRow(intbl, formatter:hopup('改造ローダー') .. '&nbsp;', aw.round(1.15 * magazine), stat.magazine_reserve, cfg)
				return
			else
				text = string.format(
					'<span class="text-type-number">%d</span>%s <span class="text-separator">/</span> <span class="text-type-number">%s</span>%s',
					magazine,
					cfg.unit,
					stat.magazine_reserve == math.huge and cfg.infinity or stat.magazine_reserve,
					cfg.unit)
			end
			
		else
			text = string.format('<span class="text-type-number">%d</span>%s', magazine, cfg.unit)
		end
		renderRow(tbl, cfg.name, text)
	end
end

local function renderReload(tbl, cfg, reload, attachments, ammo, isModdedLoaderAttachable)
	if reload.full == nil then
		return
	end
	
	local muls
	if ammo == 'heavy' or ammo == 'special_heavy' then
		muls = { level2 = 0.92, level3 = 0.87 }
	else
		muls = { level2 = 0.95, level3 = 0.9 }
	end
	
	local incompatible = not attachments.extended_mag_or_shotgun_bolt
	if incompatible or ammo == 'shotgun' or aw.stringstarts(ammo, "special_") then
		if reload.tactical ~= nil and reload.tactical ~= reload.full then
			local tacticalText, fullText
			if incompatible or ammo == 'special_sniper' then
				tacticalText = tostring(reload.tactical)
				fullText     = tostring(reload.full)
			else
				tacticalText = tostring(muls.level3 * reload.tactical)
				fullText     = tostring(muls.level3 * reload.full)
			end
			
			local intable = createCellInRow(tbl, cfg.name)
				:tag('table')
					:addClass('condensedtable')
					:addClass('listtable')
			renderFormatRow(
				intable,
				cfg.tactical.name .. '&nbsp;',
				string.format(cfg.tactical.format, reload.tactical),
				nil, nil, nil, { footer = cfg.tactical.unit })
			if isModdedLoaderAttachable then
				renderFormatRow(
					intable,
					formatter:hopup('改造ローダー') .. '&nbsp;',
					string.format(cfg.tactical.format, 0.75 * reload.tactical),
					nil, nil, nil, { class = 'no-list-style', headerAlign = 'right', footer = cfg.tactical.unit })
			end
			
			renderFormatRow(
				intable,
				cfg.full.name .. '&nbsp;',
				string.format(cfg.full.format, reload.full),
				nil, nil, nil, { footer = cfg.full.unit })
			if isModdedLoaderAttachable then
				renderFormatRow(
					intable,
					formatter:hopup('改造ローダー') .. '&nbsp;',
					string.format(cfg.full.format, 0.75 * reload.full),
					nil, nil, nil, { class = 'no-list-style', headerAlign = 'right', footer = cfg.full.unit })
			end
		elseif isModdedLoaderAttachable then
			local time
			if incompatible then
				time = reload.full
			else
				time = muls.level3 * reload.full
			end
			local text = string.format(
				cfg.full.format .. cfg.full.unit .. ' <span class="text-separator">/</span> %s ' .. cfg.full.format .. cfg.full.unit,
				time,
				formatter:hopup('改造ローダー'),
				0.75 * time)
			renderRow(tbl, cfg.name, text)
		else
			local time
			if incompatible or ammo == 'special_sniper' then
				time = reload.full
			else
				time = muls.level3 * reload.full
			end
			renderRow(tbl, cfg.name, string.format(cfg.full.format, time) .. cfg.full.unit)
		end
	elseif reload.tactical ~= nil and reload.tactical ~= reload.full then
		renderRow(tbl, cfg.name, '')
		
		local intable = tbl:tag('tr')
			:tag('td')
				:attr('colspan', 2)
					:tag('table')
						:addClass('condensedtable')
						:addClass('listtable')
		renderFormatRow(
			intable,
			cfg.tactical.name .. '&nbsp;',
			string.format(cfg.tactical.format, reload.tactical),
			nil,
			string.format(cfg.tactical.format, muls.level2 * reload.tactical),
			string.format(cfg.tactical.format, muls.level3 * reload.tactical),
			{ footer = cfg.tactical.unit })
		if isModdedLoaderAttachable then
			renderFormatRow(
				intable,
				formatter:hopup('改造ローダー') .. '&nbsp;',
				string.format(cfg.tactical.format, 0.75 * reload.tactical),
				nil,
				string.format(cfg.tactical.format, 0.75 * muls.level2 * reload.tactical),
				string.format(cfg.tactical.format, 0.75 * muls.level3 * reload.tactical),
				{ class = 'no-list-style', headerAlign = 'right', footer = cfg.tactical.unit })
		end
		
		renderFormatRow(
			intable,
			cfg.full.name .. '&nbsp;',
			string.format(cfg.full.format, reload.full),
			nil,
			string.format(cfg.full.format, muls.level2 * reload.full),
			string.format(cfg.full.format, muls.level3 * reload.full),
			{ footer = cfg.full.unit })
		if isModdedLoaderAttachable then
			renderFormatRow(
				intable,
				formatter:hopup('改造ローダー') .. '&nbsp;',
				string.format(cfg.full.format, 0.75 * reload.full),
				nil,
				string.format(cfg.full.format, 0.75 * muls.level2 * reload.full),
				string.format(cfg.full.format, 0.75 * muls.level3 * reload.full),
				{ class = 'no-list-style', headerAlign = 'right', footer = cfg.full.unit })
		end
	else
		local text = formatter:format(
			string.format(cfg.full.format, reload.full),
			nil,
			string.format(cfg.full.format, muls.level2 * reload.full),
			string.format(cfg.full.format, muls.level3 * reload.full),
			cfg.full.unit, ' - ')
		renderRow(tbl, cfg.name, text)
	end
end

local function renderDraw(tbl, cfg, draw, quickdraw_holster, attachments, ammo)
	if draw == nil then
		return
	end
	
	local muls
	if ammo == 'heavy' or ammo == 'special_heavy' then
		muls = { level2 = 0.92, level3 = 0.87 }
	else
		muls = { level2 = 0.95, level3 = 0.9 }
	end
	
	local incompatible = not attachments.stock
	if incompatible or aw.stringstarts(ammo, "special_") then
		local time
		if incompatible or ammo == 'special_sniper' then
			time = draw
		else
			time = 0.75 * draw
		end
		
		local text
		if quickdraw_holster > 0 then
			text = string.format(
				cfg.format .. cfg.unit .. ' <span class="text-separator">/</span> %s ' .. formatter:epic(cfg.format .. cfg.unit),
				time,
				iu.hopup('quickdraw_holster'),
				quickdraw_holster * time)
		else
			text = string.format(cfg.format, time) .. cfg.unit
		end
		renderRow(tbl, cfg.name, text)
	elseif quickdraw_holster > 0 then
		local intable = createCellInRow(tbl, cfg.name)
			:tag('table')
				:addClass('condensedtable')
		renderFormatRow(
			intable,
			'',
			string.format(cfg.format, draw),
			string.format(cfg.format, 0.85 * draw),
			string.format(cfg.format, 0.80 * draw),
			string.format(cfg.format, 0.75 * draw),
			{ footer = cfg.unit })
		renderFormatRow(
			intable,
			iu.hopup('quickdraw_holster') .. '&nbsp;',
			string.format(cfg.format, quickdraw_holster * draw),
			string.format(cfg.format, 0.85 * quickdraw_holster * draw),
			string.format(cfg.format, 0.80 * quickdraw_holster * draw),
			string.format(cfg.format, 0.75 * quickdraw_holster * draw),
			{ footer = cfg.unit })
	else
		local text = formatter:format(
			string.format(cfg.format, draw),
			string.format(cfg.format, 0.85 * draw),
			string.format(cfg.format, 0.80 * draw),
			string.format(cfg.format, 0.75 * draw),
			cfg.unit, ' - ')
		renderRow(tbl, cfg.name, text)
	end
end

local function renderSpread(tbl, cfg, stat)
	local node = require('Module:WeaponInfobox/Spread').renderSpread(stat, 'ja')
	createCellInRow(tbl, cfg.name)
	tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
end

local function createTable(cfg, stat)
	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)
	renderAmmo(tbl, cfg.ammo, stat.ammo)
	renderMode(tbl, cfg.mode, stat.mode)
	renderDamage(tbl, cfg.damage, stat.damage, stat.ammo, stat.pellet)
	renderFirerate(tbl, cfg.firerate, stat)
	renderProjectileSpeed(tbl, cfg.projectilespeed, stat.projectile_speed, stat.projectile_speed_charged)
	renderMagazine(tbl, cfg.magazine, stat, isModdedLoaderAttachable)
	if stat.time then
		if stat.time.draw then
			renderDraw(tbl, cfg.draw, stat.time.draw, stat.time.quickdraw_holster or 0, stat.attachments, stat.ammo)
		end
		
		if stat.time.reload then
			renderReload(tbl, cfg.reload, stat.time.reload, stat.attachments, stat.ammo, isModdedLoaderAttachable)
		end
	end
	renderDPS(tbl, 'DPS', stat)
	renderSpread(tbl, cfg.spread, stat.spread)
	return tbl
end

local function renderInfobox(args)
	local cfglang = cfg[args and args.lang or 'ja']
	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))
	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