| 🌟 | 現在、  鉄壁ヘッドショットには対応済みです。 鉄壁HSは通常HSと同じダメージになります。LMG及びDMR、チャージライフル、ハンマーポイント弾を除き、すべてのダメージ値が一致していることを確認しています。 | 
「モジュール:Apex/TTKCalculator」の版間の差分
		
		
		
		
		
		ナビゲーションに移動
		検索に移動
		
				
		
		
	
|  (スナイパーストックが付与可能な際、物資投下武器でなくともリロード時間が固定されていた問題の修正) |  (可変レート射撃武器の最高速開始からのTTK算出の不具合を修正) | ||
| (同じ利用者による、間の23版が非表示) | |||
| 4行目: | 4行目: | ||
| local aw = require('Module:Utility/Library') | local aw = require('Module:Utility/Library') | ||
| -- [prop] 発射レート | -- [prop] 発射レート | ||
| 60行目: | 54行目: | ||
| 		self.reload = tonumber(reloads) | 		self.reload = tonumber(reloads) | ||
| 	elseif self.isSegmented then | 	elseif self.isSegmented then | ||
| 		self.reload = (self.magazine - 1) * self.reloads + self.reloadEnd | 		if self.level > 0 then | ||
| 			self.reload = ((self.magazine - 1) * self.reloads + self.reloadEnd) * attachment.sniper_stock.reload_segment[self.level] | |||
| 		else | |||
| 			self.reload = (self.magazine - 1) * self.reloads + self.reloadEnd | |||
| 		end | |||
| 	else | 	else | ||
| 		self.reload = reloads | 		self.reload = reloads | ||
| 72行目: | 70行目: | ||
| -- [prop] リチャンバー時間 | -- [prop] リチャンバー時間 | ||
| function TTKCalculator: | function TTKCalculator:_loadRechamberTimes() | ||
| 	local  | 	local rechamberTimes = self.rechamberTimes | ||
| 	local type  | 	local type           = type(rechamberTimes) | ||
| 	if type == 'table' then | 	if type == 'table' then | ||
| 		self. | 		self.rechamberTime = rechamberTimes[1 + self.level] | ||
| 	elseif type == 'string' then | 	elseif type == 'string' then | ||
| 		self. | 		self.rechamberTime = tonumber(rechamberTimes) | ||
| 	else | 	else | ||
| 		self. | 		self.rechamberTime = rechamberTimes | ||
| 	end | 	end | ||
| end | end | ||
| function TTKCalculator: | function TTKCalculator:setRechamberTimes(rechamberTimes) | ||
| 	self. | 	self.rechamberTimes = rechamberTimes or 0 | ||
| 	self: | 	self:_loadRechamberTimes() | ||
| end | end | ||
| 94行目: | 92行目: | ||
| 	self:_loadMagazines() | 	self:_loadMagazines() | ||
| 	self:_loadReloads() | 	self:_loadReloads() | ||
| 	self: | 	self:_loadRechamberTimes() | ||
| end | end | ||
| 103行目: | 101行目: | ||
| -- [func] TTK算出関数 | -- [func] TTK算出関数 | ||
| function TTKCalculator:_getTtk(left) | function TTKCalculator:_getTtk(left, firerate, rechamber, charge) | ||
| 	return (left - 1) /  | 	firerate  = firerate  or self.firerate | ||
| 	rechamber = rechamber or self.rechamberTime | |||
| 	charge    = charge    or self.chargeTime | |||
| 	if self.isSemiauto then | |||
| 		return self.raiseTime + (left - 1) / firerate + (left - 1) * (self.raiseTime + rechamber + charge) | |||
| 	else | |||
| 		return self.raiseTime + (left - 1) / firerate + (left - 1) * (rechamber + charge) | |||
| 	end | |||
| end | end | ||
| function TTKCalculator: | function TTKCalculator:_getDeadeyesTempoTtkCore(left) | ||
| 	local  | 	return self:_getTtk(left, nil, self.tempoRechamberTime, self.tempoChargeTime) | ||
| end | |||
| 		local  | |||
| function TTKCalculator:_getDeadeyesTempoTtk(left) | |||
| 	local targetCount = 1 + self.firerateMaximumCount | |||
| 	if left <= targetCount then | |||
| 		return self:_getTtk(left) | |||
| 	else | |||
| 		local normalTTK = self:_getTtk(targetCount) | |||
| 		local tempoTTK  = self:_getDeadeyesTempoTtkCore(left - self.firerateMaximumCount) | |||
| 		return normalTTK + tempoTTK | |||
| 	end | |||
| end | |||
| function TTKCalculator:_getBurstTtk(left, skipLastDelay) | |||
| 	local groupCount = math.floor(left / self.burstCount) | |||
| 	local ttk = groupCount * (self.raiseTime + (self.burstCount - 1) / self.firerate + self.burstDelay) | |||
| 	left = left - groupCount * self.burstCount | |||
| 	if left > 1 then | |||
| 		ttk = ttk + (left - 1) / self.firerate | |||
| 	elseif left == 0 then | |||
| 		ttk = ttk - self.burstDelay | |||
| 	end | 	end | ||
| 	return ttk | 	return ttk | ||
| 128行目: | 140行目: | ||
| function TTKCalculator:_getVariableFirerateTtkInOneMagazine(left) | function TTKCalculator:_getVariableFirerateTtkInOneMagazine(left) | ||
| 	local ttk =  | 	local ttk = self.raiseTime | ||
| 	-- The first | 	-- The first | ||
| 137行目: | 149行目: | ||
| 		left = left - 1 | 		left = left - 1 | ||
| 		local current = self. | 		local current = self.firerate + (self.firerateMaximum - self.firerate) * math.min(1, ttk / self.firerateMaximumTime) | ||
| 		local time = 1 / current | 		local time = 1 / current | ||
| 		ttk = ttk + time + self. | 		ttk = ttk + time + self.rechamberTime + self.chargeTime | ||
| 	end | 	end | ||
| 	return ttk | 	return ttk | ||
| 147行目: | 159行目: | ||
| function TTKCalculator:get(shot) | function TTKCalculator:get(shot) | ||
| 	local ttk = 0 | 	local ttk = 0 | ||
| 	local magazine = math.floor(self.magazine / self. | 	local magazine = math.floor(self.magazine / self.ammoPerShot) | ||
| 	-- バースト | 	-- バースト | ||
| 	if self. | 	if self.burstCount > 1 and self.burstCount <= magazine then | ||
| 		while shot > magazine do | 		while shot > magazine do | ||
| 			local shotTTK = self: | 			local shotTTK = self:_getBurstTtk(magazine) | ||
| 			ttk = ttk  | 			ttk = ttk + shotTTK + self.reload | ||
| 			shot = shot - magazine | 			shot = shot - magazine | ||
| 		end | 		end | ||
| 		local shotTTK = self: | 		local shotTTK = self:_getBurstTtk(shot, true) | ||
| 		ttk = ttk  | 		ttk = ttk + shotTTK | ||
| 	-- 可変オート | 	-- 可変オート | ||
| 	elseif self. | 	elseif self.firerateMaximum > 0 and self.firerateMaximumTime > 0 then | ||
| 		local first = true | 		local first = true | ||
| 		if magazine > 0 then | 		if magazine > 0 then | ||
| 			local virtualMagazine = magazine - self.firerateMaximumCount | |||
| 			if self.startMaximum and shot > virtualMagazine then | 			if self.startMaximum and shot > virtualMagazine then | ||
| 				local shotTTK = self:_getTtk(virtualMagazine) | 				local shotTTK = self:_getTtk(virtualMagazine, self.firerateMaximum) | ||
| 				ttk = ttk  | 				ttk = ttk + shotTTK + self.reload | ||
| 				shot = shot - virtualMagazine | 				shot = shot - virtualMagazine | ||
| 				first = false | 				first = false | ||
| 177行目: | 186行目: | ||
| 			while shot > magazine do | 			while shot > magazine do | ||
| 				local shotTTK = self:_getVariableFirerateTtkInOneMagazine(magazine) | 				local shotTTK = self:_getVariableFirerateTtkInOneMagazine(magazine) | ||
| 				ttk = ttk  | 				ttk = ttk + shotTTK + self.reload | ||
| 				shot = shot - magazine | 				shot = shot - magazine | ||
| 			end | 			end | ||
| 184行目: | 193行目: | ||
| 		local shotTTK | 		local shotTTK | ||
| 		if self.startMaximum and first then | 		if self.startMaximum and first then | ||
| 			shotTTK = self:_getTtk(shot) | 			shotTTK = self:_getTtk(shot, self.firerateMaximum) | ||
| 		else | 		else | ||
| 			shotTTK = self:_getVariableFirerateTtkInOneMagazine(shot) | 			shotTTK = self:_getVariableFirerateTtkInOneMagazine(shot) | ||
| 		end | 		end | ||
| 		ttk = ttk + self. | 		ttk = ttk + shotTTK | ||
| 	-- 最高速スタート w/デッドアイズテンポ | |||
| 	elseif self.startMaximum and self.tempoChargeTime > 0 or self.tempoRechamberTime > 0 then | |||
| 		local first = true | |||
| 		local virtualMagazine = magazine - self.firerateMaximumCount | |||
| 		if magazine > 0 then | |||
| 			if self.startMaximum and shot > virtualMagazine then | |||
| 				local shotTTK = self:_getDeadeyesTempoTtkCore(virtualMagazine) | |||
| 				ttk = ttk + shotTTK + self.reload | |||
| 				shot = shot - virtualMagazine | |||
| 				first = false | |||
| 			end | |||
| 			while shot > magazine do | |||
| 				local shotTTK = self:_getDeadeyesTempoTtk(magazine) | |||
| 				ttk = ttk + shotTTK + self.reload | |||
| 				shot = shot - magazine | |||
| 			end | |||
| 		end | |||
| 		local shotTTK | |||
| 		if self.startMaximum and first then | |||
| 			shotTTK = self:_getDeadeyesTempoTtkCore(shot) | |||
| 		else | |||
| 			shotTTK = self:_getDeadeyesTempoTtk(shot) | |||
| 		end | |||
| 		ttk = ttk + shotTTK | |||
| 	-- 非バースト (オート・単発) | 	-- 非バースト (オート・単発) | ||
| 	else | 	else | ||
| 		local getTtk | |||
| 		-- デッドアイズテンポ | |||
| 		if self.tempoChargeTime > 0 or self.tempoRechamberTime > 0 then | |||
| 			getTtk = TTKCalculator._getDeadeyesTempoTtk | |||
| 		-- 通常 | |||
| 		else | |||
| 			getTtk = TTKCalculator._getTtk | |||
| 		end | |||
| 		if magazine > 0 then | 		if magazine > 0 then | ||
| 			while shot > magazine do | 			while shot > magazine do | ||
| 				local shotTTK = self | 				local shotTTK = getTtk(self, magazine) | ||
| 				ttk = ttk  | 				ttk = ttk + shotTTK + self.reload | ||
| 				shot = shot - magazine | 				shot = shot - magazine | ||
| 			end | 			end | ||
| 		end | 		end | ||
| 		local shotTTK = self | 		local shotTTK = getTtk(self, shot) | ||
| 		ttk = ttk  | 		ttk = ttk + shotTTK | ||
| 	end | 	end | ||
| 	return ttk | 	return ttk | ||
| 214行目: | 260行目: | ||
| function TTKCalculator.new(firerates, magazines, reloads, opts) | function TTKCalculator.new(firerates, magazines, reloads, opts) | ||
| 	local obj = setmetatable({ | 	local obj = setmetatable({ | ||
| 		firerates  | 		firerates = firerates or 1, | ||
| 		magazines  | 		magazines = magazines or math.huge, | ||
| 		reloads  | 		reloads   = reloads or 0, | ||
| 		ammoPerShot          = opts.ammoPerShot          or 1, | |||
| 		burstCount           = opts.burstCount           or 1, | |||
| 		burstDelay           = opts.burstDelay           or 0, | |||
| 		raiseTime            = opts.raiseTime            or 0, | |||
| 		startMaximum         = opts.startMaximum ~= false, | |||
| 		firerateMaximum      = opts.firerateMaximum      or 0, | |||
| 		firerateMaximumTime  = opts.firerateMaximumTime  or 0, | |||
| 		firerateMaximumCount = opts.firerateMaximumCount or 0, | |||
| 		chargeTime           = opts.chargeTime           or 0, | |||
| 		reloadEnd  | 		tempoChargeTime      = opts.tempoChargeTime      or 0, | ||
| 		rechamberTimes       = opts.rechamberTimes       or 0, | |||
| 		tempoRechamberTime   = opts.tempoRechamberTime   or 0, | |||
| 		level  | 		reloadEnd            = opts.reloadEnd            or 0, | ||
| 		isSegmented          = opts.isSegmented, | |||
| 		isSemiauto           = opts.isSemiauto, | |||
| 		level                = opts.level                or 0, | |||
| 	}, { __index = TTKCalculator }) | 	}, { __index = TTKCalculator }) | ||
| 	obj:_load() | 	obj:_load() | ||
| 236行目: | 285行目: | ||
| end | end | ||
| function TTKCalculator.newFromStat(stat | function TTKCalculator.newFromStat(stat, opts2) | ||
| 	opts2 = opts2 or {} | 	opts2 = opts2 or {} | ||
| 244行目: | 292行目: | ||
| 	local isModdedLoader = (stat.category == 'light_machine_gun' or stat.ammo == 'minigun') and opts2.useModdedLoader | 	local isModdedLoader = (stat.category == 'light_machine_gun' or stat.ammo == 'minigun') and opts2.useModdedLoader | ||
| 	--  | 	-- Altfire mode | ||
| 	if stat. | 	if opts2.useAltfire and stat.altfire and aw.isNumberAndGreaterThanZero(stat.altfire.firerate) then | ||
| 		firerate  | 		firerate = stat.altfire.firerate or stat.firerate | ||
| 		opts. | 		opts.ammoPerShot = stat.altfire.ammo_per_shot or stat.ammo_per_shot or 1 | ||
| 		opts. | 		opts.isSemiauto  = stat.altfire.is_semi_auto == nil and stat.is_semi_auto or stat.altfire.is_semi_auto | ||
| 		opts. | |||
| 		opts. | 	-- Revved Up mode | ||
| 	elseif opts2.useRevvedUpMode and aw.isNumberAndGreaterThanZero(stat.firerate_revvedup) then | |||
| 		firerate = stat.firerate_revvedup | |||
| 		opts.ammoPerShot = stat.ammo_per_shot or 1 | |||
| 		opts.isSemiauto  = stat.is_semi_auto | |||
| 	--  | 	-- Turbocharger | ||
| 	elseif  | 	elseif opts2.useTurbocharger and stat.turbocharger then | ||
| 		firerate = stat.firerate. | 		firerate = stat.turbocharger.firerate or stat.firerate | ||
| 		opts.ammoPerShot = stat.ammo_per_shot or 1 | |||
| 		opts.isSemiauto  = stat.is_semi_auto | |||
| 		-- Raise time | |||
| 			opts. | 		local raiseTime = stat.turbocharger.raise or stat.raise | ||
| 		if aw.isNumberAndGreaterThanZero(raiseTime) then | |||
| 			opts.raiseTime = raiseTime | |||
| 			opts. | 		end | ||
| 			opts. | |||
| 			opts. | 		-- Variable firerate | ||
| 		local firerateMaximum = stat.turbocharger.firerate_maximum or stat.firerate_maximum | |||
| 		if aw.isNumberAndGreaterThanOrEqualToX(firerateMaximum, stat.turbocharger.firerate) then | |||
| 			opts.firerateMaximum      = firerateMaximum | |||
| 			opts.firerateMaximumTime  = stat.turbocharger.firerate_maximum_duration or stat.firerate_maximum_duration | |||
| 			opts.firerateMaximumCount = stat.turbocharger.firerate_maximum_count    or stat.firerate_maximum_count | |||
| 		end | 		end | ||
| 	--  | 	-- Selectfire Receiver | ||
| 	elseif stat. | 	elseif opts.useSelectfireReceiver and stat.selectfire_receiver then | ||
| 		firerate = stat.firerate | |||
| 		opts.ammoPerShot = stat.selectfire_receiver.ammo_per_shot or stat.ammo_per_shot or 1 | |||
| 		opts.isSemiauto  = stat.selectfire_receiver.is_semi_auto == nil and stat.is_semi_auto or stat.selectfire_receiver.is_semi_auto | |||
| 		if aw.isNumberAndGreaterThanOrEqualToX(stat.selectfire_receiver.burst_count, 2) then | |||
| 			opts.burstCount = stat.selectfire_receiver.burst_count | |||
| 			opts.burstDelay = stat.selectfire_receiver.burst_delay | |||
| 			opts. | |||
| 			opts. | |||
| 		end | 		end | ||
| 	--  | 	-- Anvil Receiver | ||
| 	elseif opts2.useAnvilReceiver and stat.anvil_receiver then | |||
| 		firerate = stat.anvil_receiver.firerate or stat.firerate | |||
| 		opts.ammoPerShot = stat.anvil_receiver.ammo_per_shot or 2 | |||
| 		opts.isSemiauto  = stat.anvil_receiver.is_semi_auto == nil and stat.is_semi_auto or stat.anvil_receiver.is_semi_auto | |||
| 	-- Double Tap Trigger | |||
| 	elseif opts2.useDoubleTapTrigger and stat.double_tap_trigger then | |||
| 		firerate = stat.double_tap_trigger.firerate or stat.firerate | |||
| 		opts.ammoPerShot = stat.double_tap_trigger.ammo_per_shot or stat.ammo_per_shot or 1 | |||
| 		opts.burstCount  = stat.double_tap_trigger.burst_count | |||
| 		opts.burstDelay  = stat.double_tap_trigger.burst_delay | |||
| 		opts.isSemiauto  = stat.double_tap_trigger.is_semi_auto == nil or stat.double_tap_trigger.is_semi_auto | |||
| 	-- Deadeye's Tempo | |||
| 	elseif opts2.useDeadeyesTempo then | |||
| 		opts.firerateMaximumCount = stat.deadeyes_tempo.firerate_maximum_count | |||
| 		opts.isSemiauto  = stat.is_semi_auto | |||
| 		-- Charge time | |||
| 		if opts2.useCharged and aw.isNumberAndGreaterThanZero(stat.deadeyes_tempo.charge) then | |||
| 			opts.tempoChargeTime = stat.deadeyes_tempo.charge | |||
| 		elseif aw.isNumberAndGreaterThanZero(stat.deadeyes_tempo.charge_minimum) then | |||
| 			opts.tempoChargeTime = stat.deadeyes_tempo.charge_minimum | |||
| 		end | |||
| 		-- Rechamber time | |||
| 		if aw.isNumberAndGreaterThanZero(stat.deadeyes_tempo.rechamber) then | |||
| 			opts.tempoRechamberTime = stat.deadeyes_tempo.rechamber | |||
| 		end | |||
| 	else | 	else | ||
| 		firerate  | 		firerate = stat.firerate | ||
| 		opts. | 		opts.ammoPerShot = stat.ammo_per_shot or 1 | ||
| 		opts. | 		opts.isSemiauto  = stat.is_semi_auto | ||
| 		opts. | |||
| 		-- Burst mode | |||
| 		if aw.isNumberAndGreaterThanOrEqualToX(stat.burst_count, 2) then | |||
| 			opts.burstCount = stat.burst_count | |||
| 			opts.burstDelay = stat.burst_delay | |||
| 		end | |||
| 		-- Raise time | |||
| 		if aw.isNumberAndGreaterThanZero(stat.raise) then | |||
| 			opts.raiseTime = stat.raise | |||
| 		elseif aw.isNumberAndGreaterThanZero(stat.sustained_discharge_duration) then | |||
| 			opts.raiseTime = stat.sustained_discharge_duration | |||
| 		end | |||
| 		-- Rechamber time | |||
| 		if type(stat.rechamber) == 'table' or aw.isNumberAndGreaterThanZero(stat.rechamber) then | |||
| 			opts.rechamberTimes = stat.rechamber | |||
| 		end | |||
| 		-- Variable firerate | |||
| 		if aw.isNumberAndGreaterThanOrEqualToX(stat.firerate_maximum, firerate) then | |||
| 			opts.firerateMaximum      = stat.firerate_maximum | |||
| 			opts.firerateMaximumTime  = stat.firerate_maximum_duration | |||
| 			opts.firerateMaximumCount = stat.firerate_maximum_count | |||
| 		end | |||
| 		-- Charge | |||
| 		if opts2.useCharged and aw.isNumberAndGreaterThanZero(stat.charge) then | |||
| 			opts.chargeTime = stat.charge | |||
| 		elseif aw.isNumberAndGreaterThanZero(stat.charge_minimum) then | |||
| 			opts.chargeTime = stat.charge_minimum | |||
| 		end | |||
| 	end | 	end | ||
| 362行目: | 468行目: | ||
| 	-- セグメントリロード | 	-- セグメントリロード | ||
| 	if stat.time. | 	if aw.isNumberAndGreaterThanZero(stat.time.reload_segment_loop) and aw.isNumberAndGreaterThanZero(stat.time.reload_segment_end) then | ||
| 		reload = stat.time. | 		reload = stat.time.reload_segment_loop or 0 | ||
| 		opts.isSegmented = true | 		opts.isSegmented = true | ||
| 		opts.reloadEnd   = stat.time. | 		opts.reloadEnd   = stat.time.reload_segment_end or 0 | ||
| 	-- 拡張マガジンがつかない | 	-- 拡張マガジンがつかない | ||
| 	elseif not stat.attachments.extended_mag_or_shotgun_bolt then | 	elseif not stat.attachments.extended_mag_or_shotgun_bolt then | ||
| 		reload = stat.time.reload | 		reload = stat.time.reloadempty or stat.time.reload | ||
| 	-- 物資投下武器 | 	-- 物資投下武器 | ||
| 377行目: | 483行目: | ||
| 			-- スナイパーストック | 			-- スナイパーストック | ||
| 			if stat.category == 'sniper' or stat.category == 'marksman_weapon' then | 			if stat.category == 'sniper' or stat.category == 'marksman_weapon' then | ||
| 				reload = stat.time.reload | 				reload = (stat.time.reloadempty or stat.time.reload) * attachment.sniper_stock.reload[3] | ||
| 			-- 標準ストック | 			-- 標準ストック | ||
| 			else | 			else | ||
| 				reload = stat.time.reload | 				reload = (stat.time.reloadempty or stat.time.reload) * attachment.standard_stock.reload[3] | ||
| 			end | 			end | ||
| 		-- ストックがつかない | 		-- ストックがつかない | ||
| 		else | 		else | ||
| 			reload = stat.time.reload | 			reload = stat.time.reloadempty or stat.time.reload | ||
| 		end | 		end | ||
| 	-- ストックがつかない | 	-- ストックがつかない | ||
| 	elseif stat.attachments.stock then | 	elseif not stat.attachments.stock then | ||
| 		reload = stat.time.reload | 		reload = stat.time.reloadempty or stat.time.reload | ||
| 	-- その他 | 	-- その他 | ||
| 402行目: | 508行目: | ||
| 		end | 		end | ||
| 		local base = stat.time.reload | 		local base = stat.time.reloadempty or stat.time.reload | ||
| 		if isModdedLoader then | 		if isModdedLoader then | ||
| 			base = 0.75 * base | 			base = 0.75 * base | ||
| 419行目: | 525行目: | ||
| function TTKCalculator._test(name, count, opts) | function TTKCalculator._test(name, count, opts) | ||
| 	local stat = mw.loadData('Module:Stat/Weapon')[name] | 	local stat = mw.loadData('Module:Stat/Weapon')[name] | ||
| 	local ttkc = TTKCalculator.newFromStat(stat, opts) | |||
| 	local ttkc = TTKCalculator.newFromStat(stat | |||
| 	return ttkc:get(count) | 	return ttkc:get(count) | ||
| end | end | ||
| return TTKCalculator | return TTKCalculator | ||
2022年4月24日 (日) 15:01時点における最新版
このモジュールについての説明文ページを モジュール:Apex/TTKCalculator/doc に作成できます
local TTKCalculator = {}
local attachment = mw.loadData('Module:Stat/Attachment')
local aw = require('Module:Utility/Library')
-- [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
		if self.level > 0 then
			self.reload = ((self.magazine - 1) * self.reloads + self.reloadEnd) * attachment.sniper_stock.reload_segment[self.level]
		else
			self.reload = (self.magazine - 1) * self.reloads + self.reloadEnd
		end
	else
		self.reload = reloads
	end
end
function TTKCalculator:setReloads(reloads)
	self.reloads = reloads or 0
	self:_loadReloads()
end
-- [prop] リチャンバー時間
function TTKCalculator:_loadRechamberTimes()
	local rechamberTimes = self.rechamberTimes
	local type           = type(rechamberTimes)
	if type == 'table' then
		self.rechamberTime = rechamberTimes[1 + self.level]
	elseif type == 'string' then
		self.rechamberTime = tonumber(rechamberTimes)
	else
		self.rechamberTime = rechamberTimes
	end
end
function TTKCalculator:setRechamberTimes(rechamberTimes)
	self.rechamberTimes = rechamberTimes or 0
	self:_loadRechamberTimes()
end
-- [prop] レベル
function TTKCalculator:_load()
	self:_loadFirerates()
	self:_loadMagazines()
	self:_loadReloads()
	self:_loadRechamberTimes()
end
function TTKCalculator:setLevel(level)
	self.level = level
	self:_load()
end
-- [func] TTK算出関数
function TTKCalculator:_getTtk(left, firerate, rechamber, charge)
	firerate  = firerate  or self.firerate
	rechamber = rechamber or self.rechamberTime
	charge    = charge    or self.chargeTime
	if self.isSemiauto then
		return self.raiseTime + (left - 1) / firerate + (left - 1) * (self.raiseTime + rechamber + charge)
	else
		return self.raiseTime + (left - 1) / firerate + (left - 1) * (rechamber + charge)
	end
end
function TTKCalculator:_getDeadeyesTempoTtkCore(left)
	return self:_getTtk(left, nil, self.tempoRechamberTime, self.tempoChargeTime)
end
function TTKCalculator:_getDeadeyesTempoTtk(left)
	local targetCount = 1 + self.firerateMaximumCount
	if left <= targetCount then
		return self:_getTtk(left)
	else
		local normalTTK = self:_getTtk(targetCount)
		local tempoTTK  = self:_getDeadeyesTempoTtkCore(left - self.firerateMaximumCount)
		return normalTTK + tempoTTK
	end
end
function TTKCalculator:_getBurstTtk(left, skipLastDelay)
	local groupCount = math.floor(left / self.burstCount)
	local ttk = groupCount * (self.raiseTime + (self.burstCount - 1) / self.firerate + self.burstDelay)
	left = left - groupCount * self.burstCount
	if left > 1 then
		ttk = ttk + (left - 1) / self.firerate
	elseif left == 0 then
		ttk = ttk - self.burstDelay
	end
	return ttk
end
function TTKCalculator:_getVariableFirerateTtkInOneMagazine(left)
	local ttk = self.raiseTime
	
	-- The first
	left = left - 1
	
	-- To the last
	while left > 0 do
		left = left - 1
		
		local current = self.firerate + (self.firerateMaximum - self.firerate) * math.min(1, ttk / self.firerateMaximumTime)
		local time = 1 / current
		ttk = ttk + time + self.rechamberTime + self.chargeTime
	end
	return ttk
end
function TTKCalculator:get(shot)
	local ttk = 0
	local magazine = math.floor(self.magazine / self.ammoPerShot)
	-- バースト
	if self.burstCount > 1 and self.burstCount <= magazine then
		while shot > magazine do
			local shotTTK = self:_getBurstTtk(magazine)
			ttk = ttk + shotTTK + self.reload
			shot = shot - magazine
		end
		
		local shotTTK = self:_getBurstTtk(shot, true)
		ttk = ttk + shotTTK
	
	-- 可変オート
	elseif self.firerateMaximum > 0 and self.firerateMaximumTime > 0 then
		local first = true
		if magazine > 0 then
			local virtualMagazine = magazine - self.firerateMaximumCount
			if self.startMaximum and shot > virtualMagazine then
				local shotTTK = self:_getTtk(virtualMagazine, self.firerateMaximum)
				ttk = ttk + shotTTK + self.reload
				shot = shot - virtualMagazine
				first = false
			end
			
			while shot > magazine do
				local shotTTK = self:_getVariableFirerateTtkInOneMagazine(magazine)
				ttk = ttk + shotTTK + self.reload
				shot = shot - magazine
			end
		end
		
		local shotTTK
		if self.startMaximum and first then
			shotTTK = self:_getTtk(shot, self.firerateMaximum)
		else
			shotTTK = self:_getVariableFirerateTtkInOneMagazine(shot)
		end
		ttk = ttk + shotTTK
	
	-- 最高速スタート w/デッドアイズテンポ
	elseif self.startMaximum and self.tempoChargeTime > 0 or self.tempoRechamberTime > 0 then
		local first = true
		local virtualMagazine = magazine - self.firerateMaximumCount
		if magazine > 0 then
			if self.startMaximum and shot > virtualMagazine then
				local shotTTK = self:_getDeadeyesTempoTtkCore(virtualMagazine)
				ttk = ttk + shotTTK + self.reload
				shot = shot - virtualMagazine
				first = false
			end
			
			while shot > magazine do
				local shotTTK = self:_getDeadeyesTempoTtk(magazine)
				ttk = ttk + shotTTK + self.reload
				shot = shot - magazine
			end
		end
		
		local shotTTK
		if self.startMaximum and first then
			shotTTK = self:_getDeadeyesTempoTtkCore(shot)
		else
			shotTTK = self:_getDeadeyesTempoTtk(shot)
		end
		ttk = ttk + shotTTK
	
	-- 非バースト (オート・単発)
	else
		local getTtk
		-- デッドアイズテンポ
		if self.tempoChargeTime > 0 or self.tempoRechamberTime > 0 then
			getTtk = TTKCalculator._getDeadeyesTempoTtk
		
		-- 通常
		else
			getTtk = TTKCalculator._getTtk
		end
		
		if magazine > 0 then
			while shot > magazine do
				local shotTTK = getTtk(self, magazine)
				ttk = ttk + shotTTK + self.reload
				shot = shot - magazine
			end
		end
		
		local shotTTK = getTtk(self, shot)
		ttk = ttk + 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,
		
		ammoPerShot          = opts.ammoPerShot          or 1,
		burstCount           = opts.burstCount           or 1,
		burstDelay           = opts.burstDelay           or 0,
		raiseTime            = opts.raiseTime            or 0,
		startMaximum         = opts.startMaximum ~= false,
		firerateMaximum      = opts.firerateMaximum      or 0,
		firerateMaximumTime  = opts.firerateMaximumTime  or 0,
		firerateMaximumCount = opts.firerateMaximumCount or 0,
		chargeTime           = opts.chargeTime           or 0,
		tempoChargeTime      = opts.tempoChargeTime      or 0,
		rechamberTimes       = opts.rechamberTimes       or 0,
		tempoRechamberTime   = opts.tempoRechamberTime   or 0,
		reloadEnd            = opts.reloadEnd            or 0,
		isSegmented          = opts.isSegmented,
		isSemiauto           = opts.isSemiauto,
		level                = opts.level                or 0,
	}, { __index = TTKCalculator })
	obj:_load()
	return obj
end
function TTKCalculator.newFromStat(stat, opts2)
	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
	
	-- Altfire mode
	if opts2.useAltfire and stat.altfire and aw.isNumberAndGreaterThanZero(stat.altfire.firerate) then
		firerate = stat.altfire.firerate or stat.firerate
		opts.ammoPerShot = stat.altfire.ammo_per_shot or stat.ammo_per_shot or 1
		opts.isSemiauto  = stat.altfire.is_semi_auto == nil and stat.is_semi_auto or stat.altfire.is_semi_auto
	
	-- Revved Up mode
	elseif opts2.useRevvedUpMode and aw.isNumberAndGreaterThanZero(stat.firerate_revvedup) then
		firerate = stat.firerate_revvedup
		opts.ammoPerShot = stat.ammo_per_shot or 1
		opts.isSemiauto  = stat.is_semi_auto
	
	-- Turbocharger
	elseif opts2.useTurbocharger and stat.turbocharger then
		firerate = stat.turbocharger.firerate or stat.firerate
		opts.ammoPerShot = stat.ammo_per_shot or 1
		opts.isSemiauto  = stat.is_semi_auto
		
		-- Raise time
		local raiseTime = stat.turbocharger.raise or stat.raise
		if aw.isNumberAndGreaterThanZero(raiseTime) then
			opts.raiseTime = raiseTime
		end
		
		-- Variable firerate
		local firerateMaximum = stat.turbocharger.firerate_maximum or stat.firerate_maximum
		if aw.isNumberAndGreaterThanOrEqualToX(firerateMaximum, stat.turbocharger.firerate) then
			opts.firerateMaximum      = firerateMaximum
			opts.firerateMaximumTime  = stat.turbocharger.firerate_maximum_duration or stat.firerate_maximum_duration
			opts.firerateMaximumCount = stat.turbocharger.firerate_maximum_count    or stat.firerate_maximum_count
		end
	
	-- Selectfire Receiver
	elseif opts.useSelectfireReceiver and stat.selectfire_receiver then
		firerate = stat.firerate
		opts.ammoPerShot = stat.selectfire_receiver.ammo_per_shot or stat.ammo_per_shot or 1
		opts.isSemiauto  = stat.selectfire_receiver.is_semi_auto == nil and stat.is_semi_auto or stat.selectfire_receiver.is_semi_auto
		
		if aw.isNumberAndGreaterThanOrEqualToX(stat.selectfire_receiver.burst_count, 2) then
			opts.burstCount = stat.selectfire_receiver.burst_count
			opts.burstDelay = stat.selectfire_receiver.burst_delay
		end
	
	-- Anvil Receiver
	elseif opts2.useAnvilReceiver and stat.anvil_receiver then
		firerate = stat.anvil_receiver.firerate or stat.firerate
		opts.ammoPerShot = stat.anvil_receiver.ammo_per_shot or 2
		opts.isSemiauto  = stat.anvil_receiver.is_semi_auto == nil and stat.is_semi_auto or stat.anvil_receiver.is_semi_auto
	
	-- Double Tap Trigger
	elseif opts2.useDoubleTapTrigger and stat.double_tap_trigger then
		firerate = stat.double_tap_trigger.firerate or stat.firerate
		opts.ammoPerShot = stat.double_tap_trigger.ammo_per_shot or stat.ammo_per_shot or 1
		opts.burstCount  = stat.double_tap_trigger.burst_count
		opts.burstDelay  = stat.double_tap_trigger.burst_delay
		opts.isSemiauto  = stat.double_tap_trigger.is_semi_auto == nil or stat.double_tap_trigger.is_semi_auto
		
	-- Deadeye's Tempo
	elseif opts2.useDeadeyesTempo then
		opts.firerateMaximumCount = stat.deadeyes_tempo.firerate_maximum_count
		opts.isSemiauto  = stat.is_semi_auto
		
		-- Charge time
		if opts2.useCharged and aw.isNumberAndGreaterThanZero(stat.deadeyes_tempo.charge) then
			opts.tempoChargeTime = stat.deadeyes_tempo.charge
		elseif aw.isNumberAndGreaterThanZero(stat.deadeyes_tempo.charge_minimum) then
			opts.tempoChargeTime = stat.deadeyes_tempo.charge_minimum
		end
		
		-- Rechamber time
		if aw.isNumberAndGreaterThanZero(stat.deadeyes_tempo.rechamber) then
			opts.tempoRechamberTime = stat.deadeyes_tempo.rechamber
		end
		
	else
		firerate = stat.firerate
		opts.ammoPerShot = stat.ammo_per_shot or 1
		opts.isSemiauto  = stat.is_semi_auto
		
		-- Burst mode
		if aw.isNumberAndGreaterThanOrEqualToX(stat.burst_count, 2) then
			opts.burstCount = stat.burst_count
			opts.burstDelay = stat.burst_delay
		end
		
		-- Raise time
		if aw.isNumberAndGreaterThanZero(stat.raise) then
			opts.raiseTime = stat.raise
		elseif aw.isNumberAndGreaterThanZero(stat.sustained_discharge_duration) then
			opts.raiseTime = stat.sustained_discharge_duration
		end
		
		-- Rechamber time
		if type(stat.rechamber) == 'table' or aw.isNumberAndGreaterThanZero(stat.rechamber) then
			opts.rechamberTimes = stat.rechamber
		end
		
		-- Variable firerate
		if aw.isNumberAndGreaterThanOrEqualToX(stat.firerate_maximum, firerate) then
			opts.firerateMaximum      = stat.firerate_maximum
			opts.firerateMaximumTime  = stat.firerate_maximum_duration
			opts.firerateMaximumCount = stat.firerate_maximum_count
		end
		
		-- Charge
		if opts2.useCharged and aw.isNumberAndGreaterThanZero(stat.charge) then
			opts.chargeTime = stat.charge
		elseif aw.isNumberAndGreaterThanZero(stat.charge_minimum) then
			opts.chargeTime = stat.charge_minimum
		end
	end
	
	-- 装填数
	if isModdedLoader then
		if type(stat.magazine) == 'table' then
			if aw.stringstarts(stat.ammo, 'special_') then
				magazine = aw.round(1.15 * stat.magazine[4])
			else
				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]),
				}
			end
		elseif stat.magazine == math.huge then
			if type(stat.overheat) == 'table' then
				magazine = {
					math.ceil(1.15 * stat.overheat[1] * firerate),
					math.ceil(1.15 * stat.overheat[2] * firerate),
					math.ceil(1.15 * stat.overheat[3] * firerate),
					math.ceil(1.15 * stat.overheat[4] * firerate),
				}
			elseif aw.isNumberAndGreaterThanZero(stat.overheat) then
				magazine = math.ceil(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 type(stat.overheat) == 'table' then
			magazine = {
				math.ceil(stat.overheat[1] * firerate),
				math.ceil(stat.overheat[2] * firerate),
				math.ceil(stat.overheat[3] * firerate),
				math.ceil(stat.overheat[4] * firerate),
			}
		elseif aw.isNumberAndGreaterThanZero(stat.overheat) then
			magazine = math.ceil(stat.overheat * firerate)
		else
			magazine = math.huge -- dummy
		end
	
	-- ブーステッドローダー
	elseif opts2.useBoostedLoader then
		magazine = stat.boosted_loader and stat.boosted_loader.magazine or stat.magazine
	
	-- 拡張マガジンがつく場合
	elseif type(stat.magazine) == 'table' then
		-- 物資投下武器
		if aw.stringstarts(stat.ammo, 'special_') then
			magazine = stat.magazine[4]
		else
			magazine = stat.magazine
		end
	
	-- 拡張マガジンがつかない場合
	else
		magazine = stat.magazine
	end
	
	-- セグメントリロード
	if aw.isNumberAndGreaterThanZero(stat.time.reload_segment_loop) and aw.isNumberAndGreaterThanZero(stat.time.reload_segment_end) then
		reload = stat.time.reload_segment_loop or 0
		opts.isSegmented = true
		opts.reloadEnd   = stat.time.reload_segment_end or 0
	
	-- 拡張マガジンがつかない
	elseif not stat.attachments.extended_mag_or_shotgun_bolt then
		reload = stat.time.reloadempty or stat.time.reload
	
	-- 物資投下武器
	elseif aw.stringstarts(stat.ammo, 'special_') then
		-- ストックがつく
		if stat.attachments.stock then
			-- スナイパーストック
			if stat.category == 'sniper' or stat.category == 'marksman_weapon' then
				reload = (stat.time.reloadempty or stat.time.reload) * attachment.sniper_stock.reload[3]
			
			-- 標準ストック
			else
				reload = (stat.time.reloadempty or stat.time.reload) * attachment.standard_stock.reload[3]
			end
		
		-- ストックがつかない
		else
			reload = stat.time.reloadempty or stat.time.reload
		end
	
	-- ストックがつかない
	elseif not stat.attachments.stock then
		reload = stat.time.reloadempty or stat.time.reload
	
	-- その他
	else
		local muls
		if stat.category == 'sniper' or stat.category == 'marksman_weapon' then
			muls = attachment.sniper_stock.reload
		else
			muls = attachment.standard_stock.reload
		end
		
		local base = stat.time.reloadempty or stat.time.reload
		if isModdedLoader then
			base = 0.75 * base
		end
		reload = {
			base,
			muls[1] * base,
			muls[2] * base,
			muls[3] * 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 ttkc = TTKCalculator.newFromStat(stat, opts)
	return ttkc:get(count)
end
return TTKCalculator