Files
escape-from-funcraft-client/tacz/daffas/assets/daffas_arsenal/scripts/dual_state_machine.lua
T
2026-07-23 16:35:06 +05:00

360 lines
14 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 脚本的位置是 "{命名空间}:{路径}",那么 require 的格式为 "{命名空间}_{路径}"
-- 注意!require 取得的内容不应该被修改,应仅调用
local default = require("tacz_default_state_machine")
local STATIC_TRACK_LINE = default.STATIC_TRACK_LINE
local MAIN_TRACK = default.MAIN_TRACK
local main_track_states = default.main_track_states
local static_track_top = default.static_track_top
local GUN_KICK_TRACK_LINE = default.GUN_KICK_TRACK_LINE
local gun_kick_state = default.gun_kick_state
local inspect_state = setmetatable({}, {__index = main_track_states.inspect})
local idle_state = setmetatable({}, {__index = main_track_states.idle})
-- 相当于 obj.value++
local function increment(obj)
obj.value = obj.value + 1
return obj.value - 1
end
local FIRE_MODE_TRACK = increment(static_track_top)
local SWITCH_MODE_TRACK = increment(static_track_top)
-- 检查当前是否还有弹药
local function isNoAmmo(context)
return (not context:hasBulletInBarrel()) and (context:getAmmoCount() <= 0)
end
local function runReloadAnimation(context)
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
local ext = context:getMagExtentLevel()
if (isNoAmmo(context)) then
if (ext == 0) then
context:runAnimation("reload_empty", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1) then
context:runAnimation("reload_empty_xmag_1", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 2) then
context:runAnimation("reload_empty_xmag_2", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 3) then
context:runAnimation("reload_empty_xmag_3", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("reload_empty", track, false, PLAY_ONCE_STOP, 0.2)
end
else
if (ext == 0) then
context:runAnimation("reload_tactical", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1) then
context:runAnimation("reload_tactical_xmag_1", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 2) then
context:runAnimation("reload_tactical_xmag_2", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 3) then
context:runAnimation("reload_tactical_xmag_3", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("reload_tactical", track, false, PLAY_ONCE_STOP, 0.2)
end
end
end
local function runInspectAnimation(context)
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
local ext = context:getMagExtentLevel()
if (isNoAmmo(context)) then
if (ext == 0) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 2) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 3) then
context:runAnimation("inspect_empty_xmag_3", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
end
else
if (ext == 0) then
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1) then
context:runAnimation("inspect_xmag_12", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 2) then
context:runAnimation("inspect_xmag_12", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 3) then
context:runAnimation("inspect_xmag_3", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
end
-- ========== 27-ROUND MAG, BULLET-BASED SHOOT SYSTEM ==========
local shoot_by_bullet = {}
for i = 1, 27 do
shoot_by_bullet[i] = "shoot_" .. tostring(i)
end
local shoot_last_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local shoot_state = setmetatable({}, { __index = gun_kick_state })
function shoot_state.transition(this, context, input)
if input ~= INPUT_SHOOT then return nil end
local fireMode = context:getFireMode()
if fireMode ~= SEMI and fireMode ~= AUTO and fireMode ~= BURST then return nil end
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
local mag = context:getAmmoCount() or 0
local chambered = context:hasBulletInBarrel() and true or false
local total_before = mag + (chambered and 1 or 0)
if total_before <= 0 then
return nil
end
-----------------------------------------------------------------
-- TRUE LAST-SHOT CHECK (fires shot that empties gun)
-----------------------------------------------------------------
if (total_before - 1) == 0 then
-- bolt-track animation
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
-- last shot on kick track
context:runAnimation(shoot_last_animation, track, true, PLAY_ONCE_STOP, 0)
-- last-shoot on MAIN_TRACK prevents undefined track errors
context:runAnimation(
"last_shoot",
context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK),
true,
PLAY_ONCE_HOLD,
0
)
return nil
end
-----------------------------------------------------------------
-- NORMAL SHOOT (127 mapping)
-----------------------------------------------------------------
local anim = shoot_by_bullet[mag]
if not anim then
-- Safety fallback: if somehow mag > 27 → clamp to 27
anim = "shoot_27"
end
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
return nil
end
-- Bolt catch after empty
-- === bolt / bullet visual state (replaces fragile idle-based bolt logic) ===
local BOLT_CAUGHT_TRACK = default.BOLT_CAUGHT_TRACK
local bolt_caught_states = default.bolt_caught_states
local normal_bolt_states = setmetatable({}, { __index = bolt_caught_states.normal })
local caught_bolt_states = setmetatable({}, { __index = bolt_caught_states.bolt_caught })
-- entry: start a dedicated 'bullet_state' animation on the bolt track
function normal_bolt_states.entry(this, context)
local track = context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK)
context:runAnimation("bullet_state", track, false, PLAY_ONCE_STOP, 0)
return this.bolt_caught_states.normal
end
local function isNoAmmoSimple(context)
-- use whatever ammo semantics you prefer here (chambered handled elsewhere)
return (context:getAmmoCount() or 0) <= 0
end
function normal_bolt_states.update(this, context)
if isNoAmmoSimple(context) then
context:trigger(this.INPUT_BOLT_CAUGHT)
return
end
local track = context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK)
local ammo = context:getAmmoCount() or 0
local maxAmmo = context:getMaxAmmoCount() or 1
-- progress goes from 0..1; adjust mapping if your bullet_state expects different
local progress = 1 - (ammo / maxAmmo)
context:setAnimationProgress(track, progress, false)
end
function caught_bolt_states.update(this, context)
if (not isNoAmmoSimple(context)) then
context:trigger(this.INPUT_BOLT_NORMAL)
end
end
-- Inject the override into the module table later (M will pick this up)
-- === end bolt visual override ===
-- === single, safe idle transition (input handling only) ===
function idle_state.transition(this, context, input)
-- INPUT_RELOAD: run reload animations and fall back to the parent's idle state
if (input == INPUT_RELOAD) then
runReloadAnimation(context)
return main_track_states.idle
end
-- INPUT_PUT_AWAY: play put_away on MAIN_TRACK and go to final
if (input == INPUT_PUT_AWAY) then
local put_away_time = context:getPutAwayTime()
local track_main = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
context:runAnimation("put_away", track_main, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
context:setAnimationProgress(track_main, 1, true)
context:adjustAnimationProgress(track_main, -put_away_time, false)
return main_track_states.final
end
-- INPUT_INSPECT: run inspect animation and switch to the inspect state
if (input == INPUT_INSPECT) then
runInspectAnimation(context)
return inspect_state
end
-- default: delegate to the original main_track_states idle transition
return main_track_states.idle.transition(this, context, input)
end
-- =============================================================
function idle_state.transition(this, context, input)
if (input == INPUT_RELOAD) then
runReloadAnimation(context)
return this.main_track_states.idle
end
if (input == INPUT_PUT_AWAY) then
local put_away_time = context:getPutAwayTime()
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
context:setAnimationProgress(track, 1, true)
context:adjustAnimationProgress(track, -put_away_time, false)
return this.main_track_states.final
end
if (input == INPUT_INSPECT) then
runInspectAnimation(context)
return inspect_state
end
return main_track_states.idle.transition(this, context, input)
end
local fire_mode_state = {
semi = {},
auto = {},
draw = {}
}
function fire_mode_state.draw.update(this, context)
context:trigger(this.INPUT_MODE_DRAW)
end
function fire_mode_state.draw.transition(this, context,input)
if (input == this.INPUT_MODE_DRAW) then
if (context:getFireMode() == SEMI) then
context:runAnimation("static_semi", context:getTrack(STATIC_TRACK_LINE, FIRE_MODE_TRACK), true, PLAY_ONCE_HOLD, 0)
return fire_mode_state.semi
elseif (context:getFireMode() == AUTO) then
context:runAnimation("static_auto", context:getTrack(STATIC_TRACK_LINE, FIRE_MODE_TRACK), true, PLAY_ONCE_HOLD, 0)
return fire_mode_state.auto
end
end
end
function fire_mode_state.semi.update(this, context)
local track = context:getTrack(STATIC_TRACK_LINE, FIRE_MODE_TRACK)
if (context:isHolding(track)) then
context:runAnimation("static_semi", track, true, PLAY_ONCE_HOLD, 0)
end
if (context:getFireMode() == AUTO) then
context:trigger(this.INPUT_MODE_AUTO)
end
end
function fire_mode_state.semi.transition(this, context,input)
if(input == this.INPUT_MODE_AUTO)then
context:runAnimation("switch_auto", context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK), false, PLAY_ONCE_STOP, 0)
return fire_mode_state.auto
end
if(input == INPUT_SHOOT or input == INPUT_RELOAD or input == INPUT_INSPECT)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
end
function fire_mode_state.auto.update(this, context)
local track = context:getTrack(STATIC_TRACK_LINE, FIRE_MODE_TRACK)
if (context:isHolding(track)) then
context:runAnimation("static_auto", track, true, PLAY_ONCE_HOLD, 0)
end
if (context:getFireMode() == SEMI) then
context:trigger(this.INPUT_MODE_SEMI)
end
end
function fire_mode_state.auto.transition(this, context,input)
if(input == this.INPUT_MODE_SEMI)then
context:runAnimation("switch_semi", context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK), false, PLAY_ONCE_STOP, 0)
return fire_mode_state.semi
end
if(input == INPUT_SHOOT or input == INPUT_RELOAD or input == INPUT_INSPECT)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
end
function inspect_state.transition(this, context, input)
if (input == INPUT_FIRE_SELECT) then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK))
return this.main_track_states.idle
end
return main_track_states.inspect.transition(this, context, input)
end
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
bolt_caught_states = setmetatable({
bolt_caught = caught_bolt_states,
normal = normal_bolt_states
}, { __index = bolt_caught_states }), -- << ensure this line is present
main_track_states = setmetatable({
idle = idle_state,
inspect = inspect_state
}, {__index = main_track_states}),
FIRE_MODE_TRACK = FIRE_MODE_TRACK,
SWITCH_MODE_TRACK = SWITCH_MODE_TRACK,
INPUT_MODE_SEMI = "input_mode_semi",
INPUT_MODE_BURST = "input_mode_burst",
INPUT_MODE_AUTO = "input_mode_auto",
INPUT_MODE_DRAW = "input_mode_draw",
fire_mode_state = fire_mode_state
}, {__index = default})
function M:initialize(context)
default.initialize(self, context)
end
function M:states()
return {
self.base_track_state,
self.bolt_caught_states.normal,
self.main_track_states.start,
self.gun_kick_state,
self.movement_track_states.idle,
self.ADS_states.normal,
self.slide_states.normal,
self.fire_mode_state.draw
}
end
return M