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

モジュール:DamageTable

提供:Apex Data
2021年9月12日 (日) 12:55時点におけるMntone (トーク | 投稿記録)による版 (一部の表示形態で位置がずれる問題の修正)
ナビゲーションに移動 検索に移動

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

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

local p = {}

local table = require('Module:TableExtensions')
local aw    = require('Module:Utility/Library')
local apex  = require('Module:Utility/ApexLibrary')
local STKCalculator = require('Module:Apex/STKCalculator')
local getArgs -- lazily initialized

local function convertHslToRgb(hue, saturation, lightness)
	hue = math.max(0, math.min(1, hue))
	saturation = math.max(0, math.min(1, saturation))
	lightness = math.max(0, math.min(1, lightness))
	
	if saturation == 0 then
		return lightness, lightness, lightness
	else
		local function to(p, q, t)
			if t < 0 then t = t + 1 end
			if t > 1 then t = t - 1 end
			
			if t < 1/6 then
				return p + (q - p) * 6 * t
			elseif t < 3/6 then
				return q
			elseif t < 4/6 then
				return p + (q - p) * (2/3 - t) * 6
			else
				return p
			end
		end
		
		local q
		if lightness < 0.5 then
			q = lightness * (1 + saturation)
		else
			q = lightness + saturation - lightness * saturation
		end
		
		local p = 2 * lightness - q
		return to(p, q, hue + 1/3), to(p, q, hue), to(p, q, hue - 1/3)
	end
end

local function getColorAsString(hue)
	local r, g, b = convertHslToRgb(hue, 0.91, 0.89)
	return string.format('#%02X%02X%02X', 255 * r, 255 * g, 255 * b)
end

local function getShotCount(part, health, shield, info, gunshield)
	local damages = apex.calcShotCount(info.damage, part, info.mul, health, shield, gunshield or 0, info.opts)
	return #damages, damages
end

local function renderHeader(tbl, mul, colspan, shields, res)
	tbl:tag('tr')
		:tag('th')
			:attr('colspan', colspan)
			:attr('rowspan', 2)
			:wikitextIf(
				mul == 1,
				res.targets.caption,
				function()
					return string.format(res.targets.caption_format, mul)
				end)
			:done()
		:tag('th')
			:attr('rowspan', 2)
			:wikitext(res.damages.caption)
			:done()
		:tag('th')
			:attr('colspan', #shields)
			:wikitext(res.shotstokill)
	
	local row = tbl:tag('tr')
	for _, shield in ipairs(shields) do
		row:tag('th')
			:attrIf(
				shield.tooltip ~= nil,
				{ ['data-tooltip'] = shield.tooltip })
			:wikitext(shield.label)
	end
end

local function renderCell(row, part, health, shield, info, gunshield, rowspan)
	local shotCount, damages = getShotCount(part, health, shield, info, gunshield)
	local ratio = (shotCount - info.minCount) / info.diffCount
	local tooltip = { aw.stringifyRepeatingArray(damages, '→') }
	if info.ttkCalculator then
		local template = '<tr><th>%s&nbsp;</th><td class="cell-type-number" style="font-weight:bold">%d</td><td>&thinsp;ミリ秒</td></tr>'
		local ttkc = info.ttkCalculator
		local ttk0 = aw.round(1000 * ttkc:getAsLevel(shotCount, 0))
		local ttk1 = aw.round(1000 * ttkc:getAsLevel(shotCount, 1))
		local ttk2 = aw.round(1000 * ttkc:getAsLevel(shotCount, 2))
		local ttk3 = aw.round(1000 * ttkc:getAsLevel(shotCount, 3))
		
		if ttk2 ~= ttk3 then
			table.insert(tooltip, '<br><br>キルタイム:<table class="condensedtable listtable">')
			if ttk0 == ttk1 then
				table.insert(tooltip, string.format(template, 'なし・Lv.1', ttk0))
				table.insert(tooltip, string.format(template, 'Lv.2', ttk2))
			else
				table.insert(tooltip, string.format(template, 'なし', ttk0))
				table.insert(tooltip, string.format(template, 'Lv.1', ttk1))
				table.insert(tooltip, string.format(template, 'Lv.2', ttk2))
			end
			table.insert(tooltip, string.format(template, 'Lv.3', ttk3))
			table.insert(tooltip, '</table>')
		elseif ttk1 ~= ttk2 then
			table.insert(tooltip, '<br><br>キルタイム:<table class="condensedtable listtable">')
			if ttk0 == ttk1 then
				table.insert(tooltip, string.format(template, 'なし・Lv.1', ttk0))
			else
				table.insert(tooltip, string.format(template, 'なし', ttk0))
				table.insert(tooltip, string.format(template, 'Lv.1', ttk1))
			end
			table.insert(tooltip, string.format(template, 'Lv.2, 3', ttk2))
			table.insert(tooltip, '</table>')
		elseif ttk0 ~= ttk1 then
			table.insert(tooltip, '<br><br>キルタイム:<table class="condensedtable listtable">')
			table.insert(tooltip, string.format(template, 'なし', ttk0))
			table.insert(tooltip, string.format(template, 'Lv.1, 2, 3', ttk1))
			table.insert(tooltip, '</table>')
		else
			table.insert(tooltip, string.format('<br>キルタイム: <span class="text-style-number" style="font-weight:bold">%d</span>&thinsp;ミリ秒', ttk0))
		end
	end
	
	row:tag('td')
		:attrIf(rowspan > 1, { rowspan = rowspan })
		:attr('data-tooltip', table.concat(tooltip))
		:css('background-color', getColorAsString((info.diffCount < 5 and 0.65 or 0.85) * ratio))
		:wikitext(shotCount)
end

local function renderHeaderCell(row, name, tooltip, rowinfo)
	for i = 1, #rowinfo do
		local cellinfo = rowinfo[i]
		local colspan = cellinfo.colspan or 1
		local rowspan = cellinfo.rowspan or 1
		
		local cell = row:tag('th')
		if colspan > 1 then
			cell:attr('colspan', colspan)
		end
		if rowspan > 1 then
			cell:attr('rowspan', rowspan)
		end
		if cellinfo.breaktop then
			cell:css('border-top', '0 none transparent')
		end
		if cellinfo.breakbottom then
			cell:css('border-bottom', '0 none transparent')
		end
		--if cellinfo.showbottom then
		--	cell:css({
		--		['border-bottom-style'] = 'solid',
		--		['border-bottom-width'] = '1px',
		--	})
		--end
		if cellinfo.verticalAlign then
			cell:css('vertical-align', cellinfo.verticalAlign)
		end
		if i == #rowinfo then
			if tooltip ~= nil then
				cell:attr('data-tooltip', tooltip)
			end
			cell:wikitext(name)
		elseif cellinfo.content then
			cell:wikitext(cellinfo.content)
		else
			cell:wikitext('&nbsp;&nbsp;')
		end
	end
end

local function renderRow(tbl, name, tooltip, part, weaponinfo, rowinfo, res, gunshield)
	local row = tbl:tag('tr')
	renderHeaderCell(row, name, tooltip, rowinfo)
	
	local damage
	local opts2 = aw.shallowCopy(weaponinfo.opts)
	table.removeKey(opts2, 'beamdamage')
	table.removeKey(opts2, 'beamticks')
	if gunshield then
		table.removeKey(opts2, 'disruptorRounds')
		table.removeKey(opts2, 'hammerpointRounds')
		damage = apex.calcDamage(weaponinfo.damage, 1, 1, opts2)
	else
		damage = apex.calcDamage(weaponinfo.damage, part, weaponinfo.mul, opts2)
	end
	
	local text
	if not gunshield and weaponinfo.opts.disruptorRounds > 1 then
		local optsD = aw.shallowCopy(weaponinfo.opts)
		table.removeKey(optsD, 'disruptorRounds')
		local defaultDamage = apex.calcDamage(weaponinfo.damage, part, weaponinfo.mul, optsD)
		if weaponinfo.opts.pellets > 1 then
			text = string.format(
				res.damages.pelletsWithDisruptor,
				damage * weaponinfo.opts.pellets,
				damage,
				weaponinfo.opts.pellets,
				defaultDamage * weaponinfo.opts.pellets)
		else
			text = string.format(res.damages.disruptor, damage, defaultDamage)
		end
	elseif not gunshield and weaponinfo.opts.hammerpointRounds > 1 then
		local optsH = aw.shallowCopy(weaponinfo.opts)
		table.removeKey(optsH, 'hammerpointRounds')
		local defaultDamage = apex.calcDamage(weaponinfo.damage, part, weaponinfo.mul, optsH)
		if weaponinfo.opts.pellets > 1 then
			text = string.format(
				res.damages.pelletsWithHammerpoint,
				defaultDamage * weaponinfo.opts.pellets,
				damage * weaponinfo.opts.pellets,
				damage,
				weaponinfo.opts.pellets)
		else
			text = string.format(res.damages.hammerpoint, defaultDamage, damage)
		end
	elseif weaponinfo.opts.pellets > 1 then
		text = string.format(
			res.damages.pellets,
			damage * weaponinfo.opts.pellets,
			damage,
			weaponinfo.opts.pellets)
	elseif weaponinfo.opts.beamticks > 1 then
		local beamdamage = apex.calcDamageFromPartAndPassive(weaponinfo.opts.beamdamage, part, weaponinfo.mul, opts2)
		local alldamage = beamdamage * weaponinfo.opts.beamticks + damage
		text = string.format(
			res.damages.ticks,
			alldamage,
			beamdamage,
			weaponinfo.opts.beamticks,
			damage)
	else
		text = tostring(damage)
	end
	row:tag('td'):wikitext(text)
	
	for _, shield in ipairs(weaponinfo.shields) do
		renderCell(row, part, shield.health, shield.shield, weaponinfo, gunshield, 1)
	end
end

local function getTTKCalculator(args)
	if args.rps <= 0 then
		return nil
	end
	
	-- 射撃レート
	local firerate
	if args.rpsratio1 > 1 then
		if args.rpsratio2 > args.rpsratio1 then
			if args.rpsratio3 > args.rpsratio2 then
				firerate = {
					args.rps,
					args.rpsratio1 * args.rps,
					args.rpsratio2 * args.rps,
					args.rpsratio3 * args.rps,
				}
			else
				local rps2 = args.rpsratio2 * args.rps
				firerate = {
					args.rps,
					args.rpsratio1 * args.rps,
					rps2,
					rps2,
				}
			end
		else
			local rps1 = args.rpsratio1 * args.rps
			firerate = {
				args.rps,
				rps1,
				rps1,
				rps1,
			}
		end
	else
		firerate = args.rps
	end
	
	-- 装填数
	local magazine
	if args.extmag0 > 0 then
		if args.extmag1 > args.extmag0 then
			if args.extmag2 > args.extmag1 then
				if args.extmag3 > args.extmag2 then
					magazine = { args.extmag0, args.extmag1, args.extmag2, args.extmag3 }
				else
					magazine = { args.extmag0, args.extmag1, args.extmag2, args.extmag2 }
				end
			else
				magazine = { args.extmag0, args.extmag1, args.extmag1, args.extmag1 }
			end
		else
			magazine = args.extmag0
		end
	else
		magazine = math.huge
	end
	
	-- リロード時間
	local reload
	if args.reloadratio2 > 0 and args.reloadratio2 < 1 then
		if args.reloadratio3 > 0 and args.reloadratio3 < args.reloadratio2 then
			if args.reloadratio1 > 0 and args.reloadratio1 < 1 and args.reloadratio1 > args.reloadratio2 then
				reload = {
					args.reload,
					args.reloadratio1 * args.reload,
					args.reloadratio2 * args.reload,
					args.reloadratio3 * args.reload
				}
			else
				reload = {
					args.reload,
					args.reload,
					args.reloadratio2 * args.reload,
					args.reloadratio3 * args.reload
				}
			end
		else
			local reload2 = args.reloadratio2 * args.reload
			reload = {
				args.reload,
				args.reload,
				reload2,
				reload2
			}
		end
	else
		reload = args.reload
	end
	
	local ttkc = require('Module:Apex/TTKCalculator').new(firerate, magazine, reload, {
		raiseTime = args.raise,
	})
	return ttkc
end

local function getContentLanguage(lang)
	local language = lang or mw.language.getContentLanguage().code
	return language == 'en' and 'en' or 'ja'
end

-- Calc all base damages
local function calcBaseDamages(damage, headScale, legsScale, helmets, opts)
	local headLv1Scale = helmets.level1.func(headScale)
	local headLv2Scale = helmets.level2.func(headScale)
	local headLv3Scale = helmets.level3.func(headScale)
	local ret = {
		scale_hed0 = headScale,
		scale_hed1 = headLv1Scale,
		scale_hed2 = headLv2Scale,
		scale_hed3 = headLv3Scale,
		scale_legs = legsScale,
	}
	
	if opts.beamdamage > 0 then
		ret.dschrg_hed0 = aw.round(headScale    * opts.beamdamage)
		ret.dschrg_hed1 = aw.round(headLv1Scale * opts.beamdamage)
		ret.dschrg_hed2 = aw.round(headLv2Scale * opts.beamdamage)
		ret.dschrg_hed3 = aw.round(headLv3Scale * opts.beamdamage)
		ret.dschrg_body = opts.beamdamage
		if legsScale < 1 then
			ret.dschrg_legs = aw.round(legsScale * opts.beamdamage)
		else
			ret.dschrg_legs = opts.beamdamage
		end
	end
	
	-- Amped Cover
	if opts.ampedCover then
		damage = aw.round(1.2 * damage)
	end
	
	-- Additional Scale
	if opts.charged > 1 then
		damage = aw.round(opts.charged * damage)
	end
	
	-- Unshield Scale
	if opts.hammerpointRounds > 1 then
		local shieldDamage = damage
		local unshldDamage = apex.calcHammerpointDamage(damage, opts.hammerpointRounds)
		
		-- Passthrough
		if opts.passthrough < 1 then
			shieldDamage = apex.calcPassthroughDamage(shieldDamage, opts.passthrough)
			unshldDamage = apex.calcPassthroughDamage(unshldDamage, opts.passthrough)
		end
		
		local shieldLegs, unshldLegs
		if legsScale < 1 then
			shieldLegs = aw.round(legsScale * shieldDamage)
			unshldLegs = aw.round(legsScale * unshldDamage)
		else
			shieldLegs = shieldDamage
			unshldLegs = unshldDamage
		end
		
		ret.shield_hed0 = aw.round(headScale    * shieldDamage)
		ret.shield_hed1 = aw.round(headLv1Scale * shieldDamage)
		ret.shield_hed2 = aw.round(headLv2Scale * shieldDamage)
		ret.shield_hed3 = aw.round(headLv3Scale * shieldDamage)
		ret.shield_body = shieldDamage
		ret.shield_legs = shieldLegs
			
		ret.unshld_hed0 = aw.round(headScale    * unshldDamage)
		ret.unshld_hed1 = aw.round(headLv1Scale * unshldDamage)
		ret.unshld_hed2 = aw.round(headLv2Scale * unshldDamage)
		ret.unshld_hed3 = aw.round(headLv3Scale * unshldDamage)
		ret.unshld_body = unshldDamage
		ret.unshld_legs = unshldLegs
		
	-- Other
	else
		-- Passthrough
		if opts.passthrough < 1 then
			damage = apex.calcPassthroughDamage(damage, opts.passthrough)
		end
		
		local legs
		if legsScale < 1 then
			legs = aw.round(legsScale * damage)
		else
			legs = damage
		end
		
		ret.shield_hed0 = aw.round(headScale    * damage)
		ret.shield_hed1 = aw.round(headLv1Scale * damage)
		ret.shield_hed2 = aw.round(headLv2Scale * damage)
		ret.shield_hed3 = aw.round(headLv3Scale * damage)
		ret.shield_body = damage
		ret.shield_legs = legs
		
		ret.unshld_hed0 = ret.shield_hed0
		ret.unshld_hed1 = ret.shield_hed1
		ret.unshld_hed2 = ret.shield_hed2
		ret.unshld_hed3 = ret.shield_hed3
		ret.unshld_body = ret.shield_body
		ret.unshld_legs = ret.shield_legs
	end
	
	return ret
end

-- Apply perk/shield scale to damages
local function applyPerkAndShieldScaleToDamages(damages, perkScale, opts)
	if perkScale ~= 1 then
		if opts.disruptorRounds > 1 then
			return table.map(damages, function(key, val)
				if aw.stringstarts(key, 'shield_') or aw.stringstarts(key, 'unshld_') or aw.stringstarts(key, 'dschrg_') then
					local perkedDamage = apex.calcPassiveDamage(val, perkScale, { round = opts.round })
					if aw.stringstarts(key, 'shield_') then
						return apex.calcDisruptorDamage(perkedDamage, opts.disruptorRounds)
					else
						return perkedDamage
					end
				else
					return val
				end
			end)
		else
			return table.map(damages, function(key, val)
				if aw.stringstarts(key, 'shield_') or aw.stringstarts(key, 'unshld_') or aw.stringstarts(key, 'dschrg_') then
					return apex.calcPassiveDamage(val, perkScale, { round = opts.round })
				else
					return val
				end
			end)
		end
	elseif opts.disruptorRounds > 1 then
		return table.map(damages, function(key, val)
			if aw.stringstarts(key, 'shield_') then
				return apex.calcDisruptorDamage(val, opts.disruptorRounds)
			else
				return val
			end
		end)
	else
		return damages
	end
end

-- Compare damage
local function equalPartDamage(damages, part0, part1)
	if damages['dschrg_' .. part0] and damages['dschrg_' .. part1] then
		return damages['shield_' .. part0] == damages['shield_' .. part1] and damages['unshld_' .. part0] == damages['unshld_' .. part1] and damages['dschrg_' .. part0] == damages['dschrg_' .. part1]
	else
		return damages['shield_' .. part0] == damages['shield_' .. part1] and damages['unshld_' .. part0] == damages['unshld_' .. part1]
	end
end

-- Render the damage row
local function renderRow2(tbl, name, tooltip, damages, partname, weaponinfo, rowinfo, res, rowspan, gunshield)
	rowspan = rowspan or 1
	
	local scale = damages['scale_' .. partname]
	local shieldDamage = damages['shield_' .. partname]
	local unshldDamage = damages['unshld_' .. partname]
	local dschrgDamage = damages['dschrg_' .. partname]
	
	local row = tbl:tag('tr')
	renderHeaderCell(row, name, tooltip, rowinfo)
	
	local text
	if shieldDamage > unshldDamage then
		if weaponinfo.opts.pellets > 1 then
			text = string.format(
				res.damages.pelletsWithDisruptor,
				shieldDamage * weaponinfo.opts.pellets,
				shieldDamage,
				weaponinfo.opts.pellets,
				unshldDamage * weaponinfo.opts.pellets)
		else
			text = string.format(res.damages.disruptor, shieldDamage, unshldDamage)
		end
	elseif shieldDamage < unshldDamage then
		if weaponinfo.opts.pellets > 1 then
			text = string.format(
				res.damages.pelletsWithHammerpoint,
				shieldDamage * weaponinfo.opts.pellets,
				unshldDamage * weaponinfo.opts.pellets,
				unshldDamage,
				weaponinfo.opts.pellets)
		else
			text = string.format(res.damages.hammerpoint, shieldDamage, unshldDamage)
		end
	elseif dschrgDamage and weaponinfo.opts.beamticks >= 1 then
		text = string.format(
			res.damages.ticks,
			dschrgDamage * weaponinfo.opts.beamticks + shieldDamage,
			dschrgDamage,
			weaponinfo.opts.beamticks,
			shieldDamage)
	else
		if weaponinfo.opts.pellets > 1 then
			text = string.format(
				res.damages.pellets,
				shieldDamage * weaponinfo.opts.pellets,
				shieldDamage,
				weaponinfo.opts.pellets)
		else
			text = tostring(shieldDamage)
		end
	end
	row:tag('td')
		:attr('rowspan', rowspan)
		:wikitext(text)
	
	for _, shield in ipairs(weaponinfo.shields) do
		renderCell(row, scale, shield.health, shield.shield, weaponinfo, gunshield, rowspan)
	end
end

-- Render the helmet damage
local function renderHelmetSection(tbl, damages, res, weaponinfo, helmets, info)
	local skipLvl1    = false
	local skipLvl2    = false
	local hed1Label   = string.format(res.targets.helmet1, damages.scale_hed1)
	local hed2Label   = string.format(res.targets.helmet2, damages.scale_hed2)
	local hed3Label   = string.format(res.targets.helmet3, damages.scale_hed3)
	local hed1Tooltip = string.format(helmets.level1.text, damages.scale_hed1)
	local hed2Tooltip = string.format(helmets.level2.text, damages.scale_hed2)
	local hed3Tooltip = string.format(helmets.level3.text, damages.scale_hed3)
	
	if not helmets.level0.disabled then
		local hed0Rowspan
		if equalPartDamage(damages, 'hed0', 'hed1') then
			if equalPartDamage(damages, 'hed0', 'hed2') then
				if equalPartDamage(damages, 'hed0', 'hed3') then
					hed0Rowspan = 4
				else
					hed0Rowspan = 3
				end
			else
				hed0Rowspan = 2
			end
		else
			hed0Rowspan = 1
		end
		
		local hed0Label = string.format(res.targets.head_format, damages.scale_hed0)
		renderRow2(tbl, hed0Label, nil, damages, 'hed0', weaponinfo, info.level1top, res, hed0Rowspan)
		if hed0Rowspan >= 4 then
			renderHeaderCell(tbl:tag('tr'), hed1Label, hed1Tooltip, info.level2first)
			renderHeaderCell(tbl:tag('tr'), hed2Label, hed2Tooltip, info.level2)
			renderHeaderCell(tbl:tag('tr'), hed3Label, hed3Tooltip, info.level2)
			return
		elseif hed0Rowspan == 3 then
			renderHeaderCell(tbl:tag('tr'), hed1Label, hed1Tooltip, info.level2first)
			renderHeaderCell(tbl:tag('tr'), hed2Label, hed2Tooltip, info.level2)
			skipLvl1 = true
			skipLvl2 = true
		elseif hed0Rowspan == 2 then
			renderHeaderCell(tbl:tag('tr'), hed1Label, hed1Tooltip, info.level2first)
			skipLvl1 = true
		end
	end
	
	-- Lvl 1
	if not skipLvl1 then
		local hed1Rowspan
		if equalPartDamage(damages, 'hed1', 'hed2') then
			if equalPartDamage(damages, 'hed1', 'hed3') then
				hed1Rowspan = 3
			else
				hed1Rowspan = 2
			end
		else
			hed1Rowspan = 1
		end
		
		renderRow2(tbl, hed1Label, hed1Tooltip, damages, 'hed1', weaponinfo, info.level2first, res, hed1Rowspan)
		if hed1Rowspan >= 3 then
			renderHeaderCell(tbl:tag('tr'), hed2Label, hed2Tooltip, info.level2)
			renderHeaderCell(tbl:tag('tr'), hed3Label, hed3Tooltip, info.level2)
			return
		elseif hed1Rowspan == 2 then
			renderHeaderCell(tbl:tag('tr'), hed2Label, hed2Tooltip, info.level2)
			skipLvl2 = true
		end
	end
	
	-- Lvl 2
	if not skipLvl2 then
		local hed2Rowspan
		if equalPartDamage(damages, 'hed2', 'hed3') then
			hed2Rowspan = 2
		else
			hed2Rowspan = 1
		end
		
		renderRow2(tbl, hed2Label, hed2Tooltip, damages, 'hed2', weaponinfo, info.level2, res, hed2Rowspan)
		if hed2Rowspan >= 2 then
			renderHeaderCell(tbl:tag('tr'), hed3Label, hed3Tooltip, info.level2)
			return
		end
	end
	
	-- Lvl 3
	renderRow2(tbl, hed3Label, hed3Tooltip, damages, 'hed3', weaponinfo, info.level2, res, 1)
end

local function renderTable(args)
	local lang = getContentLanguage(args.lang)
	local res = mw.loadData('Module:DamageTable/configuration')[lang]
	local shieldinfo = args.shieldinfo or require('Module:Stat/Shield')[args.shieldpreset]
	local skullpiercer = args.skullpiercer or 1
	local headMul = args.head or 2
	
	local hideNoHelmets = shieldinfo.helmets.level0.disabled
	local minHead
	if hideNoHelmets then
		minHead = shieldinfo.helmets.level1.func(args.headmax)
	else
		minHead = args.headmax
	end
	local minCount, _ = getShotCount(
		minHead,
		100,
		0,
		{
			damage = math.max(args.damage, args.damagemax),
			mul    = math.max(args.mul, args.mulmax),
			
			opts = {
				ampedCover            = true,
				beamdamage            = math.max(args.beamdamage,  args.beamdamagemax),
				beamticks             = math.max(args.ticks,       args.ticksmax),
				charged               = math.max(args.charged,     args.chargedmax),
				disruptorRounds       = math.max(args.disruptor,   args.disruptorsup),
				gunshieldBleedthrough = shieldinfo.gunshieldBleedthrough,
				hammerpointRounds     = math.max(args.hammerpoint, args.hammerpointsup),
				passthrough           = 1,
				pellets               = math.max(args.pellet,      args.pelletmax),
				round                 = args.round,
			}
		})
	local damagemin = math.min(args.damage, args.damagemin)
	local mininfo = {
		damage = damagemin,
		mul    = math.min(args.mul, args.mulmin),
		
		opts = {
			ampedCover            = false,
			beamdamage            = math.min(args.beamdamage,  args.beamdamagemin),
			beamticks             = math.min(args.ticks,       args.ticksmin),
			charged               = 1,
			disruptorRounds       = 1,
			gunshieldBleedthrough = shieldinfo.gunshieldBleedthrough,
			hammerpointRounds     = 1,
			passthrough           = math.min(args.passthrough, args.passthroughsup),
			pellets               = math.min(args.pellet, args.pelletmin),
			round                 = args.round,
		}
	}
	local maxshield = shieldinfo.shields[#shieldinfo.shields]
	local legCount, _ = getShotCount(math.min(args.leg, args.legmin), maxshield.health, maxshield.shield, mininfo)
	local bodyWithGunSheild, _ = getShotCount(1, maxshield.health, maxshield.shield, mininfo, shieldinfo.gunshield)
	local maxCount = math.max(legCount, bodyWithGunSheild)
	local ttkCalculator = getTTKCalculator(args)
	
	-- Define weapon info
	local weaponinfo = {
		damage     = args.damage,
		mul        = args.mul,
		
		minCount   = minCount,
		diffCount  = maxCount - minCount,
		shields    = shieldinfo.shields,
		helmets    = shieldinfo.helmets,
		ttkCalculator = ttkCalculator,
		
		opts = {
			ampedCover            = args.amped,
			beamdamage            = args.beamdamage,
			beamticks             = args.ticks,
			charged               = args.charged,
			disruptorRounds       = args.disruptor,
			gunshieldBleedthrough = shieldinfo.gunshieldBleedthrough,
			hammerpointRounds     = args.hammerpoint,
			passthrough           = args.passthrough,
			pellets               = args.pellet,
			round                 = args.round,
		},
	}
	
	local colspan, info
	if skullpiercer > 1 then
		colspan = 3
		info = {
			level1 = {
				{ breaktop = false, breakbottom = false, colspan = 3 },
			},
			level1top = {
				{ breaktop = true, breakbottom = true, colspan = 3 },
			},
			level1gunshiled = {
				{ breaktop = false, breakbottom = true, colspan = 3 },
			},
			level2 = {
				{ breaktop = true, breakbottom = false, colspan = 2 },
			},
			level2gunshiled = {
				{ breaktop = true, breakbottom = false },
				{ breaktop = false, breakbottom = false, colspan = 2 },
			},
			level3 = {
				{ breaktop = false, breakbottom = true },
				{ breaktop = false, breakbottom = false, showbottom = true },
			},
			level3first = {
				hideNoHelmets and { breaktop = false, breakbottom = false, rowspan = 6, content = res.targets.head, verticalAlign = 'top' } or { breaktop = true,  breakbottom = false, rowspan = 6 },
				{ breaktop = false, breakbottom = true },
				{ breaktop = false, breakbottom = false, showbottom = true },
			},
			level3top = {
				{ breaktop = false, breakbottom = true, colspan = 2 },
				{ breaktop = false, breakbottom = false, showbottom = true },
			},
		}
		info.level2first = info.level2
	else
		colspan = 2
		info = {
			level1 = {
				{ breaktop = false, breakbottom = false, colspan = 2 },
			},
			level1gunshiled = {
				{ breaktop = false, breakbottom = true, colspan = 2 },
			},
			level2 = {
				{ breaktop = false, breakbottom = false },
			},
			level2first = {
				hideNoHelmets and { breaktop = false, breakbottom = false, rowspan = 3, content = res.targets.head, verticalAlign = 'top' } or { breaktop = true,  breakbottom = false, rowspan = 3 },
				{ breaktop = false, breakbottom = false },
			},
			level2gunshiled = {
				{ breaktop = true, breakbottom = false },
				{ breaktop = false, breakbottom = false },
			},
		}
		info.level1top = info.level1gunshiled
	end
	
	local table = mw.html.create('table')
		:addClass('wikitable')
		:addClass('numbertable')
		:addClass('damagetable')
	if args.caption ~= nil then
		table:tag('caption')
			:wikitext(args.caption)
	end
	
	local baseDamages = calcBaseDamages(weaponinfo.damage, headMul, args.leg, weaponinfo.helmets, weaponinfo.opts)
	local perkedDamages = applyPerkAndShieldScaleToDamages(baseDamages, weaponinfo.mul, weaponinfo.opts)
	renderHeader(table, args.mul, colspan, shieldinfo.shields, res)
	
	-- Headshot damage
	if headMul > 1 then
		-- w/Skullpiercer Rifling
		if skullpiercer > 1 then
			if not hideNoHelmets then
				renderRow(
					table,
					string.format(res.targets.skullpiercer0, args.skullpiercerrarity, skullpiercer),
					nil,
					skullpiercer, weaponinfo, info.level3top, res)
				
				renderRow2(
					table,
					string.format(res.targets.head_format, perkedDamages.scale_hed0),
					nil,
					perkedDamages,
					'hed0',
					weaponinfo, info.level1top, res)
			end
			
			local skullpiercerLv1Mul = shieldinfo.helmets.level1.func(skullpiercer)
			renderRow(
				table,
				string.format(res.targets.skullpiercer1, args.skullpiercerrarity, skullpiercerLv1Mul),
				string.format(shieldinfo.helmets.level1.text, skullpiercer),
				skullpiercerLv1Mul, weaponinfo, info.level3first, res)
			renderRow2(
				table,
				string.format(res.targets.helmet1, perkedDamages.scale_hed1),
				string.format(shieldinfo.helmets.level1.text, headMul),
				perkedDamages,
				'hed1',
				weaponinfo, info.level2first, res)
			
			local skullpiercerLv2Mul = shieldinfo.helmets.level2.func(skullpiercer)
			renderRow(
				table,
				string.format(res.targets.skullpiercer2, args.skullpiercerrarity, skullpiercerLv2Mul),
				string.format(shieldinfo.helmets.level2.text, skullpiercer),
				skullpiercerLv2Mul, weaponinfo, info.level3, res)
			renderRow2(
				table,
				string.format(res.targets.helmet2, perkedDamages.scale_hed2),
				string.format(shieldinfo.helmets.level2.text, headMul),
				perkedDamages,
				'hed2',
				weaponinfo, info.level2, res)
			
			local skullpiercerLv3Mul = shieldinfo.helmets.level3.func(skullpiercer)
			renderRow(
				table,
				string.format(res.targets.skullpiercer3, args.skullpiercerrarity, skullpiercerLv3Mul),
				string.format(shieldinfo.helmets.level3.text, skullpiercer),
				skullpiercerLv3Mul, weaponinfo, info.level3, res)
			renderRow2(
				table,
				string.format(res.targets.helmet3, perkedDamages.scale_hed3),
				string.format(shieldinfo.helmets.level3.text, headMul),
				perkedDamages,
				'hed3',
				weaponinfo, info.level2, res)
		
		-- w/o Skullpiercer Rifling
		else
			renderHelmetSection(table, perkedDamages, res, weaponinfo, shieldinfo.helmets, info)
		end
	end
	
	-- Body & legs shot damage
	if perkedDamages.scale_legs >= 1 then
		if args.mul < 1 or args.forcegunshield then
			renderRow2(table, res.targets.bodylegs, nil, perkedDamages, 'body', weaponinfo, info.level1gunshiled, res)
			renderRow(
				table,
				res.targets.gunshield,
				shieldinfo.gunshieldLabel,
				1, weaponinfo, info.level2gunshiled, res, shieldinfo.gunshield)
		else
			renderRow2(table, res.targets.bodylegs, nil, perkedDamages, 'body', weaponinfo, info.level1, res)
		end
	else
		if shieldinfo.legAsBodyOnLowProfile and args.mul > 1 then
			renderRow2(table, res.targets.bodylegs, nil, perkedDamages, 'body', weaponinfo, info.level1, res)
		else
			if args.mul < 1 or args.forcegunshield then
				renderRow2(table, res.targets.body, nil, perkedDamages, 'body', weaponinfo, info.level1gunshiled, res)
				renderRow(
					table,
					res.targets.gunshield,
					shieldinfo.gunshieldLabel,
					1, weaponinfo, info.level2gunshiled, res, shieldinfo.gunshield)
			else
				renderRow2(table, res.targets.body, nil, perkedDamages, 'body', weaponinfo, info.level1, res)
			end
			
			local legsLabel = string.format(res.targets.legs, args.leg)
			renderRow2(table, legsLabel, nil, perkedDamages, 'legs', weaponinfo, info.level1, res)
		end
	end
	
	return table
end

function p._main(args, frame)
	-- init value
	local initValues = {
		damage = 20,
		damagemin = 1000,
		damagemax = 0,
		beamdamage = 0,
		beamdamagemin = 0,
		beamdamagemax = 0,
		ticks = 0,
		ticksmin = math.huge,
		ticksmax = 0,
		pellet = 1,
		pelletmin = 0,
		pelletmax = 0,
		head = 2,
		headmax = -1,
		leg = 0.8,
		legmin = 0,
		mul = 1,
		mulmin = 0.85,
		mulmax = 1,
		charged = 1,
		chargedmax = 1,
		skullpiercer = 1,
		disruptor = 1,
		disruptorsup = 1,
		hammerpoint = 1,
		hammerpointsup = 1,
		passthrough = 1,
		passthroughsup = 1,
		rps = 0,
		rpsratio1 = 0,
		rpsratio2 = 0,
		rpsratio3 = 0,
		raise = 0,
		reload = 0,
		reloadratio1 = 0.963,
		reloadratio2 = 0.933,
		reloadratio3 = 0.9,
		extmag0 = 0,
		extmag1 = 0,
		extmag2 = 0,
		extmag3 = 0,
	}
	
	-- fix arguments
	for key, value in pairs(initValues) do
		args[key] = aw.getAsNumber(args[key], value)
	end
	if args.pelletmin == 0 then
		args.pelletmin = args.pellet
	end
	if args.pelletmax == 0 then
		args.pelletmax = args.pellet
	end
	if args.headmax == -1 then
		args.headmax = math.max(args.head, args.skullpiercer)
	end
	if args.legmin == 0 then
		args.legmin = args.leg
	end
	if args.beamdamagemin == 0 then
		args.beamdamagemin = args.beamdamage
	end
	if args.beamdamagemax == 0 then
		args.beamdamagemax = args.beamdamage
	end
	args.round = aw.getAsBoolean(args.round, false)
	args.amped = aw.getAsBoolean(args.amped, false)
	args.forcegunshield = aw.getAsBoolean(args.forcegunshield, false)
	args.rarity = args.rarity or 'epic'
	args.skullpiercerrarity = args.skullpiercerrarity or 'legendary'
	args.shieldpreset = args.shieldpreset or 'removelowprofile'
	
	return tostring(renderTable(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