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

モジュール:DamageTable

提供:Apex Data
2021年3月26日 (金) 11:03時点におけるMntone (トーク | 投稿記録)による版 (ディスラプター弾に対応)
ナビゲーションに移動 検索に移動

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

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

local p = {}

local aw = require('Module:Utility/Library')
local apex = require('Module:Utility/ApexLibrary')
local iu = require('Module:Utility/Image')
local STKCalculator = require('Module:Apex/STKCalculator')
local formatter -- lazily initialized
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', 255 * r)
		.. string.format('%02X', 255 * g)
		.. string.format('%02X', 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, {
		ampedCover = info.amped,
		beamdamage = info.beamdamage,
		beamticks  = info.ticks,
		disruptorRounds   = info.disruptor,
		hammerpointRounds = info.hammerpoint,
		gunshieldBleedthrough = info.bleedthrough,
		pellets = info.pellet,
		round = info.useRound,
	})
	return #damages, damages
end

local function renderHeader(tbl, mul, colspan, shields)
	tbl:tag('tr')
		:tag('th')
			:attr('colspan', colspan)
			:attr('rowspan', 2)
			:wikitextIf(
				mul == 1,
				'部位',
				function()
					return '部位 (x' .. mul .. ')'
				end)
			:done()
		:tag('th')
			:attr('rowspan', 2)
			:wikitext('ダメージ')
			:done()
		:tag('th')
			:attr('colspan', #shields)
			:wikitext('確殺数')
	
	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)
	local shotCount, damages = getShotCount(part, health, shield, info, gunshield)
	local ratio = (shotCount - info.minCount) / (info.maxCount - info.minCount)
	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')
		:attr('data-tooltip', table.concat(tooltip))
		:css('background-color', getColorAsString(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 cell = row:tag('th')
		if colspan > 1 then
			cell:attr('colspan', colspan)
		end
		if cellinfo.breaktop then
			cell:css('border-top', '0 none')
		end
		if cellinfo.breakbottom then
			cell:css('border-bottom', '0 none')
		end
		if i == #rowinfo then
			if tooltip ~= nil then
				cell:attr('data-tooltip', tooltip)
			end
			cell:wikitext(name)
		else
			cell:wikitext('&nbsp;&nbsp;')
		end
	end
end

local function renderRow(tbl, name, tooltip, part, weaponinfo, rowinfo, gunshield)
	local row = tbl:tag('tr')
	renderHeaderCell(row, name, tooltip, rowinfo)
	
	local opts = {
		ampedCover            = weaponinfo.amped,
		disruptorRounds       = weaponinfo.disruptor,
		hammerpointRounds     = weaponinfo.hammerpoint,
		gunshieldBleedthrough = weaponinfo.bleedthrough,
		round                 = weaponinfo.useRound,
	}
	
	local damage
	if gunshield then
		damage = apex.calcDamage(weaponinfo.damage, 1, 1, {
			ampedCover = weaponinfo.amped,
			round      = weaponinfo.useRound,
		})
	else
		damage = apex.calcDamage(weaponinfo.damage, part, weaponinfo.mul, opts)
	end
	
	local text
	if weaponinfo.disruptor > 1 then
		local defaultDamage = apex.calcDamage(weaponinfo.damage, part, weaponinfo.mul, {
			ampedCover = weaponinfo.amped,
			round      = weaponinfo.useRound,
		})
		if weaponinfo.pellet > 1 then
			text = string.format(
				'%s <small><span style="white-space:nowrap">(%s&thinsp;×&thinsp;%s)</span> → %s </small>',
				damage * weaponinfo.pellet,
				damage,
				weaponinfo.pellet,
				defaultDamage * weaponinfo.pellet)
		else
			text = string.format(
				'%s<small> → %s</small>',
				damage,
				defaultDamage)
		end
	elseif weaponinfo.hammerpoint > 1 then
		local defaultDamage = apex.calcDamage(weaponinfo.damage, part, weaponinfo.mul, {
			ampedCover = weaponinfo.amped,
			round      = weaponinfo.useRound,
		})
		if weaponinfo.pellet > 1 then
			text = string.format(
				'<small>%s → </small>%s <small><span style="white-space:nowrap">(%s&thinsp;×&thinsp;%s)</span></small>',
				defaultDamage * weaponinfo.pellet,
				damage * weaponinfo.pellet,
				damage,
				weaponinfo.pellet)
		else
			text = string.format(
				'<small>%s → </small>%s',
				defaultDamage,
				damage)
		end
	elseif weaponinfo.pellet > 1 then
		text = string.format(
			'%s <small><span style="white-space:nowrap">(%s&thinsp;×&thinsp;%s)</span></small>',
			damage * weaponinfo.pellet,
			damage,
			weaponinfo.pellet)
	elseif weaponinfo.ticks > 1 then
		local beamdamage = apex.calcDamageFromPartAndPassive(weaponinfo.beamdamage, part, weaponinfo.mul, opts)
		local alldamage = beamdamage * weaponinfo.ticks + damage
		text = string.format(
			'%s<small> <span style="white-space:nowrap">(%s&thinsp;×&thinsp;%s&thinsp;+&thinsp;%s)</span></small>',
			alldamage,
			beamdamage,
			weaponinfo.ticks,
			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)
	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
			reload = {
				args.reload,
				args.reload,
				args.reloadratio2 * args.reload,
				args.reloadratio3 * args.reload
			}
		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, {
		raise = args.raise,
	})
	return ttkc
end

local function renderTable(args)
	local shieldinfo = require('Module:Stat/Shield')[args.shieldpreset]
	local skullpiercer = args.skullpiercer or 1
	local headMul = args.head or 2
	
	local minCount, _ = getShotCount(
		math.max(headMul, skullpiercer),
		100,
		0,
		{
			damage = math.max(args.damage, args.damagemax),
			beamdamage = math.max(args.beamdamage, args.beamdamagemax),
			ticks = args.ticks,
			mul = math.max(args.mul, args.mulmax),
			pellet = math.max(args.pellet, args.pelletmax),
			amped = true,
			disruptor = math.max(args.disruptor, args.disruptorsup),
			hammerpoint = math.max(args.hammerpoint, args.hammerpointsup),
		})
	local damagemin = math.min(args.damage, args.damagemin)
	local mininfo = {
		damage = damagemin,
		beamdamage = math.min(args.beamdamage, args.beamdamagemin),
		ticks = args.ticks,
		mul = math.min(args.mul, args.mulmin),
		pellet = math.min(args.pellet, args.pelletmin),
		amped = false,
		disruptor = 1,
		hammerpoint = 1,
		bleedthrough = shieldinfo.gunshieldBleedthrough,
	}
	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)
	local weaponinfo = {
		damage = args.damage,
		beamdamage = args.beamdamage,
		ticks = args.ticks,
		mul = args.mul,
		pellet = args.pellet,
		amped = args.amped,
		disruptor = args.disruptor,
		hammerpoint = args.hammerpoint,
		bleedthrough = shieldinfo.gunshieldBleedthrough,
		useRound = args.round,
		minCount = minCount,
		maxCount = maxCount,
		shields = shieldinfo.shields,
		ttkCalculator = ttkCalculator,
	}
	
	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 = true },
				{ breaktop = true, breakbottom = false, colspan = 2 },
			},
			level2gunshiled = {
				{ breaktop = true, breakbottom = false },
				{ breaktop = false, breakbottom = false, colspan = 2 },
			},
			level3 = {
				{ breaktop = true, breakbottom = true },
				{ breaktop = false, breakbottom = true },
				{ breaktop = false, breakbottom = false },
			},
			level3top = {
				{ breaktop = false, breakbottom = true, colspan = 2 },
				{ breaktop = false, breakbottom = false },
			},
		}
	else
		colspan = 2
		info = {
			level1 = {
				{ breaktop = false, breakbottom = true, colspan = 2 },
			},
			level1gunshiled = {
				{ breaktop = false, breakbottom = true, colspan = 2 },
			},
			level2 = {
				{ breaktop = true, breakbottom = true },
				{ breaktop = false, breakbottom = false },
			},
			level2gunshiled = {
				{ breaktop = true, breakbottom = false },
				{ breaktop = false, breakbottom = false },
			},
		}
		info.level1top = info.level1
	end
	
	local skullpiercerPrefix = iu.hopup('スカルピアサーライフリング', { rarity = args.skullpiercerrarity }) .. ' '
	local table = mw.html.create('table')
		:addClass('wikitable')
		:addClass('numbertable')
		:addClass('damagetable')
	if args.caption ~= nil then
		table:tag('caption')
			:wikitext(args.caption)
	end
	
	renderHeader(table, args.mul, colspan, shieldinfo.shields)
	
	if skullpiercer > 1 then
		renderRow(
			table,
			skullpiercerPrefix .. '(x' .. skullpiercer .. ')',
			nil,
			skullpiercer, weaponinfo, info.level3top)
	end
	
	renderRow(
		table,
		"頭 (x" .. headMul .. ")",
		nil,
		headMul, weaponinfo, info.level1top)
	
	if skullpiercer > 1 then
		local skullpiercerLv1Mul = shieldinfo.helmets.level1.func(skullpiercer)
		renderRow(
			table,
			skullpiercerPrefix .. formatter:common('(x' .. skullpiercerLv1Mul .. ')'),
			string.format(shieldinfo.helmets.level1.text, skullpiercer),
			skullpiercerLv1Mul, weaponinfo, info.level3)
	end
	
	local hlmLv1Mul = shieldinfo.helmets.level1.func(headMul)
	renderRow(
		table,
		formatter:common('Lv.1 (x' .. hlmLv1Mul .. ')'),
		string.format(shieldinfo.helmets.level1.text, headMul),
		hlmLv1Mul, weaponinfo, info.level2)
	
	if skullpiercer > 1 then
		local skullpiercerLv2Mul = shieldinfo.helmets.level2.func(skullpiercer)
		renderRow(
			table,
			skullpiercerPrefix .. formatter:rare('(x' .. skullpiercerLv2Mul .. ')'),
			string.format(shieldinfo.helmets.level2.text, skullpiercer),
			skullpiercerLv2Mul, weaponinfo, info.level3)
	end
	
	local hlmLv2Mul = shieldinfo.helmets.level2.func(headMul)
	renderRow(
		table,
		formatter:rare('Lv.2 (x' .. hlmLv2Mul .. ')'),
		string.format(shieldinfo.helmets.level2.text, headMul),
		hlmLv2Mul, weaponinfo, info.level2)
	
	if skullpiercer > 1 then
		local skullpiercerLv3Mul = shieldinfo.helmets.level3.func(skullpiercer)
		renderRow(
			table,
			skullpiercerPrefix .. formatter:epic('(x' .. skullpiercerLv3Mul .. ')'),
			string.format(shieldinfo.helmets.level3.text, skullpiercer),
			skullpiercerLv3Mul, weaponinfo, info.level3)
	end
	
	local hlmLv3Mul = shieldinfo.helmets.level3.func(headMul)
	renderRow(
		table,
		formatter:epic('Lv.3') .. '/' .. formatter:legendary('4') .. ' ' .. formatter:epic('(x' .. hlmLv3Mul .. ')'),
		string.format(shieldinfo.helmets.level3.text, headMul),
		hlmLv3Mul, weaponinfo, info.level2)
	
	if args.leg == 1 then
		if args.mul < 1 or args.forcegunshield then
			renderRow(table, "胴・脚", nil, 1, weaponinfo, info.level1gunshiled)
			renderRow(
				table,
				'+ガンシールド',
				shieldinfo.gunshieldLabel,
				1, weaponinfo, info.level2gunshiled, shieldinfo.gunshield)
		else
			renderRow(table, "胴・脚", nil, 1, weaponinfo, info.level1)
		end
	else
		if shieldinfo.legAsBodyOnLowProfile and args.mul == 1.05 then
			renderRow(table, "胴・脚", nil, 1, weaponinfo, info.level1)
		else
			if args.mul < 1 or args.forcegunshield then
				renderRow(table, "胴", nil, 1, weaponinfo, info.level1gunshiled)
				renderRow(
					table,
					'+ガンシールド',
					shieldinfo.gunshieldLabel,
					1, weaponinfo, info.level2gunshiled, shieldinfo.gunshield)
			else
				renderRow(table, "胴", nil, 1, weaponinfo, info.level1)
			end
			renderRow(table, "脚 (x" .. args.leg .. ")", nil, args.leg, weaponinfo, info.level1)
		end
	end
	
	return table
end

function p._main(args, frame)
	formatter = require('Module:Utility/Formatter').new(frame)
	
	-- init value
	local initValues = {
		damage = 20,
		damagemin = 1000,
		damagemax = 0,
		beamdamage = 0,
		beamdamagemin = 0,
		beamdamagemax = 0,
		ticks = 0,
		pellet = 1,
		pelletmin = 0,
		pelletmax = 0,
		head = 2,
		leg = 0.8,
		legmin = 0,
		mul = 1,
		mulmin = 0.85,
		mulmax = 1.05,
		skullpiercer = 1,
		disruptor = 1,
		disruptorsup = 1,
		hammerpoint = 1,
		hammerpointsup = 1,
		rps = 0,
		rpsratio1 = 0,
		rpsratio2 = 0,
		rpsratio3 = 0,
		raise = 0,
		reload = 0,
		reloadratio2 = 0.95,
		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.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.skullpiercerrarity = args.skullpiercerrarity or 'legendary'
	args.shieldpreset = args.shieldpreset or 'evoshieldonly'
	
	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