🌟 | 現在、 鉄壁ヘッドショットには対応済みです。 鉄壁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) firerate = firerate or self.firerate return (left - 1) / firerate + (left - 1) * (self.rechamberTime + self.chargeTime) end function TTKCalculator:_getDeadeyesTempoTtkCore(left) return (left - 1) / self.firerate + (left - 1) * (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:_getBurstTtkInOneMagazine(left, skipLastDelay) local ttk = 0 while left > 0 do local current = math.min(self.burstCount, 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.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:_getBurstTtkInOneMagazine(magazine) ttk = ttk + self.raiseTime + shotTTK + self.reload shot = shot - magazine end local shotTTK = self:_getBurstTtkInOneMagazine(shot, true) ttk = ttk + self.raiseTime + shotTTK -- 可変オート elseif self.firerateMaximum > 0 and self.firerateMaximumTime > 0 then local first = true local virtualMagazine = magazine - self.firerateMaximumCount if magazine > 0 then if self.startMaximum and shot > virtualMagazine then local shotTTK = self:_getTtk(virtualMagazine, self.firerateMaximum) ttk = ttk + self.raiseTime + shotTTK + self.reload shot = shot - virtualMagazine first = false end while shot > magazine do local shotTTK = self:_getVariableFirerateTtkInOneMagazine(magazine) ttk = ttk + self.raiseTime + 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.raiseTime + 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 + self.raiseTime + shotTTK + self.reload shot = shot - virtualMagazine first = false end while shot > magazine do local shotTTK = self:_getDeadeyesTempoTtk(magazine) ttk = ttk + self.raiseTime + 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 + self.raiseTime + 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 + self.raiseTime + shotTTK + self.reload shot = shot - magazine end end local shotTTK = getTtk(self, shot) ttk = ttk + self.raiseTime + 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, 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 -- 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 -- Turbocharger elseif opts2.useTurbocharger and stat.turbocharger then firerate = stat.turbocharger.firerate or stat.firerate opts.ammoPerShot = stat.ammo_per_shot or 1 -- 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 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 -- 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 -- Deadeye's Tempo elseif opts2.useDeadeyesTempo then opts.firerateMaximumCount = stat.deadeyes_tempo.firerate_maximum_count -- 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 -- 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 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