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

モジュール:Apex/TTKCalculator

提供:Apex Data
2021年3月21日 (日) 13:36時点におけるMntone (トーク | 投稿記録)による版 (ミニガン「シーラ」の場合改造ローダーが反映されていない不具合の修正)
ナビゲーションに移動 検索に移動

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

local TTKCalculator = {}

local aw = require('Module:Utility/Library')

-- モード
TTKCalculator.BURST                 = 0
TTKCalculator.AUTO                  = 100
TTKCalculator.SINGLE                = 200
TTKCalculator.SINGLE_ANVIL_RECEIVER = 201

-- [prop] 発射レート
function TTKCalculator:_loadFirerates()
	local firerates = self.firerates
	local type      = type(firerates)
	if type == 'table' then
		self.firerate = firerates[1 + self.level]
	elseif type == 'string' then
		self.firerate = tonumber(firerates)
	else
		self.firerate = firerates
	end
end

function TTKCalculator:setFirerates(firerates)
	self.firerates = firerates or 1
	self:_loadFirerates()
end

-- [prop] 装填数
function TTKCalculator:_loadMagazines()
	local magazines = self.magazines
	local type      = type(magazines)
	if type == 'table' then
		self.magazine = magazines[1 + self.level]
	elseif type == 'string' then
		self.magazine = tonumber(magazines)
	else
		self.magazine = magazines
	end
end

function TTKCalculator:setMagazines(magazines)
	self.magazines = magazines or 20
	self:_loadMagazines()
	
	if self.isSegmented then
		self:_loadReloads()
	end
end

-- [prop] リロード時間
function TTKCalculator:_loadReloads()
	local reloads = self.reloads
	local type    = type(reloads)
	if type == 'table' then
		self.reload = reloads[1 + self.level]
	elseif type == 'string' then
		self.reload = tonumber(reloads)
	elseif self.isSegmented then
		self.reload = (self.magazine - 1) * self.reloads + self.reloadEnd
	else
		self.reload = reloads
	end
end

function TTKCalculator:setReloads(reloads)
	self.reloads = reloads or 0
	self:_loadReloads()
end

-- [prop] リチャンバー時間
function TTKCalculator:_loadRechambers()
	local rechambers = self.rechambers
	local type       = type(rechambers)
	if type == 'table' then
		self.rechamber = rechambers[1 + self.level]
	elseif type == 'string' then
		self.rechamber = tonumber(rechambers)
	else
		self.rechamber = rechambers
	end
end

function TTKCalculator:setRechambers(rechambers)
	self.rechambers = rechambers or 0
	self:_loadRechambers()
end

-- [prop] レベル
function TTKCalculator:_load()
	self:_loadFirerates()
	self:_loadMagazines()
	self:_loadReloads()
	self:_loadRechambers()
end

function TTKCalculator:setLevel(level)
	self.level = level
	self:_load()
end

-- [func] TTK算出関数
function TTKCalculator:_getTtk(left)
	return (left - 1) / self.firerate + (left - 1) * self.rechamber
end

function TTKCalculator:_getBurstTtkInOneMagazine(left, skipLastDelay)
	local ttk = 0
	while left > 0 do
		local current = math.min(self.burst, left)
		left = left - current
		if current > 1 then
			ttk = ttk + (current - 1) / self.firerate
		end
		
		if left == 0 and skipLastDelay then
			break
		end
		
		if current == self.burst then
			ttk = ttk + self.burstDelay
		end
	end
	return ttk
end

function TTKCalculator:_getVariableFirerateTtkInOneMagazine(left)
	local ttk = 0
	
	-- The first
	left = left - 1
	
	-- To the last
	while left > 0 do
		left = left - 1
		
		local current = self.start + (self.firerate - self.start) * math.min(1, ttk / self.duration)
		local time = 1 / current
		ttk = ttk + time + self.rechamber
	end
	return ttk
end


function TTKCalculator:get(shot)
	local ttk = 0
	local magazine = math.floor(self.magazine / self.roundsPerShot)

	-- バースト
	if self.burst > 1 and self.burst <= magazine then
		local function getTtkInOneMag(left, burst, burstDelay, firerate)
		end
		
		while shot > magazine do
			local shotTTK = self:_getBurstTtkInOneMagazine(magazine)
			ttk = ttk + self.raise + shotTTK + self.reload
			shot = shot - magazine
		end
		
		local shotTTK = self:_getBurstTtkInOneMagazine(shot, true)
		ttk = ttk + self.raise + shotTTK
	
	-- 可変オート
	elseif self.start > 0 and self.duration > 0 then
		local first = true
		local virtualMagazine = magazine - self.maximum
		if magazine > 0 then
			if self.startMaximum and shot > virtualMagazine then
				local shotTTK = self:_getTtk(virtualMagazine)
				ttk = ttk + self.raise + shotTTK + self.reload
				shot = shot - virtualMagazine
				first = false
			end
			
			while shot > magazine do
				local shotTTK = self:_getVariableFirerateTtkInOneMagazine(magazine)
				ttk = ttk + self.raise + shotTTK + self.reload
				shot = shot - magazine
			end
		end
		
		local shotTTK
		if self.startMaximum and first then
			shotTTK = self:_getTtk(shot)
		else
			shotTTK = self:_getVariableFirerateTtkInOneMagazine(shot)
		end
		ttk = ttk + self.raise + shotTTK
	
	-- 非バースト (オート・単発)
	else
		if magazine > 0 then
			while shot > magazine do
				local shotTTK = self:_getTtk(magazine)
				ttk = ttk + self.raise + shotTTK + self.reload
				shot = shot - magazine
			end
		end
		
		local shotTTK = self:_getTtk(shot)
		ttk = ttk + self.raise + shotTTK
	end
	return ttk
end

function TTKCalculator:getAsLevel(shot, level)
	self:setLevel(level)
	return self:get(shot)
end

-- コンストラクター
function TTKCalculator.new(firerates, magazines, reloads, opts)
	local obj = setmetatable({
		firerates     = firerates or 1,
		magazines     = magazines or math.huge,
		reloads       = reloads or 0,
		
		raise         = opts.raise or 0,
		start         = opts.start or 0,
		duration      = opts.duration or 0,
		maximum       = opts.maximum or 0,
		startMaximum  = opts.startMaximum or false,
		burst         = opts.burst or 1,
		burstDelay    = opts.burstDelay or 0,
		rechambers    = opts.rechambers or 0,
		reloadEnd     = opts.reloadEnd or 0,
		roundsPerShot = opts.roundsPerShot or 1,
		isSegmented   = opts.isSegmented,
		level         = opts.level or 0,
	}, { __index = TTKCalculator })
	obj:_load()
	return obj
end

function TTKCalculator.newFromStat(stat, mode, opts2)
	mode = mode or TTKCalculator.AUTO
	opts2 = opts2 or {}
	
	local firerate, magazine, reload
	local opts = { startMaximum = opts2.startMaximum or false }
	local isModdedLoader = (stat.category == 'light_machine_gun' or stat.ammo == 'minigun') and opts2.useModdedLoader
	
	-- バースト
	if stat.mode.burst > 1 and mode == TTKCalculator.BURST then
		firerate           = stat.firerate.burst
		opts.burst         = stat.mode.burst
		opts.burstDelay    = stat.firerate.burst_delay
		opts.rechambers    = 0
		opts.roundsPerShot = stat.rounds_per_shot and stat.rounds_per_shot.burst or 1
	
	-- オート
	elseif stat.mode.auto and (mode == TTKCalculator.AUTO or not stat.mode.single) then
		firerate = stat.firerate.auto
		if opts2.useTurbocharger then
			opts.raise    = stat.firerate.auto_raise_turbocharger    or 0
			opts.start    = stat.firerate.auto_start_turbocharger    or 0
			opts.duration = stat.firerate.auto_duration_turbocharger or 0
			opts.maximum  = stat.firerate.auto_maximum_turbocharger  or 0
		else
			opts.raise    = stat.firerate.auto_raise    or 0
			opts.start    = stat.firerate.auto_start    or 0
			opts.duration = stat.firerate.auto_duration or 0
			opts.maximum  = stat.firerate.auto_maximum  or 0
		end
		opts.rechambers    = 0
		opts.roundsPerShot = stat.rounds_per_shot and stat.rounds_per_shot.auto or 1
	
	-- 単発
	elseif stat.mode.single then
		-- アンビルレシーバー
		if mode == TTKCalculator.SINGLE_ANVIL_RECEIVER then
			firerate           = stat.firerate.anvil_receiver
			opts.rechambers    = stat.firerate.anvil_receiver_rechamber or 0
			opts.roundsPerShot = stat.rounds_per_shot and stat.rounds_per_shot.anvil_receiver or 1
		
		-- 通常の単発
		else
			firerate           = stat.firerate.single
			opts.rechambers    = stat.firerate.single_rechamber or 0
			opts.roundsPerShot = stat.rounds_per_shot and stat.rounds_per_shot.single or 1
		end
	
	-- プレースホルダー
	else
		firerate           = 1
		opts.rechambers    = 0
		opts.roundsPerShot = 1
	end
	
	-- 装填数
	if isModdedLoader then
		if type(stat.magazine) == 'table' then
			magazine = {
				aw.round(1.15 * stat.magazine[1]),
				aw.round(1.15 * stat.magazine[2]),
				aw.round(1.15 * stat.magazine[3]),
				aw.round(1.15 * stat.magazine[4]),
			}
		elseif stat.magazine == math.huge then
			if aw.isNumberAndGreaterThanZero(stat.overheat) then
				magazine = 1 + math.floor(1.15 * stat.overheat * firerate)
			else
				magazine = math.huge -- dummy
			end
		else
			magazine = aw.round(1.15 * stat.magazine)
		end
	
	-- L-スターEMG
	elseif stat.magazine == math.huge then
		if aw.isNumberAndGreaterThanZero(stat.overheat) then
			magazine = 1 + math.floor(stat.overheat * firerate)
		else
			magazine = math.huge -- dummy
		end
	
	-- 拡張マガジンがつかない場合
	else
		magazine = stat.magazine
	end
	
	-- セグメントリロード
	if stat.time.reload.segment then
		reload = stat.time.reload.segment.fullloop or stat.time.reload.segment.loop or 0
		opts.isSegmented = true
		opts.reloadEnd   = stat.time.reload.segment.fullloopend or stat.time.reload.segment.loopend or 0
	
	-- ショットガンまたは拡張マガジンがつかない
	elseif stat.ammo == 'shotgun' or stat.ammo == 'special_shotgun'
		or not stat.attachments.extended_mag_or_shotgun_bolt
		then
		reload = stat.time.reload.full
	
	-- 投下物資 (ヘビーアモ)
	elseif stat.ammo == 'special_heavy' then
		reload = 0.87 * stat.time.reload.full
	
	-- 投下物資 (その他)
	elseif aw.stringstarts(stat.ammo, 'special_') then
		reload = 0.9 * stat.time.reload.full
		
	-- ヘビー
	elseif stat.ammo == 'heavy' then
		local base = stat.time.reload.full
		if isModdedLoader then
			base = 0.75 * base
		end
		reload = {
			base,
			base,
			0.92 * base,
			0.87 * base,
		}
	
	-- その他
	else
		local base = stat.time.reload.full
		if isModdedLoader then
			base = 0.75 * base
		end
		reload = {
			base,
			base,
			0.95 * base,
			0.9  * base,
		}
	end
	
	return TTKCalculator.new(firerate, magazine, reload, opts)
end

function TTKCalculator._test(name, count, opts)
	local stat = mw.loadData('Module:Stat/Weapon')[name]
	local mode = stat.mode.auto and TTKCalculator.AUTO or TTKCalculator.SINGLE
	local ttkc = TTKCalculator.newFromStat(stat, mode, opts)
	return ttkc:get(count)
end

return TTKCalculator