| 🌟 | 現在、 鉄壁HSは通常HSと同じダメージになります。LMG及びDMR、チャージライフル、ハンマーポイント弾を除き、すべてのダメージ値が一致していることを確認しています。 |
モジュール:Apex/TTKCalculator
ナビゲーションに移動
検索に移動
このモジュールについての説明文ページを モジュール: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