Escape from Funcraft: Beta 2 configs, mods, etc.

This commit is contained in:
BergaBruh
2026-07-23 16:35:06 +05:00
commit b218433c3c
27575 changed files with 24025424 additions and 0 deletions
@@ -0,0 +1,131 @@
-- 定义逻辑机,定式
local M = {}
-- 当开始换弹的时候会调用一次
function M.start_reload(api)
return true
end
-- 这是个 lua 函数,用来从枪 data 文件里获取装弹相关的动画时间点,由于 lua 内的时间是毫秒,所以要和 1000 做乘算
local function getReloadTimingFromParam(param)
local reload_feed = {param.reload_feed, param.reload_xmag_1_feed, param.reload_xmag_2_feed, param.reload_xmag_3_feed}
local reload_cooldown = {param.reload_cooldown, param.reload_xmag_1_cooldown, param.reload_xmag_2_cooldown, param.reload_xmag_3_cooldown}
local empty_feed = {param.empty_feed, param.empty_xmag_1_feed, param.empty_xmag_2_feed, param.empty_xmag_3_feed}
local empty_cooldown = {param.empty_cooldown, param.empty_xmag_1_cooldown, param.empty_xmag_2_cooldown, param.empty_xmag_3_cooldown}
for i = 1, 4 do
-- 将 param 中的时间点转换为毫秒
-- 如果有nil直接返回nil
if (reload_feed[i] == nil or reload_cooldown[i] == nil or empty_feed[i] == nil or empty_cooldown[i] == nil) then
return nil, nil, nil, nil
end
reload_feed[i] = reload_feed[i] * 1000
reload_cooldown[i] = reload_cooldown[i] * 1000
empty_feed[i] = empty_feed[i] * 1000
empty_cooldown[i] = empty_cooldown[i] * 1000
end
-- 顺序返回获取到的这 4 个数组
return reload_feed, reload_cooldown, empty_feed, empty_cooldown
end
-- 判断这个状态是否是空仓换弹过程中的其中一个阶段。包括空仓换弹的收尾阶段
local function isReloadingEmpty(stateType)
return stateType == EMPTY_RELOAD_FEEDING or stateType == EMPTY_RELOAD_FINISHING
end
-- 判断这个状态是否是战术换弹过程中的其中一个阶段。包括战术换弹的收尾阶段
local function isReloadingTactical(stateType)
return stateType == TACTICAL_RELOAD_FEEDING or stateType == TACTICAL_RELOAD_FINISHING
end
-- 判断这个状态是否是任意换弹过程中的其中一个阶段。包括任意换弹的收尾阶段
local function isReloading(stateType)
return isReloadingEmpty(stateType) or isReloadingTactical(stateType)
end
-- 判断这个状态是否是任意换弹过程中的的收尾阶段
local function isReloadFinishing(stateType)
return stateType == EMPTY_RELOAD_FINISHING or stateType == TACTICAL_RELOAD_FINISHING
end
local function finishReload(api, is_tactical)
local needAmmoCount = api:getNeededAmmoAmount();
if (api:isReloadingNeedConsumeAmmo()) then
-- 需要消耗弹药(生存或冒险)的话就消耗换弹所需的弹药并将消耗的数量装填进弹匣
api:putAmmoInMagazine(api:consumeAmmoFromPlayer(needAmmoCount))
else
-- 不需要消耗弹药(创造)的话就直接把弹匣塞满
api:putAmmoInMagazine(needAmmoCount)
end
if not is_tactical then
local i = api:removeAmmoFromMagazine(1);
if i ~= 0 then
api:setAmmoInBarrel(true)
end
end
end
function M.tick_reload(api)
-- 从枪 data 文件中获取所有需要传入逻辑机的参数,注意此时的 param 是个列表,还不能直接拿来用
local param = api:getScriptParams();
-- 调用刚才的 lua 函数,把 param 里包含的八个参数依次赋值给我们新定义的变量
local reload_feed, reload_cooldown, empty_feed, empty_cooldown = getReloadTimingFromParam(param)
-- 照例检查是否有参数缺失
if (reload_feed == nil or reload_cooldown == nil or empty_feed == nil or empty_cooldown == nil) then
return NOT_RELOADING, -1
end
-- 获取当前弹匣等级,我们假设最多 3 级
local mag_level = math.min(api:getMagExtentLevel(), 3) + 1
local countDown = -1
local stateType = NOT_RELOADING
local oldStateType = api:getReloadStateType()
-- 获取换弹时间,在玩家按下 R 的一瞬间作为零点,单位是毫秒。假设玩家在一秒前按下了 R ,那么此时这个时间就是 1000
local progressTime = api:getReloadTime()
if isReloadingEmpty(oldStateType) then
local feed_time = empty_feed[mag_level]
local finishing_time = empty_cooldown[mag_level]
if progressTime < feed_time then
stateType = EMPTY_RELOAD_FEEDING
countDown = feed_time - progressTime
elseif progressTime < finishing_time then
stateType = EMPTY_RELOAD_FINISHING
countDown = finishing_time - progressTime
else
stateType = NOT_RELOADING;
countDown = -1
end
elseif isReloadingTactical(oldStateType) then
local feed_time = reload_feed[mag_level]
local finishing_time = reload_cooldown[mag_level]
if progressTime < feed_time then
stateType = TACTICAL_RELOAD_FEEDING
countDown = feed_time - progressTime
elseif progressTime < finishing_time then
stateType = TACTICAL_RELOAD_FINISHING
countDown = finishing_time - progressTime
else
stateType = NOT_RELOADING;
countDown = -1
end
else
stateType = NOT_RELOADING;
countDown = -1
end
if oldStateType == EMPTY_RELOAD_FEEDING and oldStateType ~= stateType then
finishReload(api,false);
end
if oldStateType == TACTICAL_RELOAD_FEEDING and oldStateType ~= stateType then
finishReload(api, true);
end
return stateType, countDown
end
-- 向模组返回整个逻辑机,定式
return M
@@ -0,0 +1,220 @@
local M = {}
function M.shoot(api)
api:shootOnce(api:isShootingNeedConsumeAmmo())
if (api:getAttachment("MUZZLE") == "hamster:gas_tube") then
if (api:removeAmmoFromMagazine(1) ~= 0) then
api:setAmmoInBarrel(true);
end
return false
end
local cache = api:getCachedScriptData()
if (cache == nil) then
cache = {
shoot_mode = 0
}
end
if (api:getAimingProgress() == 0) then
cache.shoot_mode = 1
else
cache.shoot_mode = 0
end
api:cacheScriptData(cache)
end
function M.start_bolt(api)
-- Return true to start ticking, since there are nothing needed to be check
return true
end
function M.tick_bolt(api)
-- 调用缓存数据
local cache = api:getCachedScriptData()
-- 获取data中的数据
local total_bolt_time = api:getScriptParams().bolt_time * 1000
if (cache ~= nil) then
-- 如果当前是腰射状态,则赋值覆盖掉原来的拉栓时长数据
if (cache.shoot_mode ~= nil and cache.shoot_mode == 1) then
total_bolt_time = api:getScriptParams().rapid_bolt_time * 1000
end
end
if (total_bolt_time == nil) then
return false
end
if (api:getBoltTime() < total_bolt_time) then
return true
else
if (api:removeAmmoFromMagazine(1) ~= 0) then
api:setAmmoInBarrel(true);
end
return false
end
end
function M.start_reload(api)
-- Initialize cache that will be used in reload ticking
local cache = {
reloaded_count = 0,
needed_count = api:getNeededAmmoAmount(),
is_tactical = api:getReloadStateType() == TACTICAL_RELOAD_FEEDING,
interrupted_time = -1,
}
if(api:hasAmmoInBarrel() and api:getAttachment("MUZZLE") ~= "hamster:gas_tube")then
cache.needed_count = cache.needed_count - 1
end
api:cacheScriptData(cache)
-- Return true to start ticking
return true
end
local function getReloadTimingFromParam(param)
-- Need to convert time from seconds to milliseconds
local intro = param.intro * 1000
local intro_empty = param.intro_empty * 1000
local loop = param.loop * 1000
local ending = param.ending * 1000
local ending_empty_feed = param.ending_empty_feed * 1000
local clip_load = param.clip_load * 1000
local clip_load_feed = param.clip_load_feed * 1000
local loop_feed = param.loop_feed * 1000
local mag_feed = param.mag_feed * 1000
local mag = param.mag * 1000
local mag_empty = param.mag_empty * 1000
-- Check if any timing is nil
if (intro == nil or intro_empty == nil or loop == nil or ending == nil or ending_empty_feed == nil or clip_load == nil or clip_load_feed == nil or loop_feed == nil or mag_feed == nil or mag == nil or mag_empty == nil) then
return nil
end
return intro, intro_empty, loop, ending, ending_empty_feed, clip_load, clip_load_feed, loop_feed, mag_feed, mag, mag_empty
end
function M.tick_reload(api)
-- 获取所有装弹所需要的数据
local param = api:getScriptParams();
local intro, intro_empty, loop, ending, ending_empty_feed, clip_load, clip_load_feed, loop_feed, mag_feed, mag, mag_empty = getReloadTimingFromParam(param)
-- 获取从开始装弹到现在的时间,单位为毫秒
local reload_time = api:getReloadTime()
-- 调用在装弹开始时初始化写入的缓存数据
local cache = api:getCachedScriptData()
local interrupted_time = cache.interrupted_time
-- Handle interrupting reload
if (api:getAttachment("MUZZLE") == "hamster:gas_tube") then
if (not cache.is_tactical) then
if (reload_time < mag_feed) then
return TACTICAL_RELOAD_FEEDING, mag_feed - reload_time
elseif (reload_time >= mag_feed and reload_time < mag_empty) then
if (cache.needed_count > 0) then
api:putAmmoInMagazine(api:isReloadingNeedConsumeAmmo()
and api:consumeAmmoFromPlayer(cache.needed_count) or cache.needed_count)
cache.needed_count = 0
end
return TACTICAL_RELOAD_FINISHING, mag_empty - reload_time
else
return NOT_RELOADING, -1
end
else
if (reload_time < mag_feed) then
return EMPTY_RELOAD_FEEDING, mag_feed - reload_time
elseif (reload_time >= mag_feed and reload_time < mag) then
if (cache.needed_count > 0) then
api:putAmmoInMagazine(api:isReloadingNeedConsumeAmmo()
and api:consumeAmmoFromPlayer(cache.needed_count) or cache.needed_count)
cache.needed_count = 0
end
return EMPTY_RELOAD_FINISHING, mag - reload_time
else
return NOT_RELOADING, -1
end
end
end
if (cache.interrupted_time ~= -1) then
local int_time = reload_time - cache.interrupted_time
if (not cache.is_tactical) then
if (int_time > ending_empty_feed) then
if(not api:hasAmmoInBarrel()) then
if (api:removeAmmoFromMagazine(1) ~= 0) then
api:setAmmoInBarrel(true);
end
end
end
if(int_time >= ending) then
return NOT_RELOADING, -1
else
return EMPTY_RELOAD_FINISHING, ending - int_time
end
else
if(int_time >= ending) then
return NOT_RELOADING, -1
else
return TACTICAL_RELOAD_FINISHING, ending - int_time
end
end
else
if (not api:hasAmmoToConsume()) then
interrupted_time = api:getReloadTime()
end
end
local reloaded_count = cache.reloaded_count;
-- Load the ammo into the magazine one by one
if (reloaded_count >= 0) then
local base_time = reloaded_count * loop
if(reloaded_count >= 5) then
base_time = (math.floor(reloaded_count / 5) * clip_load) + (reloaded_count % 5) * loop
end
if (cache.needed_count - reloaded_count >= 5) then
base_time = base_time + clip_load_feed
else
base_time = base_time + loop_feed
end
if (not cache.is_tactical) then
base_time = base_time + intro_empty
else
base_time = base_time + intro
end
while (base_time < reload_time) do
if (reloaded_count > cache.needed_count) then
break
end
if (cache.needed_count - reloaded_count >= 5) then
reloaded_count = reloaded_count + 5
base_time = base_time + clip_load
api:putAmmoInMagazine(api:isReloadingNeedConsumeAmmo() and api:consumeAmmoFromPlayer(5) or 5)
else
reloaded_count = reloaded_count + 1
base_time = base_time + loop
api:consumeAmmoFromPlayer(1)
api:putAmmoInMagazine(1)
end
end
end
-- Write back cache
if (reloaded_count >= cache.needed_count) then
interrupted_time = api:getReloadTime() - loop_feed + loop
end
cache.interrupted_time = interrupted_time
cache.reloaded_count = reloaded_count
api:cacheScriptData(cache)
-- return reloadstate
local total_time = cache.needed_count * loop
if (not cache.is_tactical) then
total_time = total_time + intro
return EMPTY_RELOAD_FEEDING, total_time - reload_time
else
total_time = total_time + intro
return TACTICAL_RELOAD_FEEDING, total_time - reload_time
end
end
function M.interrupt_reload(api)
local cache = api:getCachedScriptData()
if (cache ~= nil and cache.interrupted_time == -1) then
cache.interrupted_time = api:getReloadTime()
end
end
return M
@@ -0,0 +1,86 @@
-- Striker-12 Gun Logic (now using 'start' param)
local M = {}
function M.shoot(api)
api:shootOnce(api:isShootingNeedConsumeAmmo())
end
function M.start_reload(api)
local cache = {
reloaded_count = 0,
needed_count = api:getNeededAmmoAmount(),
interrupted_time = -1,
}
api:cacheScriptData(cache)
return true
end
local function getReloadTimingFromParam(param)
local start = param.start * 1000
local loop = param.loop * 1000
local ending = param.ending * 1000
local loop_feed = param.loop_feed * 1000
return start, loop, ending, loop_feed
end
function M.tick_reload(api)
local param = api:getScriptParams()
local start, loop, ending, loop_feed = getReloadTimingFromParam(param)
if (start == nil) then
return NOT_RELOADING, -1
end
local reload_time = api:getReloadTime()
local cache = api:getCachedScriptData()
local interrupted_time = cache.interrupted_time
if (interrupted_time ~= -1) then
local int_time = reload_time - interrupted_time
if (int_time >= ending) then
return NOT_RELOADING, -1
else
return TACTICAL_RELOAD_FINISHING, ending - int_time
end
else
if (not api:hasAmmoToConsume()) then
interrupted_time = api:getReloadTime()
end
end
local reloaded_count = cache.reloaded_count
if (reloaded_count > 0 or reload_time > start) then
local base_time = (reloaded_count) * loop + loop_feed
base_time = base_time + start
while (base_time < reload_time) do
if (reloaded_count >= cache.needed_count) then
break
end
reloaded_count = reloaded_count + 1
base_time = base_time + loop
api:consumeAmmoFromPlayer(1)
api:putAmmoInMagazine(1)
end
end
if (reloaded_count >= cache.needed_count) then
interrupted_time = api:getReloadTime() - loop_feed + loop
end
cache.interrupted_time = interrupted_time
cache.reloaded_count = reloaded_count
api:cacheScriptData(cache)
local total_time = cache.needed_count * loop + start
return TACTICAL_RELOAD_FEEDING, total_time - reload_time
end
function M.interrupt_reload(api)
local cache = api:getCachedScriptData()
if (cache ~= nil and cache.interrupted_time == -1) then
cache.interrupted_time = api:getReloadTime()
end
end
return M
@@ -0,0 +1,152 @@
local M = {}
function M.shoot(api)
api:shootOnce(api:isShootingNeedConsumeAmmo())
end
function M.start_bolt(api)
-- Return true to start ticking, since there are nothing needed to be check
return true
end
function M.tick_bolt(api)
-- Get total bolt time from script parameter in gun data
local params = api:getScriptParams()
local total_bolt_time = params.bolt_time * 1000
local bolt_feed_time = params.bolt_feed_time * 1000
if (total_bolt_time == nil or bolt_feed_time == nil) then
return false
end
local bolt_time = api:getBoltTime()
if (bolt_time < bolt_feed_time) then
-- Bolt time less than total means we need to keep ticking, return true
return true
else
-- Bolt time greater than total means that the bullet
-- needs to be put from the magazine into the barrel, and then return false to end ticking.
if (not api:hasAmmoInBarrel()) then
if (api:removeAmmoFromMagazine(1) ~= 0) then
api:setAmmoInBarrel(true);
end
end
return bolt_time < total_bolt_time
end
end
function M.start_reload(api)
-- Initialize cache that will be used in reload ticking
local cache = {
reloaded_count = 0,
needed_count = api:getNeededAmmoAmount(),
is_tactical = api:getReloadStateType() == TACTICAL_RELOAD_FEEDING,
interrupted_time = -1,
}
api:cacheScriptData(cache)
-- Return true to start ticking
return true
end
local function getReloadTimingFromParam(param)
-- Need to convert time from seconds to milliseconds
local intro_empty = param.intro_empty * 1000
local intro = param.intro * 1000
local loop = param.loop * 1000
local ending = param.ending * 1000
local intro_empty_feed = param.intro_empty_feed * 1000
local loop_feed = param.loop_feed * 1000
-- Check if any timing is nil
if (intro_empty == nil or intro == nil or loop == nil or ending == nil or intro_empty_feed == nil or loop_feed == nil) then
return nil
end
return intro_empty, intro, loop, ending, intro_empty_feed, loop_feed
end
function M.tick_reload(api)
-- Get all timings from script parameter in gun data
local param = api:getScriptParams();
local intro_empty, intro, loop, ending, intro_empty_feed, loop_feed = getReloadTimingFromParam(param)
if (intro_empty == nil) then
return NOT_RELOADING, -1
end
-- Get reload time (The time from the start of reloading to the current time) from api
local reload_time = api:getReloadTime()
-- Get cache from api, it will be used to count loaded ammo, mark reload interruptions, etc.
local cache = api:getCachedScriptData()
local interrupted_time = cache.interrupted_time
-- Handle interrupting reload
if (interrupted_time ~= -1) then
local int_time = reload_time - interrupted_time
if (int_time >= ending) then
return NOT_RELOADING, -1
else
if (cache.is_tactical) then
return TACTICAL_RELOAD_FINISHING, ending - int_time
else
return EMPTY_RELOAD_FINISHING, ending - int_time
end
end
else
-- if there is no ammo to consume, interrupt reloading
if (not api:hasAmmoToConsume()) then
interrupted_time = api:getReloadTime()
end
end
-- Put an ammo into the barrel first
local reloaded_count = cache.reloaded_count;
if (reloaded_count == 0) then
if (not cache.is_tactical) then
if (reload_time > intro_empty_feed) then
api:consumeAmmoFromPlayer(1)
api:setAmmoInBarrel(true)
reloaded_count = reloaded_count + 1
end
else
reloaded_count = reloaded_count + 1
end
end
-- Load the ammo into the magazine one by one
if (reloaded_count > 0) then
local base_time = (reloaded_count -1) * loop + loop_feed
if (not cache.is_tactical) then
base_time = base_time + intro_empty
else
base_time = base_time + intro
end
while (base_time < reload_time) do
if (reloaded_count > cache.needed_count) then
break
end
reloaded_count = reloaded_count + 1
base_time = base_time + loop
api:consumeAmmoFromPlayer(1)
api:putAmmoInMagazine(1)
end
end
-- Write back cache
if (reloaded_count > cache.needed_count) then
interrupted_time = api:getReloadTime() - loop_feed + loop
end
cache.interrupted_time = interrupted_time
cache.reloaded_count = reloaded_count
api:cacheScriptData(cache)
-- return reloadstate
local total_time = cache.needed_count * loop
if (not cache.is_tactical) then
total_time = total_time + intro_empty
return EMPTY_RELOAD_FEEDING, total_time - reload_time
else
total_time = total_time + intro
return TACTICAL_RELOAD_FEEDING, total_time - reload_time
end
end
function M.interrupt_reload(api)
local cache = api:getCachedScriptData()
if (cache ~= nil and cache.interrupted_time == -1) then
cache.interrupted_time = api:getReloadTime()
end
end
return M
@@ -0,0 +1,153 @@
local M = {}
function M.start_reload(api)
return true
end
-- 🔥 SAFE PARAM PARSER (MATCHES YOUR JSON)
local function getReloadTimingFromParam(param)
if not param then return nil, nil end
local reload_feed = {}
local reload_cooldown = {}
local empty_feed = {}
local empty_cooldown = {}
-- EMPTY (only one slot)
empty_feed[1] = param.reload_empty_feed
empty_cooldown[1] = param.reload_empty_cooldown
-- tactical (ammo based → reversed like your animations)
reload_feed[1] = param.reload_4_feed
reload_cooldown[1] = param.reload_4_cooldown
reload_feed[2] = param.reload_3_feed
reload_cooldown[2] = param.reload_3_cooldown
reload_feed[3] = param.reload_2_feed
reload_cooldown[3] = param.reload_2_cooldown
reload_feed[4] = param.reload_1_feed
reload_cooldown[4] = param.reload_1_cooldown
-- ✅ VALIDATION + ms conversion
for i = 1, 4 do
if reload_feed[i] == nil or reload_cooldown[i] == nil then
return nil, nil
end
reload_feed[i] = reload_feed[i] * 1000
reload_cooldown[i] = reload_cooldown[i] * 1000
end
if empty_feed[1] == nil or empty_cooldown[1] == nil then
return nil, nil
end
empty_feed[1] = empty_feed[1] * 1000
empty_cooldown[1] = empty_cooldown[1] * 1000
return reload_feed, reload_cooldown, empty_feed, empty_cooldown
end
local function isReloadingEmpty(stateType)
return stateType == EMPTY_RELOAD_FEEDING or stateType == EMPTY_RELOAD_FINISHING
end
local function isReloadingTactical(stateType)
return stateType == TACTICAL_RELOAD_FEEDING or stateType == TACTICAL_RELOAD_FINISHING
end
local function finishReload(api, is_tactical)
local needAmmoCount = api:getNeededAmmoAmount()
if api:isReloadingNeedConsumeAmmo() then
api:putAmmoInMagazine(api:consumeAmmoFromPlayer(needAmmoCount))
else
api:putAmmoInMagazine(needAmmoCount)
end
-- chamber logic for empty reload
if not is_tactical then
local i = api:removeAmmoFromMagazine(1)
if i ~= 0 then
api:setAmmoInBarrel(true)
end
end
end
function M.tick_reload(api)
local param = api:getScriptParams()
local reload_feed, reload_cooldown, empty_feed, empty_cooldown =
getReloadTimingFromParam(param)
if reload_feed == nil then
return NOT_RELOADING, -1
end
local ammo = api:getAmmoCount()
local hasBullet = api:hasBulletInBarrel()
local totalAmmo = ammo + (hasBullet and 1 or 0)
-- prevent overflow
if totalAmmo >= 5 then
return NOT_RELOADING, -1
end
-- 🔥 SAFE LEVEL (14 ONLY)
local level = math.max(1, math.min(totalAmmo, 4))
local progressTime = api:getReloadTime()
local oldStateType = api:getReloadStateType()
local stateType = NOT_RELOADING
local countDown = -1
if isReloadingEmpty(oldStateType) then
local feed_time = empty_feed[1]
local finish_time = empty_cooldown[1]
if progressTime < feed_time then
stateType = EMPTY_RELOAD_FEEDING
countDown = feed_time - progressTime
elseif progressTime < finish_time then
stateType = EMPTY_RELOAD_FINISHING
countDown = finish_time - progressTime
else
stateType = NOT_RELOADING
end
elseif isReloadingTactical(oldStateType) then
local feed_time = reload_feed[level]
local finish_time = reload_cooldown[level]
if progressTime < feed_time then
stateType = TACTICAL_RELOAD_FEEDING
countDown = feed_time - progressTime
elseif progressTime < finish_time then
stateType = TACTICAL_RELOAD_FINISHING
countDown = finish_time - progressTime
else
stateType = NOT_RELOADING
end
else
return NOT_RELOADING, -1
end
-- apply ammo (same safe pattern as working script)
if oldStateType == EMPTY_RELOAD_FEEDING and oldStateType ~= stateType then
finishReload(api, false)
end
if oldStateType == TACTICAL_RELOAD_FEEDING and oldStateType ~= stateType then
finishReload(api, true)
end
return stateType, countDown
end
return M
@@ -0,0 +1,54 @@
function M.tick_reload(api)
local param = api:getScriptParams()
local reload_feed, reload_cooldown = getReloadTimingFromParam(param)
if reload_feed == nil then
return NOT_RELOADING, -1
end
local ammo = api:getAmmoCount()
local hasBullet = api:hasBulletInBarrel()
local totalAmmo = ammo + (hasBullet and 1 or 0)
-- ✅ PREVENT FULL MAG RELOAD CRASH
if totalAmmo >= 5 then
return NOT_RELOADING, -1
end
-- map ammo → index (15)
local level = totalAmmo + 1
local progressTime = api:getReloadTime()
local feed_time = reload_feed[level]
local finish_time = reload_cooldown[level]
local stateType = NOT_RELOADING
local countDown = -1
local oldStateType = api:getReloadStateType()
if progressTime < feed_time then
stateType = TACTICAL_RELOAD_FEEDING
countDown = feed_time - progressTime
elseif progressTime < finish_time then
stateType = TACTICAL_RELOAD_FINISHING
countDown = finish_time - progressTime
else
stateType = NOT_RELOADING
countDown = -1
end
-- ✅ THIS WAS MISSING → REQUIRED
if oldStateType == TACTICAL_RELOAD_FEEDING and oldStateType ~= stateType then
local needAmmoCount = api:getNeededAmmoAmount()
if api:isReloadingNeedConsumeAmmo() then
api:putAmmoInMagazine(api:consumeAmmoFromPlayer(needAmmoCount))
else
api:putAmmoInMagazine(needAmmoCount)
end
end
return stateType, countDown
end
@@ -0,0 +1,157 @@
local M = {}
-- Credit goes to Koei
function M.shoot(api)
if (api:getFireMode() == SEMI) then
api:shootOnce(api:isShootingNeedConsumeAmmo())
elseif (api:getFireMode() == BURST) then
api:shootOnce(api:isShootingNeedConsumeAmmo())
if (api:removeAmmoFromMagazine(1) == 1) then
api:setAmmoInBarrel(true)
end
end
end
function M.start_bolt(api)
-- Return true to start ticking, since there are nothing needed to be check
return true
end
function M.tick_bolt(api)
-- Get total bolt time from script parameter in gun data
local params = api:getScriptParams()
local total_bolt_time = params.bolt_time * 1000
local bolt_feed_time = params.bolt_feed_time * 1000
if (total_bolt_time == nil or bolt_feed_time == nil) then
return false
end
local bolt_time = api:getBoltTime()
if (bolt_time < bolt_feed_time) then
-- Bolt time less than total means we need to keep ticking, return true
return true
else
-- Bolt time greater than total means that the bullet
-- needs to be put from the magazine into the barrel, and then return false to end ticking.
if (not api:hasAmmoInBarrel()) then
if (api:removeAmmoFromMagazine(1) ~= 0) then
api:setAmmoInBarrel(true);
end
end
return bolt_time < total_bolt_time
end
end
function M.start_reload(api)
-- Initialize cache that will be used in reload ticking
local cache = {
reloaded_count = 0,
needed_count = api:getNeededAmmoAmount(),
is_tactical = api:getReloadStateType() == TACTICAL_RELOAD_FEEDING,
interrupted_time = -1,
}
api:cacheScriptData(cache)
-- Return true to start ticking
return true
end
local function getReloadTimingFromParam(param)
-- Need to convert time from seconds to milliseconds
local intro_empty = param.intro_empty * 1000
local intro = param.intro * 1000
local loop = param.loop * 1000
local ending = param.ending * 1000
local intro_empty_feed = param.intro_empty_feed * 1000
local loop_feed = param.loop_feed * 1000
-- Check if any timing is nil
if (intro_empty == nil or intro == nil or loop == nil or ending == nil or intro_empty_feed == nil or loop_feed == nil) then
return nil
end
return intro_empty, intro, loop, ending, intro_empty_feed, loop_feed
end
function M.tick_reload(api)
-- Get all timings from script parameter in gun data
local param = api:getScriptParams();
local intro_empty, intro, loop, ending, intro_empty_feed, loop_feed = getReloadTimingFromParam(param)
if (intro_empty == nil) then
return NOT_RELOADING, -1
end
-- Get reload time (The time from the start of reloading to the current time) from api
local reload_time = api:getReloadTime()
-- Get cache from api, it will be used to count loaded ammo, mark reload interruptions, etc.
local cache = api:getCachedScriptData()
local interrupted_time = cache.interrupted_time
-- Handle interrupting reload
if (interrupted_time ~= -1) then
local int_time = reload_time - interrupted_time
if (int_time >= ending) then
return NOT_RELOADING, -1
else
if (cache.is_tactical) then
return TACTICAL_RELOAD_FINISHING, ending - int_time
else
return EMPTY_RELOAD_FINISHING, ending - int_time
end
end
else
-- if there is no ammo to consume, interrupt reloading
if (not api:hasAmmoToConsume()) then
interrupted_time = api:getReloadTime()
end
end
-- Put an ammo into the barrel first
local reloaded_count = cache.reloaded_count;
if (reloaded_count == 0) then
if (not cache.is_tactical) then
if (reload_time > intro_empty_feed) then
api:consumeAmmoFromPlayer(1)
api:setAmmoInBarrel(true)
reloaded_count = reloaded_count + 1
end
else
reloaded_count = reloaded_count + 1
end
end
-- Load the ammo into the magazine one by one
if (reloaded_count > 0) then
local base_time = (reloaded_count -1) * loop + loop_feed
if (not cache.is_tactical) then
base_time = base_time + intro_empty
else
base_time = base_time + intro
end
while (base_time < reload_time) do
if (reloaded_count > cache.needed_count) then
break
end
reloaded_count = reloaded_count + 1
base_time = base_time + loop
api:consumeAmmoFromPlayer(1)
api:putAmmoInMagazine(1)
end
end
-- Write back cache
if (reloaded_count > cache.needed_count) then
interrupted_time = api:getReloadTime() - loop_feed + loop
end
cache.interrupted_time = interrupted_time
cache.reloaded_count = reloaded_count
api:cacheScriptData(cache)
-- return reloadstate
local total_time = cache.needed_count * loop
if (not cache.is_tactical) then
total_time = total_time + intro_empty
return EMPTY_RELOAD_FEEDING, total_time - reload_time
else
total_time = total_time + intro
return TACTICAL_RELOAD_FEEDING, total_time - reload_time
end
end
function M.interrupt_reload(api)
local cache = api:getCachedScriptData()
if (cache ~= nil and cache.interrupted_time == -1) then
cache.interrupted_time = api:getReloadTime()
end
end
return M
@@ -0,0 +1,141 @@
local M = {}
function M.start_reload(api)
return true
end
local function getReloadTimingFromParam(param)
if not param then return nil, nil, nil, nil end
local reload_feed = {
param.reload_4_feed,
param.reload_3_feed,
param.reload_2_feed,
param.reload_1_feed
}
local reload_cooldown = {
param.reload_4_cooldown,
param.reload_3_cooldown,
param.reload_2_cooldown,
param.reload_1_cooldown
}
local empty_feed = {
param.reload_empty_feed,
param.reload_empty_feed,
param.reload_empty_feed,
param.reload_empty_feed
}
local empty_cooldown = {
param.reload_empty_cooldown,
param.reload_empty_cooldown,
param.reload_empty_cooldown,
param.reload_empty_cooldown
}
for i = 1, 4 do
if (reload_feed[i] == nil or reload_cooldown[i] == nil or empty_feed[i] == nil or empty_cooldown[i] == nil) then
return nil, nil, nil, nil
end
reload_feed[i] = reload_feed[i] * 1000
reload_cooldown[i] = reload_cooldown[i] * 1000
empty_feed[i] = empty_feed[i] * 1000
empty_cooldown[i] = empty_cooldown[i] * 1000
end
return reload_feed, reload_cooldown, empty_feed, empty_cooldown
end
local function isReloadingEmpty(stateType)
return stateType == EMPTY_RELOAD_FEEDING or stateType == EMPTY_RELOAD_FINISHING
end
local function isReloadingTactical(stateType)
return stateType == TACTICAL_RELOAD_FEEDING or stateType == TACTICAL_RELOAD_FINISHING
end
-- ✅ FIXED (NO CHAMBER LOGIC)
local function finishReload(api, is_tactical)
local needAmmoCount = api:getNeededAmmoAmount()
if api:isReloadingNeedConsumeAmmo() then
api:putAmmoInMagazine(api:consumeAmmoFromPlayer(needAmmoCount))
else
api:putAmmoInMagazine(needAmmoCount)
end
-- ❌ REMOVED: removeAmmoFromMagazine / setAmmoInBarrel
end
function M.tick_reload(api)
local param = api:getScriptParams()
local reload_feed, reload_cooldown, empty_feed, empty_cooldown =
getReloadTimingFromParam(param)
if (reload_feed == nil) then
return NOT_RELOADING, -1
end
local mag_level = math.min(api:getMagExtentLevel(), 3) + 1
local countDown = -1
local stateType = NOT_RELOADING
local oldStateType = api:getReloadStateType()
local progressTime = api:getReloadTime()
if isReloadingEmpty(oldStateType) then
local feed_time = empty_feed[mag_level]
local finishing_time = empty_cooldown[mag_level]
if progressTime < feed_time then
stateType = EMPTY_RELOAD_FEEDING
countDown = feed_time - progressTime
elseif progressTime < finishing_time then
stateType = EMPTY_RELOAD_FINISHING
countDown = finishing_time - progressTime
else
stateType = NOT_RELOADING
end
elseif isReloadingTactical(oldStateType) then
local feed_time = reload_feed[mag_level]
local finishing_time = reload_cooldown[mag_level]
if progressTime < feed_time then
stateType = TACTICAL_RELOAD_FEEDING
countDown = feed_time - progressTime
elseif progressTime < finishing_time then
stateType = TACTICAL_RELOAD_FINISHING
countDown = finishing_time - progressTime
else
stateType = NOT_RELOADING
end
else
return NOT_RELOADING, -1
end
if oldStateType == EMPTY_RELOAD_FEEDING and oldStateType ~= stateType then
finishReload(api, false)
end
if oldStateType == TACTICAL_RELOAD_FEEDING and oldStateType ~= stateType then
finishReload(api, true)
end
return stateType, countDown
end
return M
@@ -0,0 +1,141 @@
local M = {}
function M.start_reload(api)
return true
end
local function getReloadTimingFromParam(param)
if not param then return nil, nil, nil, nil end
local reload_feed = {
param.reload_4_feed,
param.reload_3_feed,
param.reload_2_feed,
param.reload_1_feed
}
local reload_cooldown = {
param.reload_4_cooldown,
param.reload_3_cooldown,
param.reload_2_cooldown,
param.reload_1_cooldown
}
local empty_feed = {
param.reload_empty_feed,
param.reload_empty_feed,
param.reload_empty_feed,
param.reload_empty_feed
}
local empty_cooldown = {
param.reload_empty_cooldown,
param.reload_empty_cooldown,
param.reload_empty_cooldown,
param.reload_empty_cooldown
}
for i = 1, 4 do
if (reload_feed[i] == nil or reload_cooldown[i] == nil or empty_feed[i] == nil or empty_cooldown[i] == nil) then
return nil, nil, nil, nil
end
reload_feed[i] = reload_feed[i] * 1000
reload_cooldown[i] = reload_cooldown[i] * 1000
empty_feed[i] = empty_feed[i] * 1000
empty_cooldown[i] = empty_cooldown[i] * 1000
end
return reload_feed, reload_cooldown, empty_feed, empty_cooldown
end
local function isReloadingEmpty(stateType)
return stateType == EMPTY_RELOAD_FEEDING or stateType == EMPTY_RELOAD_FINISHING
end
local function isReloadingTactical(stateType)
return stateType == TACTICAL_RELOAD_FEEDING or stateType == TACTICAL_RELOAD_FINISHING
end
-- ✅ FIXED (NO CHAMBER LOGIC)
local function finishReload(api, is_tactical)
local needAmmoCount = api:getNeededAmmoAmount()
if api:isReloadingNeedConsumeAmmo() then
api:putAmmoInMagazine(api:consumeAmmoFromPlayer(needAmmoCount))
else
api:putAmmoInMagazine(needAmmoCount)
end
-- ❌ REMOVED: removeAmmoFromMagazine / setAmmoInBarrel
end
function M.tick_reload(api)
local param = api:getScriptParams()
local reload_feed, reload_cooldown, empty_feed, empty_cooldown =
getReloadTimingFromParam(param)
if (reload_feed == nil) then
return NOT_RELOADING, -1
end
local mag_level = math.min(api:getMagExtentLevel(), 3) + 1
local countDown = -1
local stateType = NOT_RELOADING
local oldStateType = api:getReloadStateType()
local progressTime = api:getReloadTime()
if isReloadingEmpty(oldStateType) then
local feed_time = empty_feed[mag_level]
local finishing_time = empty_cooldown[mag_level]
if progressTime < feed_time then
stateType = EMPTY_RELOAD_FEEDING
countDown = feed_time - progressTime
elseif progressTime < finishing_time then
stateType = EMPTY_RELOAD_FINISHING
countDown = finishing_time - progressTime
else
stateType = NOT_RELOADING
end
elseif isReloadingTactical(oldStateType) then
local feed_time = reload_feed[mag_level]
local finishing_time = reload_cooldown[mag_level]
if progressTime < feed_time then
stateType = TACTICAL_RELOAD_FEEDING
countDown = feed_time - progressTime
elseif progressTime < finishing_time then
stateType = TACTICAL_RELOAD_FINISHING
countDown = finishing_time - progressTime
else
stateType = NOT_RELOADING
end
else
return NOT_RELOADING, -1
end
if oldStateType == EMPTY_RELOAD_FEEDING and oldStateType ~= stateType then
finishReload(api, false)
end
if oldStateType == TACTICAL_RELOAD_FEEDING and oldStateType ~= stateType then
finishReload(api, true)
end
return stateType, countDown
end
return M