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

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

提供:Apex Data
ナビゲーションに移動 検索に移動
(ヘッドショット倍率の単位が欠損する不具合の修正)
(カテゴリーの単数・複数形の切り替え対応による変更)
105行目: 105行目:


local function renderCategory(tbl, cfg, stat, lang)
local function renderCategory(tbl, cfg, stat, lang)
local category = nu.type(stat, lang)
local category = nu.type(stat, lang, 1)
local item
local item
if lang == 'ja' then
if lang == 'ja' then

2021年5月14日 (金) 17: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 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, 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
	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 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,
		rarityclass = 'text-rarity-legendary',
		inboxclass = 'tpl-weapon-inbox-legendary',
	},
	{
		name = 'shatter_caps',
		proto = ShatterCapsProto,
		rarityclass = 'text-rarity-epic',
		inboxclass = 'tpl-weapon-inbox-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.inboxclass)
				:wikitext(iu.hopup(v.name) .. '&nbsp;')
				:tag('span')
					:addClass('text-rarity')
					:addClass(v.rarityclass)
					: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, 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.spread, stat.quickdraw_holster and stat.quickdraw_holster.spread, 'ja')
	createCellInRow(tbl, cfg.name)
	tbl:tag('tr'):tag('td'):attr('colspan', 2):node(node)
end

local function createTable(cfg, stat, lang)
	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)
	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, 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)
	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