🌟 | 現在、 鉄壁ヘッドショットには対応済みです。 鉄壁HSは通常HSと同じダメージになります。LMG及びDMR、チャージライフル、ハンマーポイント弾を除き、すべてのダメージ値が一致していることを確認しています。 |
「モジュール:Apex/TTKCalculator」の版間の差分
ナビゲーションに移動
検索に移動
(セグメントリロードでマガジンがレベルで異なる場合の不具合を修正) |
(可変レート射撃武器の最高速開始からのTTK算出の不具合を修正) |
||
(同じ利用者による、間の38版が非表示) | |||
1行目: | 1行目: | ||
local TTKCalculator = {} | local TTKCalculator = {} | ||
local attachment = mw.loadData('Module:Stat/Attachment') | |||
local aw = require('Module:Utility/Library') | local aw = require('Module:Utility/Library') | ||
-- [prop] 発射レート | -- [prop] 発射レート | ||
58行目: | 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 | ||
70行目: | 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 | ||
92行目: | 92行目: | ||
self:_loadMagazines() | self:_loadMagazines() | ||
self:_loadReloads() | self:_loadReloads() | ||
self: | self:_loadRechamberTimes() | ||
end | end | ||
101行目: | 101行目: | ||
-- [func] TTK算出関数 | -- [func] TTK算出関数 | ||
function TTKCalculator:_getTtk(left) | function TTKCalculator:_getTtk(left, firerate, rechamber, charge) | ||
return (left - 1) / self.firerate + (left - 1) * self. | 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 | end | ||
function TTKCalculator: | function TTKCalculator:_getBurstTtk(left, skipLastDelay) | ||
local | 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 | ||
126行目: | 140行目: | ||
function TTKCalculator:_getVariableFirerateTtkInOneMagazine(left) | function TTKCalculator:_getVariableFirerateTtkInOneMagazine(left) | ||
local ttk = | local ttk = self.raiseTime | ||
-- The first | -- The first | ||
135行目: | 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 | ||
145行目: | 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 | ||
175行目: | 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 | ||
182行目: | 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 | ||
212行目: | 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, | |||
tempoChargeTime = opts.tempoChargeTime or 0, | |||
isSegmented | rechamberTimes = opts.rechamberTimes or 0, | ||
level | tempoRechamberTime = opts.tempoRechamberTime or 0, | ||
reloadEnd = opts.reloadEnd or 0, | |||
isSegmented = opts.isSegmented, | |||
isSemiauto = opts.isSemiauto, | |||
level = opts.level or 0, | |||
}, { __index = TTKCalculator }) | }, { __index = TTKCalculator }) | ||
obj:_load() | obj:_load() | ||
233行目: | 285行目: | ||
end | end | ||
function TTKCalculator.newFromStat(stat | function TTKCalculator.newFromStat(stat, opts2) | ||
opts2 = opts2 or {} | opts2 = opts2 or {} | ||
local firerate, magazine, reload | local firerate, magazine, reload | ||
local opts = { startMaximum = opts2.startMaximum or false } | local opts = { startMaximum = opts2.startMaximum or false } | ||
local isModdedLoader = stat.category == 'light_machine_gun' 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 | ||
-- 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 | ||
291行目: | 407行目: | ||
if isModdedLoader then | if isModdedLoader then | ||
if type(stat.magazine) == 'table' then | if type(stat.magazine) == 'table' then | ||
magazine = { | 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 | elseif stat.magazine == math.huge then | ||
if aw.isNumberAndGreaterThanZero(stat.overheat) then | if type(stat.overheat) == 'table' then | ||
magazine = | 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 | else | ||
magazine = math.huge -- dummy | magazine = math.huge -- dummy | ||
309行目: | 436行目: | ||
-- L-スターEMG | -- L-スターEMG | ||
elseif stat.magazine == math.huge then | elseif stat.magazine == math.huge then | ||
if aw.isNumberAndGreaterThanZero(stat.overheat) then | if type(stat.overheat) == 'table' then | ||
magazine = | 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 | else | ||
magazine = math.huge -- dummy | 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 | end | ||
321行目: | 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 | elseif not stat.attachments.extended_mag_or_shotgun_bolt then | ||
reload = stat.time.reloadempty or stat.time.reload | |||
reload = stat.time.reload | |||
-- | -- 物資投下武器 | ||
elseif aw.stringstarts(stat.ammo, 'special_') then | elseif aw.stringstarts(stat.ammo, 'special_') then | ||
reload = | -- ストックがつく | ||
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 | end | ||
-- ストックがつかない | |||
elseif not stat.attachments.stock then | |||
reload = stat.time.reloadempty or stat.time.reload | |||
-- その他 | -- その他 | ||
else | else | ||
local base = stat.time.reload | 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 | if isModdedLoader then | ||
base = 0.75 * base | base = 0.75 * base | ||
361行目: | 514行目: | ||
reload = { | reload = { | ||
base, | base, | ||
base, | muls[1] * base, | ||
muls[2] * base, | |||
muls[3] * base, | |||
} | } | ||
end | end | ||
372行目: | 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