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,265 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8", "shoot_9"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local current_shoot_index = 1
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 }),
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
@@ -0,0 +1,263 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8", "shoot_9"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 }),
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
@@ -0,0 +1,359 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
@@ -0,0 +1,341 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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 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
-- ===== Get the actual gun kick track =====
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
-- ===== Ammo calculation =====
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
-- ===== Last shot =====
if (total_before - 1) == 0 then
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
context:runAnimation(shoot_last_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), true, PLAY_ONCE_HOLD, 0)
return nil
end
-- ===== Normal shoot (27 → 1) =====
-- Map ammo 27 → shoot_27, 1 → shoot_1
local animIndex = math.max(1, math.min(27, mag))
local anim = shoot_by_bullet[animIndex] or "shoot_1"
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
@@ -0,0 +1,263 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8", "shoot_9"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 }),
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
@@ -0,0 +1,262 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8", "shoot_9"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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 ~= BURST and fireMode ~= AUTO and fireMode ~= BURST then
return nil
end
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 = {
burst = {},
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() == BURST) then
context:runAnimation("static_burst", context:getTrack(STATIC_TRACK_LINE, FIRE_MODE_TRACK), true, PLAY_ONCE_HOLD, 0)
return fire_mode_state.burst
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.burst.update(this, context)
local track = context:getTrack(STATIC_TRACK_LINE, FIRE_MODE_TRACK)
if (context:isHolding(track)) then
context:runAnimation("static_burst", track, true, PLAY_ONCE_HOLD, 0)
end
if (context:getFireMode() == AUTO) then
context:trigger(this.INPUT_MODE_AUTO)
end
end
function fire_mode_state.burst.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() == BURST) then
context:trigger(this.INPUT_MODE_BURST)
end
end
function fire_mode_state.auto.transition(this, context,input)
if(input == this.INPUT_MODE_BURST)then
context:runAnimation("switch_burst", context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK), false, PLAY_ONCE_STOP, 0)
return fire_mode_state.burst
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 }),
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_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
@@ -0,0 +1,263 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8", "shoot_9"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 }),
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
@@ -0,0 +1,252 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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 == 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 == 3) then
context:runAnimation("inspect_xmag_3", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1 or ext == 2) then
context:runAnimation("inspect_xmag_12", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
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
-- HOPE STATE
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8", "shoot_9"
}
local shoot_animation_index = 1
local last_shoot_animation = "shoot_sup_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", true)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- HOPE STATE END
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
idle = idle_state,
main_track_states = setmetatable({
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
@@ -0,0 +1,251 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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 == 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 == 3) then
context:runAnimation("inspect_xmag_3", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1 or ext == 2) then
context:runAnimation("inspect_xmag_12", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
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
-- HOPE STATE
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4"
}
local shoot_animation_index = 1
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- HOPE STATE END
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
idle = idle_state,
main_track_states = setmetatable({
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
@@ -0,0 +1,305 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- "ext"表示扩容插件等级,"0"代表不装扩容插件
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
-- "ext"表示扩容插件等级,"0"代表不装扩容插件
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
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))
-- 播放 put_away 动画,并且将其过渡时长设为从上下文里传入的 put_away_time * 0.75
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
-- 设定动画进度为最后一帧
context:setAnimationProgress(track, 1, true)
-- 将动画进度向前拨动 {put_away_time}
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
-- 此处为开火模式轨道的状态,专门为快慢机动画服务,兼容其他动作,可按需求添加三种射击模式(semi、burst、auto)
local fire_mode_state = {
-- 半自动状态
semi = {},
-- 全自动状态
auto = {},
-- 掏枪状态
draw = {}
}
-- 这一块专门用来检测枪械在掏枪(播放draw)时枪械处于什么射击模式,并切换到对应模式的idle
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
-- 注意!后面关于每个开火模式对应状态之间的切换,需要按照data里填写的顺序进行转换
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)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(input == INPUT_RELOAD)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(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)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(input == INPUT_RELOAD)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(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
-- HOPE STATE
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getRandomShootAnimation()
local index = math.random(1, #shoot_animations)
return shoot_animations[index]
end
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 ammo_before = context:getAmmoCount()
local ammo_after = ammo_before - 0 -- ← This doesn't actually reduce ammo!
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0 then
-- This is the last shot: play "shoot_last"
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- Regular shot
local anim = getRandomShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
-- Only run bolt catch if last shoot animation finished playing (track not busy)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- HOPE STATE
-- 用元表的方式继承默认状态机的属性
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }), -- HOPE STATE
idle = idle_state, -- HOPE STATE
main_track_states = setmetatable({
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
@@ -0,0 +1,253 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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 == 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 == 3) then
context:runAnimation("inspect_xmag_3", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1 or ext == 2) then
context:runAnimation("inspect_xmag_12", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
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
-- HOPE STATE
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8"
}
local shoot_animation_index = 1
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- HOPE STATE END
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
idle = idle_state,
main_track_states = setmetatable({
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
@@ -0,0 +1,263 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", true)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 }),
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
@@ -0,0 +1,184 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 require 的格式为 "{命名空间}_{路径}"
-- 注意!require 取得的内容不应该被修改,应仅调用
local default = require("tacz_default_state_machine")
local STATIC_TRACK_LINE = default.STATIC_TRACK_LINE
local BOLT_CAUGHT_TRACK = default.BOLT_CAUGHT_TRACK
local bolt_caught_states = default.bolt_caught_states
local normal_state = setmetatable({}, {__index = bolt_caught_states.normal})
-- 检查当前是否还有弹药
local function isNoAmmo(context)
-- 这里同时检查了枪管和弹匣
return (not context:hasBulletInBarrel()) and (context:getAmmoCount() <= 0)
end
-- 更新"不空挂"状态
function normal_state.update(this, context)
-- 如果弹药数量是 0 了,那么立刻手动触发一次转到"空挂"状态的输入
if (isNoAmmo(context)) then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK))
context:trigger(this.INPUT_BOLT_CAUGHT)
else
local a = context:getAmmoCount()
if (a < 9) then
context:setAnimationProgress(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK),0.1+(8-a)*0.5,false)
else
context:setAnimationProgress(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK),0.1,false)
end
end
end
-- 进入"不空挂"状态
function normal_state.entry(this, context)
context:runAnimation("static_ammo_display", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), false, PLAY_ONCE_STOP, 0)
this.bolt_caught_states.normal.update(this, context)
end
local GUN_KICK_TRACK_LINE = default.GUN_KICK_TRACK_LINE
local gun_kick_state = default.gun_kick_state
local main_track_states = default.main_track_states
local idle_state = setmetatable({}, {__index = main_track_states.idle})
-- Shoot animations
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8", "shoot_9"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
-- ===========================================================================================================================================================================
-- Idle state bolt catch after last shot
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- ===========================================================================================================================================================================
-- Random shooting state
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
-- 用元表的方式继承默认状态机的属性
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
bolt_caught_states = setmetatable({
normal = normal_state,
}, {__index = bolt_caught_states}),
}, {__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.over_heat_states.normal,
self.main_track_states.start,
self.gun_kick_state,
self.movement_track_states.idle,
self.ADS_states.normal,
self.slide_states.normal
}
end
-- 导出状态机
return M
@@ -0,0 +1,622 @@
-- 这些栈顶指针在分配新的轨道行和轨道时起作用
-- 轨道行 栈顶指针
local track_line_top = {value = 0}
-- 主轨道行 的 轨道 栈顶指针
local static_track_top = {value = 0}
-- 混合轨道行 的 轨道 栈顶指针
local blending_track_top = {value = 0}
-- 栈顶指针自增函数,用于分配新的轨道行或者轨道
local function increment(obj)
obj.value = obj.value + 1
return obj.value - 1
end
-- 主轨道行 和 其中的七条轨道(新增 AMMO_DISPLAY_TRACK
local default = require("tacz_default_state_machine")
local STATIC_TRACK_LINE = increment(track_line_top)
local BASE_TRACK = increment(static_track_top)
local BOLT_CAUGHT_TRACK = increment(static_track_top)
local SAFETY_TRACK = increment(static_track_top)
local ADS_TRACK = increment(static_track_top)
local CROUCH_TRACK = increment(static_track_top)
local MAIN_TRACK = increment(static_track_top)
local AMMO_DISPLAY_TRACK = increment(static_track_top)
-- 开火的轨道行
local GUN_KICK_TRACK_LINE = increment(track_line_top)
-- 混合轨道行 和 其中的两条轨道,用于叠加动画,如跑步走路跳跃, LOOP_TRACK 只有定义却尚未启用,因此作用尚不得知
local BLENDING_TRACK_LINE = increment(track_line_top)
local MOVEMENT_TRACK = increment(blending_track_top)
local LOOP_TRACK = increment(blending_track_top)
local lastADSState = nil
local lastCrouchState = nil
local function updateADSAnimation(context)
local aim = context:getAimingProgress()
local adsNow = (aim > 0.5)
if adsNow ~= lastADSState then
lastADSState = adsNow
local track = context:getTrack(STATIC_TRACK_LINE, ADS_TRACK)
-- Stop crouch animation to avoid conflict
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, CROUCH_TRACK))
if adsNow then
context:runAnimation("ads_in", track, false, PLAY_ONCE_HOLD, 0.1)
else
context:runAnimation("ads_out", track, false, PLAY_ONCE_HOLD, 0.1)
end
end
end
local function updateCrouchAnimation(context)
local crouchNow = context:isCrouching()
local track = context:getTrack(STATIC_TRACK_LINE, CROUCH_TRACK)
-- If state changed, play in/out animation
if crouchNow ~= lastCrouchState then
lastCrouchState = crouchNow
-- Stop ADS animation to avoid conflict
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, ADS_TRACK))
if crouchNow then
context:runAnimation("crouch_in", track, false, PLAY_ONCE_HOLD, 0.1)
else
context:runAnimation("crouch_out", track, false, PLAY_ONCE_HOLD, 0.1)
end
end
-- 💡 Important: if crouching and crouch animation is stopped (e.g., after ADS), replay crouch_idle
if crouchNow and context:isStopped(track) and not lastADSState then
context:runAnimation("crouch_idle", track, true, LOOP, 0)
end
end
-- 播放丢枪动画的方法
local function runPutAwayAnimation(context)
local put_away_time = context:getPutAwayTime()
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 播放 put_away 动画,并且将其过渡时长设为从上下文里传入的 put_away_time * 0.75
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
-- 设定动画进度为最后一帧
context:setAnimationProgress(track, 1, true)
-- 将动画进度向前拨动 {put_away_time}
context:adjustAnimationProgress(track, -put_away_time, false)
end
-- 检查当前是否还有弹药
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)
-- 根据当前整枪内是否还有弹药决定是播放战术换弹还是空枪换弹
if (isNoAmmo(context)) then
context:runAnimation("reload_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("reload_tactical", track, false, PLAY_ONCE_STOP, 0.2)
end
end
-- 播放检视动画的方法
local function runInspectAnimation(context)
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 根据当前整枪内是否还有弹药决定是播放普通检视还是空枪检视
if (isNoAmmo(context)) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
-- 基础轨道上的状态,这个状态用于循环播放 static_idle 动画。
local base_track_state = {}
-- 进入基础状态,直接播放 static_idle
function base_track_state.entry(this, context)
-- 在 主轨道行 的 基础轨道 上循环播放 static_idle
context:runAnimation("static_idle", context:getTrack(STATIC_TRACK_LINE, BASE_TRACK), false, LOOP, 0)
end
-- 空挂部分,该部分到 147 行结束
-- 由于空挂分为"空挂"和"不空挂"两类,因此这里面需要两个状态来调控当前武器
-- 空挂的两个状态之间是会来回切换的,因此每个子状态都需要以下三个方法来操作
-- entry 方法是进入该状态时发生的事,只在进入状态时执行一次
-- update 方法是在该状态时每一渲染帧都会调用的事,注意这里是每一渲染帧,并不是游戏的 tick ,因此这个方法的执行频率远远大于 tick (除非你电脑玩游戏连 20 帧都不到)
-- transition 方法是该状态的转出,只在转换为别的状态时执行一次
--
-- 这部分的一般实现逻辑是
-- entry 负责在进入 update 前播放相关动画并进入 update
-- update 负责实时检测是否需要转出状态,如有需要则触发 transition
-- transition 负责接收 update 传来的信息,在被触发后需要停止相关动画并转去其他状态
-- 这套逻辑在之后的主状态里一样生效
local bolt_caught_states = {
-- normal 是不空挂的正常状态
normal = {},
-- bolt_caught 是空挂时的状态
bolt_caught = {}
}
-- 更新"不空挂"状态
function bolt_caught_states.normal.update(this, context)
-- 如果弹药数量是 0 了,那么立刻手动触发一次转到"空挂"状态的输入
if (isNoAmmo(context)) then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK))
context:trigger(this.INPUT_BOLT_CAUGHT)
else
local a = context:getAmmoCount()
if (a < 9) then
context:setAnimationProgress(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK),0.1+(8-a)*0.5,false)
else
context:setAnimationProgress(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK),0.1,false)
end
end
end
-- 进入"不空挂"状态
function bolt_caught_states.normal.entry(this, context)
context:runAnimation("static_ammo_display", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), false, PLAY_ONCE_STOP, 0)
this.bolt_caught_states.normal.update(this, context)
end
-- 转出"不空挂"状态
function bolt_caught_states.normal.transition(this, context, input)
-- 如果收到了"空挂"的输入,那么直接转到"空挂"状态,"'空挂'的输入"是在上文 update 方法中出现的
if (input == this.INPUT_BOLT_CAUGHT) then
return this.bolt_caught_states.bolt_caught
end
end
-- 进入"空挂"状态
function bolt_caught_states.bolt_caught.entry(this, context)
-- 进入空挂时在主轨道行的空挂轨道播放空挂的动画
context:runAnimation("static_bolt_caught", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), false, LOOP, 0)
end
-- 更新"空挂"状态
function bolt_caught_states.bolt_caught.update(this, context)
-- 如果检测到子弹数不为 0 了(此时是换弹了),那么手动触发一次转到"不空挂"状态的输入
if (not isNoAmmo(context)) then
context:trigger(this.INPUT_BOLT_NORMAL)
end
end
-- 转出"空挂"状态
function bolt_caught_states.bolt_caught.transition(this, context, input)
-- 如果收到了来自上文 update 方法的输入,则转到"不空挂"状态
if (input == this.INPUT_BOLT_NORMAL) then
-- 由于并没有一个"不空挂"的动画,因此必须在这里把空挂动画停止了才能转到"不空挂"状态,否则你会在换完弹之后发现依旧处于空挂状态
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK))
return this.bolt_caught_states.normal
end
end
-- 结束空挂部分
-- 主轨道状态,该部分到 256 行结束
-- 主轨道控制的是武器的基本动作,包括换弹,检视,刺刀攻击,掏枪,丢枪
-- 除了检视,其他动作不需要单独的状态控制。
-- 检视状态需要被射击输入打断,此外,进入检视时,需要隐藏准心,退出检视时恢复准心。
-- 除了上文的那三个方法,检视还需要 exit 方法,用于恢复准心渲染
local main_track_states = {
-- 起始
start = {},
-- 闲置,当玩家把枪拿在手里站定并什么也不做的时候就是这种情况
idle = {},
-- 检视
inspect = {},
-- 结束
final = {},
-- 刺刀攻击的计数器
bayonet_counter = 0
}
-- 转出 start (其实就是掏枪)
function main_track_states.start.transition(this, context, input)
-- 玩家手里拿到枪的那一瞬间会自动输入一个 draw 的信号,不用手动触发
if (input == INPUT_DRAW) then
-- 收到 draw 信号后在主轨道行的主轨道上播放掏枪动画,然后转到闲置态
context:runAnimation("draw", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0)
return this.main_track_states.idle
end
end
-- 转出闲置态
-- 在闲置状态中添加瞄准处理
function main_track_states.idle.transition(this, context, input)
if (input == INPUT_PUT_AWAY) then
runPutAwayAnimation(context)
return this.main_track_states.final
end
if (input == INPUT_RELOAD) then
runReloadAnimation(context)
return this.main_track_states.idle
end
if (input == INPUT_SHOOT) then
context:popShellFrom(0)
return this.main_track_states.idle
end
if (input == INPUT_BOLT) then
context:runAnimation("bolt", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
if (input == INPUT_INSPECT) then
runInspectAnimation(context)
return this.main_track_states.inspect
end
if (input == INPUT_BAYONET_MUZZLE) then
local counter = this.main_track_states.bayonet_counter
local animationName = "melee_bayonet_" .. tostring(counter + 1)
this.main_track_states.bayonet_counter = (counter + 1) % 3
context:runAnimation(animationName, context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
if (input == INPUT_BAYONET_STOCK) then
context:runAnimation("melee_stock", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
if (input == INPUT_BAYONET_PUSH) then
context:runAnimation("melee_push", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
-- 每帧检查并更新瞄准动画
updateADSAnimation(context)
updateCrouchAnimation(context)
end
-- 进入检视态
function main_track_states.inspect.entry(this, context)
-- 检视是需要隐藏屏幕中央准星
context:setShouldHideCrossHair(true)
end
-- 退出检视态
function main_track_states.inspect.exit(this, context)
-- 退出后恢复屏幕中央准星
context:setShouldHideCrossHair(false)
end
-- 更新检视态
function main_track_states.inspect.update(this, context)
-- 当检测到动画停止了(播完了)时手动触发一次退出信号
if (context:isStopped(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK))) then
context:trigger(this.INPUT_INSPECT_RETREAT)
end
end
-- 转出检视态
function main_track_states.inspect.transition(this, context, input)
-- 当收到来自 update 的退出信号时返回到闲置态,此时不需要停止动画是因为在 update 里是动画已经停止了才发出的退出信号
if (input == this.INPUT_INSPECT_RETREAT) then
return this.main_track_states.idle
end
-- 特殊地,射击应当打断检视,当检测到射击输入时应该直接停止动画并返回闲置态
if (input == INPUT_SHOOT) then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK))
return this.main_track_states.idle
end
return this.main_track_states.idle.transition(this, context, input)
end
-- 结束主状态部分
-- 射击态,没什么需要调控的
local gun_kick_state = {}
function gun_kick_state.transition(this, context, input)
-- 玩家按下开火键时需要在射击轨道行里寻找空闲轨道去播放射击动画(如果没有空闲会分配新的),需要注意的是射击动画要向下混合
if (input == INPUT_SHOOT) then
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
-- 这里是混合动画,一般是可叠加的 gun kick
context:runAnimation("shoot", track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
-- 移动轨道的状态,这部分到 435 行结束
local movement_track_states = {
-- 静止不动(或者在天上)
idle = {},
-- 奔跑, -1 是没有奔跑, 0 是在奔跑中
run = {
mode = -1
},
-- 行走, -1 是没有行走, 0 是在空中, 1 是正在瞄准, 2 是在向前走, 3 是向后退, 4 是向侧面走
walk = {
mode = -1
}
}
-- 更新静止态
function movement_track_states.idle.update(this, context)
-- 此处获取的是混合轨道行的移动轨道
local track = context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK)
-- 如果轨道空闲,则播放 idle 动画
-- 注意此处没有写成是在 entry 播放 idle 动画是因为要实时检测轨道是否空闲
if (context:isStopped(track) or context:isHolding(track)) then
context:runAnimation("idle", track, true, LOOP, 0)
end
end
-- 转出静止态
function movement_track_states.idle.transition(this, context, input)
-- 如果玩家在奔跑则转去奔跑态
if (input == INPUT_RUN) then
return this.movement_track_states.run
-- 如果玩家在行走则转去行走态
elseif (input == INPUT_WALK) then
return this.movement_track_states.walk
end
end
-- 进入奔跑态
function movement_track_states.run.entry(this, context)
this.movement_track_states.run.mode = -1
-- 此处播放的轨道是混合轨道行的移动轨道,播放的动画是奔跑的起手式,播放结束后是挂起动画而不是停止
context:runAnimation("run_start", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.2)
end
-- 退出奔跑态
function movement_track_states.run.exit(this, context)
-- 此时播放的动画是奔跑结束回到 idle 的动画,同理播放完后挂起
context:runAnimation("run_end", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.3)
end
-- 更新奔跑态
function movement_track_states.run.update(this, context)
local track = context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK)
local state = this.movement_track_states.run;
-- 等待 run_start 结束,然后循环播放 run ,此处的判断准则是轨道是否挂起,也就是为什么 entry 里播放动画要选 PLAY_ONCE_HOLD 模式
if (context:isHolding(track)) then
context:runAnimation("run", track, true, LOOP, 0.2)
-- 检测是否奔跑的标志位 0
state.mode = 0
context:anchorWalkDist() -- 打 walkDist 锚点,确保 run 动画的起点一致
end
if (state.mode ~= -1) then
if (not context:isOnGround()) then
-- 如果玩家在空中,则播放 run_hold 动画以稳定枪身
if (state.mode ~= 1) then
state.mode = 1
context:runAnimation("run_hold", track, true, LOOP, 0.6)
end
else
-- 如果玩家在地面,则切换回 run 动画
if (state.mode ~= 0) then
state.mode = 0
context:runAnimation("run", track, true, LOOP, 0.2)
end
-- 根据 walkDist 设置 run 动画的进度
context:setAnimationProgress(track, (context:getWalkDist() % 2.0) / 2.0, true)
end
end
end
-- 转出奔跑态
function movement_track_states.run.transition(this, context, input)
-- 收到闲置输入则转去闲置态
if (input == INPUT_IDLE) then
return this.movement_track_states.idle
-- 收到行走输入则转去行走态
elseif (input == INPUT_WALK) then
return this.movement_track_states.walk
end
end
-- 进入行走态
function movement_track_states.walk.entry(this, context)
-- 此时给标志位置为 -1 相当于一个初始化
this.movement_track_states.walk.mode = -1
end
-- 退出行走态
function movement_track_states.walk.exit(this, context)
-- 手动播放一次 idle 动画以打断 walk 动画的循环
context:runAnimation("idle", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.4)
end
-- 更新行走态
function movement_track_states.walk.update(this, context)
-- 此处获取的是混合轨道行的移动轨道
local track = context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK)
-- 这里的 state 代指自身,相当于一个简化写法
local state = this.movement_track_states.walk
if (context:getShootCoolDown() > 0) then
-- 如果刚刚开火,则播放 idle 动画以稳定枪身
if (state.mode ~= 0) then
state.mode = 0
context:runAnimation("idle", track, true, LOOP, 0.3)
end
elseif (not context:isOnGround()) then
-- 如果玩家在空中,则播放 idle 动画以稳定枪身
if (state.mode ~= 0) then
state.mode = 0
context:runAnimation("idle", track, true, LOOP, 0.6)
end
elseif (context:getAimingProgress() > 0.5) then
-- 如果正在喵准,则需要播放 walk_aiming 动画
if (state.mode ~= 1) then
state.mode = 1
context:runAnimation("walk_aiming", track, true, LOOP, 0.3)
end
elseif (context:isInputUp()) then
-- 如果正在向前走,则需要播放 walk_forward 动画
if (state.mode ~= 2) then
state.mode = 2
context:runAnimation("walk_forward", track, true, LOOP, 0.4)
context:anchorWalkDist() -- 打 walkDist 锚点,确保行走动画的起点一致
end
elseif (context:isInputDown()) then
-- 如果正在向后退,则需要播放 walk_backward 动画
if (state.mode ~= 3) then
state.mode = 3
context:runAnimation("walk_backward", track, true, LOOP, 0.4)
context:anchorWalkDist() -- 打 walkDist 锚点,确保行走动画的起点一致
end
elseif (context:isInputLeft() or context:isInputRight()) then
-- 如果正在向侧面,则需要播放 walk_sideway 动画
if (state.mode ~= 4) then
state.mode = 4
context:runAnimation("walk_sideway", track, true, LOOP, 0.4)
context:anchorWalkDist() -- 打 walkDist 锚点,确保行走动画的起点一致
end
end
-- 根据 walkDist 设置行走动画的进度
if (state.mode >= 1 and state.mode <= 4) then
context:setAnimationProgress(track, (context:getWalkDist() % 2.0) / 2.0, true)
end
end
-- 转出行走态,这部分和转出奔跑态是一样的
function movement_track_states.walk.transition(this, context, input)
-- 收到闲置信号则转到闲置态
if (input == INPUT_IDLE) then
return this.movement_track_states.idle
-- 收到奔跑信号则转到奔跑态
elseif (input == INPUT_RUN) then
return this.movement_track_states.run
end
end
-- 结束移动轨道的状态
-- 随机射击动画(HOPE STATE
local shoot_animations = {
"shoot_1", "shoot_2"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
-- 射击态,包含随机射击动画逻辑
local gun_kick_state = {}
function gun_kick_state.transition(this, context, input)
if (input == INPUT_SHOOT) then
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", true)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- 播放丢枪动画的方法
local function runPutAwayAnimation(context)
local put_away_time = context:getPutAwayTime()
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 播放 put_away 动画,并且将其过渡时长设为从上下文里传入的 put_away_time * 0.75
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
-- 设定动画进度为最后一帧
context:setAnimationProgress(track, 1, true)
-- 将动画进度向前拨动 {put_away_time}
context:adjustAnimationProgress(track, -put_away_time, false)
end
-- 检查当前是否还有弹药
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)
-- 根据当前整枪内是否还有弹药决定是播放战术换弹还是空枪换弹
if (isNoAmmo(context)) then
context:runAnimation("reload_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("reload_tactical", track, false, PLAY_ONCE_STOP, 0.2)
end
end
-- 播放检视动画的方法
local function runInspectAnimation(context)
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 根据当前整枪内是否还有弹药决定是播放普通检视还是空枪检视
if (isNoAmmo(context)) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
local M = {
track_line_top = track_line_top,
STATIC_TRACK_LINE = STATIC_TRACK_LINE,
GUN_KICK_TRACK_LINE = GUN_KICK_TRACK_LINE,
BLENDING_TRACK_LINE = BLENDING_TRACK_LINE,
static_track_top = static_track_top,
BASE_TRACK = BASE_TRACK,
BOLT_CAUGHT_TRACK = BOLT_CAUGHT_TRACK,
SAFETY_TRACK = SAFETY_TRACK,
ADS_TRACK = ADS_TRACK,
MAIN_TRACK = MAIN_TRACK,
AMMO_DISPLAY_TRACK = AMMO_DISPLAY_TRACK,
blending_track_top = blending_track_top,
MOVEMENT_TRACK = MOVEMENT_TRACK,
LOOP_TRACK = LOOP_TRACK,
base_track_state = base_track_state,
bolt_caught_states = bolt_caught_states,
main_track_states = main_track_states,
gun_kick_state = gun_kick_state,
movement_track_states = movement_track_states,
INPUT_BOLT_CAUGHT = "bolt_caught",
INPUT_BOLT_NORMAL = "bolt_normal",
INPUT_INSPECT_RETREAT = "inspect_retreat"
}
function M:initialize(context)
context:ensureTrackLineSize(track_line_top.value)
context:ensureTracksAmount(STATIC_TRACK_LINE, static_track_top.value)
context:ensureTracksAmount(BLENDING_TRACK_LINE, blending_track_top.value)
self.movement_track_states.run.mode = -1
self.movement_track_states.walk.mode = -1
end
function M:exit(context)
-- do some cleaning up things
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
}
end
return M
@@ -0,0 +1,685 @@
-- 这些栈顶指针在分配新的轨道行和轨道时起作用
-- 轨道行 栈顶指针
local track_line_top = {value = 0}
-- 主轨道行 的 轨道 栈顶指针
local static_track_top = {value = 0}
-- 混合轨道行 的 轨道 栈顶指针
local blending_track_top = {value = 0}
-- 栈顶指针自增函数,用于分配新的轨道行或者轨道
local function increment(obj)
obj.value = obj.value + 1
return obj.value - 1
end
-- 主轨道行 和 其中的七条轨道(新增 AMMO_DISPLAY_TRACK
local STATIC_TRACK_LINE = increment(track_line_top)
local BASE_TRACK = increment(static_track_top)
local BOLT_CAUGHT_TRACK = increment(static_track_top)
local SAFETY_TRACK = increment(static_track_top)
local ADS_TRACK = increment(static_track_top)
local CROUCH_TRACK = increment(static_track_top)
local MAIN_TRACK = increment(static_track_top)
local AMMO_DISPLAY_TRACK = increment(static_track_top)
-- 开火的轨道行
local GUN_KICK_TRACK_LINE = increment(track_line_top)
-- 混合轨道行 和 其中的两条轨道,用于叠加动画,如跑步走路跳跃, LOOP_TRACK 只有定义却尚未启用,因此作用尚不得知
local BLENDING_TRACK_LINE = increment(track_line_top)
local MOVEMENT_TRACK = increment(blending_track_top)
local LOOP_TRACK = increment(blending_track_top)
local lastADSState = nil
local lastCrouchState = nil
local function updateADSAnimation(context)
local aim = context:getAimingProgress()
local adsNow = (aim > 0.5)
if adsNow ~= lastADSState then
lastADSState = adsNow
local track = context:getTrack(STATIC_TRACK_LINE, ADS_TRACK)
-- Stop crouch animation to avoid conflict
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, CROUCH_TRACK))
if adsNow then
context:runAnimation("ads_in", track, false, PLAY_ONCE_HOLD, 0.1)
else
context:runAnimation("ads_out", track, false, PLAY_ONCE_HOLD, 0.1)
end
end
end
local function updateCrouchAnimation(context)
local crouchNow = context:isCrouching()
local adsNow = lastADSState -- read from global ADS state
local track = context:getTrack(STATIC_TRACK_LINE, CROUCH_TRACK)
-- 🚫 Don't play any crouch animation while in ADS
if adsNow then
context:stopAnimation(track) -- in case something's still playing
lastCrouchState = crouchNow -- still record the state for later
return
end
-- If crouch state changed and NOT in ADS, play in/out
if crouchNow ~= lastCrouchState then
lastCrouchState = crouchNow
if crouchNow then
context:runAnimation("crouch_in", track, false, PLAY_ONCE_HOLD, 0.1)
else
context:runAnimation("crouch_out", track, false, PLAY_ONCE_HOLD, 0.1)
end
end
-- If crouching and crouch animation is stopped, play idle
if crouchNow and context:isStopped(track) then
context:runAnimation("crouch_idle", track, true, LOOP, 0.1)
end
end
local function ensureAmmoDisplay(context)
local track = context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK)
-- restart the ammo display animation if stopped (e.g., crouch or reload interrupted it)
if context:isStopped(track) then
context:runAnimation("static_ammo_display", track, false, PLAY_ONCE_STOP, 0)
end
-- update progress to match bullet count
local a = context:getAmmoCount()
if (a < 9) then
context:setAnimationProgress(track, 0.1 + (8 - a) * 0.5, false)
else
context:setAnimationProgress(track, 0.1, false)
end
end
-- 播放丢枪动画的方法
local function runPutAwayAnimation(context)
local put_away_time = context:getPutAwayTime()
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 播放 put_away 动画,并且将其过渡时长设为从上下文里传入的 put_away_time * 0.75
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
-- 设定动画进度为最后一帧
context:setAnimationProgress(track, 1, true)
-- 将动画进度向前拨动 {put_away_time}
context:adjustAnimationProgress(track, -put_away_time, false)
end
-- 检查当前是否还有弹药
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)
-- 根据当前整枪内是否还有弹药决定是播放战术换弹还是空枪换弹
if (isNoAmmo(context)) then
context:runAnimation("reload_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("reload_tactical", track, false, PLAY_ONCE_STOP, 0.2)
end
end
-- 播放检视动画的方法
local function runInspectAnimation(context)
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 根据当前整枪内是否还有弹药决定是播放普通检视还是空枪检视
if (isNoAmmo(context)) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
-- 基础轨道上的状态,这个状态用于循环播放 static_idle 动画。
local base_track_state = {}
-- 进入基础状态,直接播放 static_idle
function base_track_state.entry(this, context)
-- 在 主轨道行 的 基础轨道 上循环播放 static_idle
context:runAnimation("static_idle", context:getTrack(STATIC_TRACK_LINE, BASE_TRACK), false, LOOP, 0)
end
-- 空挂部分,该部分到 147 行结束
-- 由于空挂分为"空挂"和"不空挂"两类,因此这里面需要两个状态来调控当前武器
-- 空挂的两个状态之间是会来回切换的,因此每个子状态都需要以下三个方法来操作
-- entry 方法是进入该状态时发生的事,只在进入状态时执行一次
-- update 方法是在该状态时每一渲染帧都会调用的事,注意这里是每一渲染帧,并不是游戏的 tick ,因此这个方法的执行频率远远大于 tick (除非你电脑玩游戏连 20 帧都不到)
-- transition 方法是该状态的转出,只在转换为别的状态时执行一次
--
-- 这部分的一般实现逻辑是
-- entry 负责在进入 update 前播放相关动画并进入 update
-- update 负责实时检测是否需要转出状态,如有需要则触发 transition
-- transition 负责接收 update 传来的信息,在被触发后需要停止相关动画并转去其他状态
-- 这套逻辑在之后的主状态里一样生效
local bolt_caught_states = {
-- normal 是不空挂的正常状态
normal = {},
-- bolt_caught 是空挂时的状态
bolt_caught = {}
}
-- 更新"不空挂"状态
function bolt_caught_states.normal.update(this, context)
-- 如果弹药数量是 0 了,那么立刻手动触发一次转到"空挂"状态的输入
if (isNoAmmo(context)) then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK))
context:trigger(this.INPUT_BOLT_CAUGHT)
else
local a = context:getAmmoCount()
if (a < 9) then
context:setAnimationProgress(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK),0.1+(8-a)*0.5,false)
else
context:setAnimationProgress(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK),0.1,false)
end
end
end
-- 进入"不空挂"状态
function bolt_caught_states.normal.entry(this, context)
context:runAnimation("static_ammo_display", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), false, PLAY_ONCE_STOP, 0)
this.bolt_caught_states.normal.update(this, context)
end
-- 转出"不空挂"状态
function bolt_caught_states.normal.transition(this, context, input)
-- 如果收到了"空挂"的输入,那么直接转到"空挂"状态,"'空挂'的输入"是在上文 update 方法中出现的
if (input == this.INPUT_BOLT_CAUGHT) then
return this.bolt_caught_states.bolt_caught
end
end
-- 进入"空挂"状态
function bolt_caught_states.bolt_caught.entry(this, context)
-- 进入空挂时在主轨道行的空挂轨道播放空挂的动画
context:runAnimation("static_bolt_caught", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), false, LOOP, 0)
end
-- 更新"空挂"状态
function bolt_caught_states.bolt_caught.update(this, context)
-- 如果检测到子弹数不为 0 了(此时是换弹了),那么手动触发一次转到"不空挂"状态的输入
if (not isNoAmmo(context)) then
context:trigger(this.INPUT_BOLT_NORMAL)
end
end
-- 转出"空挂"状态
function bolt_caught_states.bolt_caught.transition(this, context, input)
-- 如果收到了来自上文 update 方法的输入,则转到"不空挂"状态
if (input == this.INPUT_BOLT_NORMAL) then
-- 由于并没有一个"不空挂"的动画,因此必须在这里把空挂动画停止了才能转到"不空挂"状态,否则你会在换完弹之后发现依旧处于空挂状态
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK))
return this.bolt_caught_states.normal
end
end
-- 结束空挂部分
-- 主轨道状态,该部分到 256 行结束
-- 主轨道控制的是武器的基本动作,包括换弹,检视,刺刀攻击,掏枪,丢枪
-- 除了检视,其他动作不需要单独的状态控制。
-- 检视状态需要被射击输入打断,此外,进入检视时,需要隐藏准心,退出检视时恢复准心。
-- 除了上文的那三个方法,检视还需要 exit 方法,用于恢复准心渲染
local main_track_states = {
-- 起始
start = {},
-- 闲置,当玩家把枪拿在手里站定并什么也不做的时候就是这种情况
idle = {},
-- 检视
inspect = {},
-- 结束
final = {},
-- 刺刀攻击的计数器
bayonet_counter = 0
}
-- 转出 start (其实就是掏枪)
function main_track_states.start.transition(this, context, input)
-- 玩家手里拿到枪的那一瞬间会自动输入一个 draw 的信号,不用手动触发
if (input == INPUT_DRAW) then
-- 收到 draw 信号后在主轨道行的主轨道上播放掏枪动画,然后转到闲置态
context:runAnimation("draw", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0)
return this.main_track_states.idle
end
end
-- 转出闲置态
-- 在闲置状态中添加瞄准处理
function main_track_states.idle.transition(this, context, input)
if (input == INPUT_PUT_AWAY) then
runPutAwayAnimation(context)
return this.main_track_states.final
end
if (input == INPUT_RELOAD) then
runReloadAnimation(context)
return this.main_track_states.idle
end
if (input == INPUT_SHOOT) then
context:popShellFrom(0)
return this.main_track_states.idle
end
if (input == INPUT_BOLT) then
context:runAnimation("bolt", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
if (input == INPUT_INSPECT) then
runInspectAnimation(context)
return this.main_track_states.inspect
end
if (input == INPUT_BAYONET_MUZZLE) then
local counter = this.main_track_states.bayonet_counter
local animationName = "melee_bayonet_" .. tostring(counter + 1)
this.main_track_states.bayonet_counter = (counter + 1) % 3
context:runAnimation(animationName, context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
if (input == INPUT_BAYONET_STOCK) then
context:runAnimation("melee_stock", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
if (input == INPUT_BAYONET_PUSH) then
context:runAnimation("melee_push", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
-- 每帧检查并更新瞄准动画
updateADSAnimation(context)
updateCrouchAnimation(context)
ensureAmmoDisplay(context)
end
-- 进入检视态
function main_track_states.inspect.entry(this, context)
-- 检视是需要隐藏屏幕中央准星
context:setShouldHideCrossHair(true)
end
-- 退出检视态
function main_track_states.inspect.exit(this, context)
-- 退出后恢复屏幕中央准星
context:setShouldHideCrossHair(false)
end
-- 更新检视态
function main_track_states.inspect.update(this, context)
-- 当检测到动画停止了(播完了)时手动触发一次退出信号
if (context:isStopped(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK))) then
context:trigger(this.INPUT_INSPECT_RETREAT)
end
end
-- 转出检视态
function main_track_states.inspect.transition(this, context, input)
-- 当收到来自 update 的退出信号时返回到闲置态,此时不需要停止动画是因为在 update 里是动画已经停止了才发出的退出信号
if (input == this.INPUT_INSPECT_RETREAT) then
return this.main_track_states.idle
end
-- 特殊地,射击应当打断检视,当检测到射击输入时应该直接停止动画并返回闲置态
if (input == INPUT_SHOOT) then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK))
return this.main_track_states.idle
end
return this.main_track_states.idle.transition(this, context, input)
end
-- 结束主状态部分
-- 射击态,没什么需要调控的
local gun_kick_state = {}
function gun_kick_state.transition(this, context, input)
-- 玩家按下开火键时需要在射击轨道行里寻找空闲轨道去播放射击动画(如果没有空闲会分配新的),需要注意的是射击动画要向下混合
if (input == INPUT_SHOOT) then
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
-- 这里是混合动画,一般是可叠加的 gun kick
context:runAnimation("shoot", track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
-- 移动轨道的状态,这部分到 435 行结束
local movement_track_states = {
-- 静止不动(或者在天上)
idle = {},
-- 奔跑, -1 是没有奔跑, 0 是在奔跑中
run = {
mode = -1
},
-- 行走, -1 是没有行走, 0 是在空中, 1 是正在瞄准, 2 是在向前走, 3 是向后退, 4 是向侧面走
walk = {
mode = -1
}
}
-- 更新静止态
function movement_track_states.idle.update(this, context)
-- 此处获取的是混合轨道行的移动轨道
local track = context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK)
-- 如果轨道空闲,则播放 idle 动画
-- 注意此处没有写成是在 entry 播放 idle 动画是因为要实时检测轨道是否空闲
if (context:isStopped(track) or context:isHolding(track)) then
context:runAnimation("idle", track, true, LOOP, 0)
end
end
-- 转出静止态
function movement_track_states.idle.transition(this, context, input)
-- 如果玩家在奔跑则转去奔跑态
if (input == INPUT_RUN) then
return this.movement_track_states.run
-- 如果玩家在行走则转去行走态
elseif (input == INPUT_WALK) then
return this.movement_track_states.walk
end
end
-- 进入奔跑态
function movement_track_states.run.entry(this, context)
this.movement_track_states.run.mode = -1
-- 此处播放的轨道是混合轨道行的移动轨道,播放的动画是奔跑的起手式,播放结束后是挂起动画而不是停止
context:runAnimation("run_start", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.2)
end
-- 退出奔跑态
function movement_track_states.run.exit(this, context)
-- 此时播放的动画是奔跑结束回到 idle 的动画,同理播放完后挂起
context:runAnimation("run_end", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.3)
end
-- 更新奔跑态
function movement_track_states.run.update(this, context)
local track = context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK)
local state = this.movement_track_states.run;
-- 等待 run_start 结束,然后循环播放 run ,此处的判断准则是轨道是否挂起,也就是为什么 entry 里播放动画要选 PLAY_ONCE_HOLD 模式
if (context:isHolding(track)) then
context:runAnimation("run", track, true, LOOP, 0.2)
-- 检测是否奔跑的标志位 0
state.mode = 0
context:anchorWalkDist() -- 打 walkDist 锚点,确保 run 动画的起点一致
end
if (state.mode ~= -1) then
if (not context:isOnGround()) then
-- 如果玩家在空中,则播放 run_hold 动画以稳定枪身
if (state.mode ~= 1) then
state.mode = 1
context:runAnimation("run_hold", track, true, LOOP, 0.6)
end
else
-- 如果玩家在地面,则切换回 run 动画
if (state.mode ~= 0) then
state.mode = 0
context:runAnimation("run", track, true, LOOP, 0.2)
end
-- 根据 walkDist 设置 run 动画的进度
context:setAnimationProgress(track, (context:getWalkDist() % 2.0) / 2.0, true)
end
end
end
-- 转出奔跑态
function movement_track_states.run.transition(this, context, input)
-- 收到闲置输入则转去闲置态
if (input == INPUT_IDLE) then
return this.movement_track_states.idle
-- 收到行走输入则转去行走态
elseif (input == INPUT_WALK) then
return this.movement_track_states.walk
end
end
-- 进入行走态
function movement_track_states.walk.entry(this, context)
-- 此时给标志位置为 -1 相当于一个初始化
this.movement_track_states.walk.mode = -1
end
-- 退出行走态
function movement_track_states.walk.exit(this, context)
-- 手动播放一次 idle 动画以打断 walk 动画的循环
context:runAnimation("idle", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.4)
end
-- 更新行走态
function movement_track_states.walk.update(this, context)
-- 此处获取的是混合轨道行的移动轨道
local track = context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK)
-- 这里的 state 代指自身,相当于一个简化写法
local state = this.movement_track_states.walk
if (context:getShootCoolDown() > 0) then
-- 如果刚刚开火,则播放 idle 动画以稳定枪身
if (state.mode ~= 0) then
state.mode = 0
context:runAnimation("idle", track, true, LOOP, 0.3)
end
elseif (not context:isOnGround()) then
-- 如果玩家在空中,则播放 idle 动画以稳定枪身
if (state.mode ~= 0) then
state.mode = 0
context:runAnimation("idle", track, true, LOOP, 0.6)
end
elseif (context:getAimingProgress() > 0.5) then
-- 如果正在喵准,则需要播放 walk_aiming 动画
if (state.mode ~= 1) then
state.mode = 1
context:runAnimation("walk_aiming", track, true, LOOP, 0.3)
end
elseif (context:isInputUp()) then
-- 如果正在向前走,则需要播放 walk_forward 动画
if (state.mode ~= 2) then
state.mode = 2
context:runAnimation("walk_forward", track, true, LOOP, 0.4)
context:anchorWalkDist() -- 打 walkDist 锚点,确保行走动画的起点一致
end
elseif (context:isInputDown()) then
-- 如果正在向后退,则需要播放 walk_backward 动画
if (state.mode ~= 3) then
state.mode = 3
context:runAnimation("walk_backward", track, true, LOOP, 0.4)
context:anchorWalkDist() -- 打 walkDist 锚点,确保行走动画的起点一致
end
elseif (context:isInputLeft() or context:isInputRight()) then
-- 如果正在向侧面,则需要播放 walk_sideway 动画
if (state.mode ~= 4) then
state.mode = 4
context:runAnimation("walk_sideway", track, true, LOOP, 0.4)
context:anchorWalkDist() -- 打 walkDist 锚点,确保行走动画的起点一致
end
end
-- 根据 walkDist 设置行走动画的进度
if (state.mode >= 1 and state.mode <= 4) then
context:setAnimationProgress(track, (context:getWalkDist() % 2.0) / 2.0, true)
end
end
-- 转出行走态,这部分和转出奔跑态是一样的
function movement_track_states.walk.transition(this, context, input)
-- 收到闲置信号则转到闲置态
if (input == INPUT_IDLE) then
return this.movement_track_states.idle
-- 收到奔跑信号则转到奔跑态
elseif (input == INPUT_RUN) then
return this.movement_track_states.run
end
end
-- 结束移动轨道的状态
-- 随机射击动画(HOPE STATE
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
-- 射击态,包含随机射击动画逻辑
local gun_kick_state = {}
function gun_kick_state.transition(this, context, input)
if (input == INPUT_SHOOT) then
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", true)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- 播放丢枪动画的方法
local function runPutAwayAnimation(context)
local put_away_time = context:getPutAwayTime()
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 播放 put_away 动画,并且将其过渡时长设为从上下文里传入的 put_away_time * 0.75
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
-- 设定动画进度为最后一帧
context:setAnimationProgress(track, 1, true)
-- 将动画进度向前拨动 {put_away_time}
context:adjustAnimationProgress(track, -put_away_time, false)
end
-- 检查当前是否还有弹药
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)
-- 根据当前整枪内是否还有弹药决定是播放战术换弹还是空枪换弹
if (isNoAmmo(context)) then
context:runAnimation("reload_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("reload_tactical", track, false, PLAY_ONCE_STOP, 0.2)
end
end
-- 播放检视动画的方法
local function runInspectAnimation(context)
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 根据当前整枪内是否还有弹药决定是播放普通检视还是空枪检视
if (isNoAmmo(context)) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
local M = {
track_line_top = track_line_top,
STATIC_TRACK_LINE = STATIC_TRACK_LINE,
GUN_KICK_TRACK_LINE = GUN_KICK_TRACK_LINE,
BLENDING_TRACK_LINE = BLENDING_TRACK_LINE,
static_track_top = static_track_top,
BASE_TRACK = BASE_TRACK,
BOLT_CAUGHT_TRACK = BOLT_CAUGHT_TRACK,
SAFETY_TRACK = SAFETY_TRACK,
ADS_TRACK = ADS_TRACK,
MAIN_TRACK = MAIN_TRACK,
AMMO_DISPLAY_TRACK = AMMO_DISPLAY_TRACK,
blending_track_top = blending_track_top,
MOVEMENT_TRACK = MOVEMENT_TRACK,
LOOP_TRACK = LOOP_TRACK,
base_track_state = base_track_state,
bolt_caught_states = bolt_caught_states,
main_track_states = main_track_states,
gun_kick_state = gun_kick_state,
movement_track_states = movement_track_states,
INPUT_BOLT_CAUGHT = "bolt_caught",
INPUT_BOLT_NORMAL = "bolt_normal",
INPUT_INSPECT_RETREAT = "inspect_retreat"
}
function M:initialize(context)
context:ensureTrackLineSize(track_line_top.value)
context:ensureTracksAmount(STATIC_TRACK_LINE, static_track_top.value)
context:ensureTracksAmount(BLENDING_TRACK_LINE, blending_track_top.value)
self.movement_track_states.run.mode = -1
self.movement_track_states.walk.mode = -1
end
function M:exit(context)
-- do some cleaning up things
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
}
end
return M
@@ -0,0 +1,69 @@
-- Track line pointers
local track_line_top = {value = 0}
local static_track_top = {value = 0}
local blending_track_top = {value = 0}
local function increment(obj)
obj.value = obj.value + 1
return obj.value - 1
end
-- Track line and track assignments
local STATIC_TRACK_LINE = increment(track_line_top)
local BASE_TRACK = increment(static_track_top)
local BOLT_CAUGHT_TRACK = increment(static_track_top)
local SAFETY_TRACK = increment(static_track_top)
local ADS_TRACK = increment(static_track_top)
local MAIN_TRACK = increment(static_track_top)
local AMMO_DISPLAY_TRACK = increment(static_track_top)
local GUN_KICK_TRACK_LINE = increment(track_line_top)
local BLENDING_TRACK_LINE = increment(track_line_top)
local MOVEMENT_TRACK = increment(blending_track_top)
local LOOP_TRACK = increment(blending_track_top)
-- ADS state handling
local lastADSState = nil
local function updateADSAnimation(context)
local aim = context:getAimingProgress()
local adsNow = (aim > 0.5)
if adsNow ~= lastADSState then
lastADSState = adsNow
local track = context:getTrack(STATIC_TRACK_LINE, ADS_TRACK)
if adsNow then
context:runAnimation("ads_in", track, false, PLAY_ONCE_HOLD, 0.1)
else
context:runAnimation("ads_out", track, false, PLAY_ONCE_HOLD, 0.1)
end
end
end
-- Export
local M = {
track_line_top = track_line_top,
STATIC_TRACK_LINE = STATIC_TRACK_LINE,
GUN_KICK_TRACK_LINE = GUN_KICK_TRACK_LINE,
BLENDING_TRACK_LINE = BLENDING_TRACK_LINE,
static_track_top = static_track_top,
BASE_TRACK = BASE_TRACK,
BOLT_CAUGHT_TRACK = BOLT_CAUGHT_TRACK,
SAFETY_TRACK = SAFETY_TRACK,
ADS_TRACK = ADS_TRACK,
MAIN_TRACK = MAIN_TRACK,
AMMO_DISPLAY_TRACK = AMMO_DISPLAY_TRACK,
blending_track_top = blending_track_top,
MOVEMENT_TRACK = MOVEMENT_TRACK,
LOOP_TRACK = LOOP_TRACK,
updateADSAnimation = updateADSAnimation
}
function M:initialize(context)
context:ensureTrackLineSize(track_line_top.value)
context:ensureTracksAmount(STATIC_TRACK_LINE, static_track_top.value)
context:ensureTracksAmount(BLENDING_TRACK_LINE, blending_track_top.value)
end
return M
@@ -0,0 +1,583 @@
-- 这些栈顶指针在分配新的轨道行和轨道时起作用
-- 轨道行 栈顶指针
local track_line_top = {value = 0}
-- 主轨道行 的 轨道 栈顶指针
local static_track_top = {value = 0}
-- 混合轨道行 的 轨道 栈顶指针
local blending_track_top = {value = 0}
-- 栈顶指针自增函数,用于分配新的轨道行或者轨道
local function increment(obj)
obj.value = obj.value + 1
return obj.value - 1
end
-- 主轨道行 和 其中的六条轨道(新增 AMMO_DISPLAY_TRACK
local STATIC_TRACK_LINE = increment(track_line_top)
local BASE_TRACK = increment(static_track_top)
local BOLT_CAUGHT_TRACK = increment(static_track_top)
local SAFETY_TRACK = increment(static_track_top) -- 待实现
local ADS_TRACK = increment(static_track_top) -- 待实现
local MAIN_TRACK = increment(static_track_top)
-- 开火的轨道行
local GUN_KICK_TRACK_LINE = increment(track_line_top)
-- 混合轨道行 和 其中的两条轨道,用于叠加动画,如跑步走路跳跃, LOOP_TRACK 只有定义却尚未启用,因此作用尚不得知
local BLENDING_TRACK_LINE = increment(track_line_top)
local MOVEMENT_TRACK = increment(blending_track_top)
local LOOP_TRACK = increment(blending_track_top)
-- 播放丢枪动画的方法
local function runPutAwayAnimation(context)
local put_away_time = context:getPutAwayTime()
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 播放 put_away 动画,并且将其过渡时长设为从上下文里传入的 put_away_time * 0.75
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
-- 设定动画进度为最后一帧
context:setAnimationProgress(track, 1, true)
-- 将动画进度向前拨动 {put_away_time}
context:adjustAnimationProgress(track, -put_away_time, false)
end
-- 检查当前是否还有弹药
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)
-- 根据当前整枪内是否还有弹药决定是播放战术换弹还是空枪换弹
if (isNoAmmo(context)) then
context:runAnimation("reload_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("reload_tactical", track, false, PLAY_ONCE_STOP, 0.2)
end
end
-- 播放检视动画的方法
local function runInspectAnimation(context)
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 根据当前整枪内是否还有弹药决定是播放普通检视还是空枪检视
if (isNoAmmo(context)) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
-- 基础轨道上的状态,这个状态用于循环播放 static_idle 动画。
local base_track_state = {}
-- 进入基础状态,直接播放 static_idle
function base_track_state.entry(this, context)
-- 在 主轨道行 的 基础轨道 上循环播放 static_idle
context:runAnimation("static_idle", context:getTrack(STATIC_TRACK_LINE, BASE_TRACK), false, LOOP, 0)
end
-- 空挂部分,该部分到 147 行结束
-- 由于空挂分为"空挂"和"不空挂"两类,因此这里面需要两个状态来调控当前武器
-- 空挂的两个状态之间是会来回切换的,因此每个子状态都需要以下三个方法来操作
-- entry 方法是进入该状态时发生的事,只在进入状态时执行一次
-- update 方法是在该状态时每一渲染帧都会调用的事,注意这里是每一渲染帧,并不是游戏的 tick ,因此这个方法的执行频率远远大于 tick (除非你电脑玩游戏连 20 帧都不到)
-- transition 方法是该状态的转出,只在转换为别的状态时执行一次
--
-- 这部分的一般实现逻辑是
-- entry 负责在进入 update 前播放相关动画并进入 update
-- update 负责实时检测是否需要转出状态,如有需要则触发 transition
-- transition 负责接收 update 传来的信息,在被触发后需要停止相关动画并转去其他状态
-- 这套逻辑在之后的主状态里一样生效
local bolt_caught_states = {
-- normal 是不空挂的正常状态
normal = {},
-- bolt_caught 是空挂时的状态
bolt_caught = {}
}
-- 更新"不空挂"状态
function bolt_caught_states.normal.update(this, context)
-- 如果弹药数量是 0 了,那么立刻手动触发一次转到"空挂"状态的输入
if (isNoAmmo(context)) then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK))
context:trigger(this.INPUT_BOLT_CAUGHT)
else
local a = context:getAmmoCount()
if (a < 9) then
context:setAnimationProgress(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK),0.1+(8-a)*0.5,false)
else
context:setAnimationProgress(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK),0.1,false)
end
end
end
-- 进入"不空挂"状态
function bolt_caught_states.normal.entry(this, context)
context:runAnimation("static_ammo_display", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), false, PLAY_ONCE_STOP, 0)
this.bolt_caught_states.normal.update(this, context)
end
-- 转出"不空挂"状态
function bolt_caught_states.normal.transition(this, context, input)
-- 如果收到了"空挂"的输入,那么直接转到"空挂"状态,"'空挂'的输入"是在上文 update 方法中出现的
if (input == this.INPUT_BOLT_CAUGHT) then
return this.bolt_caught_states.bolt_caught
end
end
-- 进入"空挂"状态
function bolt_caught_states.bolt_caught.entry(this, context)
-- 进入空挂时在主轨道行的空挂轨道播放空挂的动画
context:runAnimation("static_bolt_caught", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), false, LOOP, 0)
end
-- 更新"空挂"状态
function bolt_caught_states.bolt_caught.update(this, context)
-- 如果检测到子弹数不为 0 了(此时是换弹了),那么手动触发一次转到"不空挂"状态的输入
if (not isNoAmmo(context)) then
context:trigger(this.INPUT_BOLT_NORMAL)
end
end
-- 转出"空挂"状态
function bolt_caught_states.bolt_caught.transition(this, context, input)
-- 如果收到了来自上文 update 方法的输入,则转到"不空挂"状态
if (input == this.INPUT_BOLT_NORMAL) then
-- 由于并没有一个"不空挂"的动画,因此必须在这里把空挂动画停止了才能转到"不空挂"状态,否则你会在换完弹之后发现依旧处于空挂状态
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK))
return this.bolt_caught_states.normal
end
end
-- 结束空挂部分
-- 主轨道状态,该部分到 256 行结束
-- 主轨道控制的是武器的基本动作,包括换弹,检视,刺刀攻击,掏枪,丢枪
-- 除了检视,其他动作不需要单独的状态控制。
-- 检视状态需要被射击输入打断,此外,进入检视时,需要隐藏准心,退出检视时恢复准心。
-- 除了上文的那三个方法,检视还需要 exit 方法,用于恢复准心渲染
local main_track_states = {
-- 起始
start = {},
-- 闲置,当玩家把枪拿在手里站定并什么也不做的时候就是这种情况
idle = {},
-- 检视
inspect = {},
-- 结束
final = {},
-- 刺刀攻击的计数器
bayonet_counter = 0
}
-- 转出 start (其实就是掏枪)
function main_track_states.start.transition(this, context, input)
-- 玩家手里拿到枪的那一瞬间会自动输入一个 draw 的信号,不用手动触发
if (input == INPUT_DRAW) then
-- 收到 draw 信号后在主轨道行的主轨道上播放掏枪动画,然后转到闲置态
context:runAnimation("draw", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0)
return this.main_track_states.idle
end
end
-- 转出闲置态
function main_track_states.idle.transition(this, context, input)
-- 玩家从枪切到其他物品的时候会自动输入丢枪的信号,不用手动触发,只要检测就好了
if (input == INPUT_PUT_AWAY) then
runPutAwayAnimation(context)
-- 丢枪后转到最终态
return this.main_track_states.final
end
-- 玩家拿着枪按下 R (或者别的什么自己绑定的换弹键)时会自动输入换弹信号
if (input == INPUT_RELOAD) then
runReloadAnimation(context)
-- 换弹动画播放完后返回闲置态(也就是返回自己)
return this.main_track_states.idle
end
-- 玩家在射击时会自动输入 shoot 信号
if (input == INPUT_SHOOT) then
context:popShellFrom(0) -- 默认射击抛壳
-- 返回闲置态(也就是返回自己),这里不播放射击动画是因为射击动画应该在 gun_kick 状态里播
return this.main_track_states.idle
end
-- 玩家在使用栓动武器射击完成后拉栓会自动输入 bolt 信号
if (input == INPUT_BOLT) then
context:runAnimation("bolt", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
-- 拉栓动画播放完后返回闲置态
return this.main_track_states.idle
end
-- 玩家按下检视键后会输入检视信号
if (input == INPUT_INSPECT) then
runInspectAnimation(context)
-- 检视需要转到检视态,因为检视过程中屏幕中央准星是隐藏的,因此需要一个检视态来调控准星
return this.main_track_states.inspect
end
-- 玩家使用近战武器时输入的近战信号,分为近战配件、枪托、推击这三种情况
-- 近战配件可以使用多种近战动画,而枪托和推击则是在枪配置文件里写的"无近战配件时的近战攻击",只能使用一个动画
if (input == INPUT_BAYONET_MUZZLE) then
-- 这里是一个顺序播放动画的方法,通过存储在状态里的 counter 决定当前播放的是第几个近战动画, animationName 是一个组合起来的字符串
-- 这样写法会使依次运行 "melee_bayonet_1" "melee_bayonet_2" "melee_bayonet_3" 这三个动画, 3 运行完后再近战则会返回 1
local counter = this.main_track_states.bayonet_counter
local animationName = "melee_bayonet_" .. tostring(counter + 1)
this.main_track_states.bayonet_counter = (counter + 1) % 3
context:runAnimation(animationName, context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
-- 枪托肘完之后返回闲置态
if (input == INPUT_BAYONET_STOCK) then
context:runAnimation("melee_stock", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
-- 推击完之后返回闲置态
if (input == INPUT_BAYONET_PUSH) then
context:runAnimation("melee_push", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
end
-- 进入检视态
function main_track_states.inspect.entry(this, context)
-- 检视是需要隐藏屏幕中央准星
context:setShouldHideCrossHair(true)
end
-- 退出检视态
function main_track_states.inspect.exit(this, context)
-- 退出后恢复屏幕中央准星
context:setShouldHideCrossHair(false)
end
-- 更新检视态
function main_track_states.inspect.update(this, context)
-- 当检测到动画停止了(播完了)时手动触发一次退出信号
if (context:isStopped(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK))) then
context:trigger(this.INPUT_INSPECT_RETREAT)
end
end
-- 转出检视态
function main_track_states.inspect.transition(this, context, input)
-- 当收到来自 update 的退出信号时返回到闲置态,此时不需要停止动画是因为在 update 里是动画已经停止了才发出的退出信号
if (input == this.INPUT_INSPECT_RETREAT) then
return this.main_track_states.idle
end
-- 特殊地,射击应当打断检视,当检测到射击输入时应该直接停止动画并返回闲置态
if (input == INPUT_SHOOT) then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK))
return this.main_track_states.idle
end
return this.main_track_states.idle.transition(this, context, input)
end
-- 结束主状态部分
-- 射击态,没什么需要调控的
local gun_kick_state = {}
function gun_kick_state.transition(this, context, input)
-- 玩家按下开火键时需要在射击轨道行里寻找空闲轨道去播放射击动画(如果没有空闲会分配新的),需要注意的是射击动画要向下混合
if (input == INPUT_SHOOT) then
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
-- 这里是混合动画,一般是可叠加的 gun kick
context:runAnimation("shoot", track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
-- 移动轨道的状态,这部分到 435 行结束
local movement_track_states = {
-- 静止不动(或者在天上)
idle = {},
-- 奔跑, -1 是没有奔跑, 0 是在奔跑中
run = {
mode = -1
},
-- 行走, -1 是没有行走, 0 是在空中, 1 是正在瞄准, 2 是在向前走, 3 是向后退, 4 是向侧面走
walk = {
mode = -1
}
}
-- 更新静止态
function movement_track_states.idle.update(this, context)
-- 此处获取的是混合轨道行的移动轨道
local track = context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK)
-- 如果轨道空闲,则播放 idle 动画
-- 注意此处没有写成是在 entry 播放 idle 动画是因为要实时检测轨道是否空闲
if (context:isStopped(track) or context:isHolding(track)) then
context:runAnimation("idle", track, true, LOOP, 0)
end
end
-- 转出静止态
function movement_track_states.idle.transition(this, context, input)
-- 如果玩家在奔跑则转去奔跑态
if (input == INPUT_RUN) then
return this.movement_track_states.run
-- 如果玩家在行走则转去行走态
elseif (input == INPUT_WALK) then
return this.movement_track_states.walk
end
end
-- 进入奔跑态
function movement_track_states.run.entry(this, context)
this.movement_track_states.run.mode = -1
-- 此处播放的轨道是混合轨道行的移动轨道,播放的动画是奔跑的起手式,播放结束后是挂起动画而不是停止
context:runAnimation("run_start", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.2)
end
-- 退出奔跑态
function movement_track_states.run.exit(this, context)
-- 此时播放的动画是奔跑结束回到 idle 的动画,同理播放完后挂起
context:runAnimation("run_end", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.3)
end
-- 更新奔跑态
function movement_track_states.run.update(this, context)
local track = context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK)
local state = this.movement_track_states.run;
-- 等待 run_start 结束,然后循环播放 run ,此处的判断准则是轨道是否挂起,也就是为什么 entry 里播放动画要选 PLAY_ONCE_HOLD 模式
if (context:isHolding(track)) then
context:runAnimation("run", track, true, LOOP, 0.2)
-- 检测是否奔跑的标志位 0
state.mode = 0
context:anchorWalkDist() -- 打 walkDist 锚点,确保 run 动画的起点一致
end
if (state.mode ~= -1) then
if (not context:isOnGround()) then
-- 如果玩家在空中,则播放 run_hold 动画以稳定枪身
if (state.mode ~= 1) then
state.mode = 1
context:runAnimation("run_hold", track, true, LOOP, 0.6)
end
else
-- 如果玩家在地面,则切换回 run 动画
if (state.mode ~= 0) then
state.mode = 0
context:runAnimation("run", track, true, LOOP, 0.2)
end
-- 根据 walkDist 设置 run 动画的进度
context:setAnimationProgress(track, (context:getWalkDist() % 2.0) / 2.0, true)
end
end
end
-- 转出奔跑态
function movement_track_states.run.transition(this, context, input)
-- 收到闲置输入则转去闲置态
if (input == INPUT_IDLE) then
return this.movement_track_states.idle
-- 收到行走输入则转去行走态
elseif (input == INPUT_WALK) then
return this.movement_track_states.walk
end
end
-- 进入行走态
function movement_track_states.walk.entry(this, context)
-- 此时给标志位置为 -1 相当于一个初始化
this.movement_track_states.walk.mode = -1
end
-- 退出行走态
function movement_track_states.walk.exit(this, context)
-- 手动播放一次 idle 动画以打断 walk 动画的循环
context:runAnimation("idle", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.4)
end
-- 更新行走态
function movement_track_states.walk.update(this, context)
-- 此处获取的是混合轨道行的移动轨道
local track = context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK)
-- 这里的 state 代指自身,相当于一个简化写法
local state = this.movement_track_states.walk
if (context:getShootCoolDown() > 0) then
-- 如果刚刚开火,则播放 idle 动画以稳定枪身
if (state.mode ~= 0) then
state.mode = 0
context:runAnimation("idle", track, true, LOOP, 0.3)
end
elseif (not context:isOnGround()) then
-- 如果玩家在空中,则播放 idle 动画以稳定枪身
if (state.mode ~= 0) then
state.mode = 0
context:runAnimation("idle", track, true, LOOP, 0.6)
end
elseif (context:getAimingProgress() > 0.5) then
-- 如果正在喵准,则需要播放 walk_aiming 动画
if (state.mode ~= 1) then
state.mode = 1
context:runAnimation("walk_aiming", track, true, LOOP, 0.3)
end
elseif (context:isInputUp()) then
-- 如果正在向前走,则需要播放 walk_forward 动画
if (state.mode ~= 2) then
state.mode = 2
context:runAnimation("walk_forward", track, true, LOOP, 0.4)
context:anchorWalkDist() -- 打 walkDist 锚点,确保行走动画的起点一致
end
elseif (context:isInputDown()) then
-- 如果正在向后退,则需要播放 walk_backward 动画
if (state.mode ~= 3) then
state.mode = 3
context:runAnimation("walk_backward", track, true, LOOP, 0.4)
context:anchorWalkDist() -- 打 walkDist 锚点,确保行走动画的起点一致
end
elseif (context:isInputLeft() or context:isInputRight()) then
-- 如果正在向侧面,则需要播放 walk_sideway 动画
if (state.mode ~= 4) then
state.mode = 4
context:runAnimation("walk_sideway", track, true, LOOP, 0.4)
context:anchorWalkDist() -- 打 walkDist 锚点,确保行走动画的起点一致
end
end
-- 根据 walkDist 设置行走动画的进度
if (state.mode >= 1 and state.mode <= 4) then
context:setAnimationProgress(track, (context:getWalkDist() % 2.0) / 2.0, true)
end
end
-- 转出行走态,这部分和转出奔跑态是一样的
function movement_track_states.walk.transition(this, context, input)
-- 收到闲置信号则转到闲置态
if (input == INPUT_IDLE) then
return this.movement_track_states.idle
-- 收到奔跑信号则转到奔跑态
elseif (input == INPUT_RUN) then
return this.movement_track_states.run
end
end
-- 结束移动轨道的状态
-- 随机射击动画(HOPE STATE
local shoot_animations = {
"shoot_1", "shoot_2"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
-- 射击态,包含随机射击动画逻辑
local gun_kick_state = {}
function gun_kick_state.transition(this, context, input)
if (input == INPUT_SHOOT) then
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", true)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- 播放丢枪动画的方法
local function runPutAwayAnimation(context)
local put_away_time = context:getPutAwayTime()
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 播放 put_away 动画,并且将其过渡时长设为从上下文里传入的 put_away_time * 0.75
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
-- 设定动画进度为最后一帧
context:setAnimationProgress(track, 1, true)
-- 将动画进度向前拨动 {put_away_time}
context:adjustAnimationProgress(track, -put_away_time, false)
end
-- 检查当前是否还有弹药
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)
-- 根据当前整枪内是否还有弹药决定是播放战术换弹还是空枪换弹
if (isNoAmmo(context)) then
context:runAnimation("reload_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("reload_tactical", track, false, PLAY_ONCE_STOP, 0.2)
end
end
-- 播放检视动画的方法
local function runInspectAnimation(context)
-- 此处获取的轨道是位于主轨道行上的主轨道
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- 根据当前整枪内是否还有弹药决定是播放普通检视还是空枪检视
if (isNoAmmo(context)) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
local M = {
-- 轨道行
track_line_top = track_line_top,
STATIC_TRACK_LINE = STATIC_TRACK_LINE,
GUN_KICK_TRACK_LINE = GUN_KICK_TRACK_LINE,
BLENDING_TRACK_LINE = BLENDING_TRACK_LINE,
-- 静态轨道
static_track_top = static_track_top,
BASE_TRACK = BASE_TRACK,
BOLT_CAUGHT_TRACK = BOLT_CAUGHT_TRACK,
SAFETY_TRACK = SAFETY_TRACK,
ADS_TRACK = ADS_TRACK,
MAIN_TRACK = MAIN_TRACK,
-- 混合轨道
blending_track_top = blending_track_top,
MOVEMENT_TRACK = MOVEMENT_TRACK,
LOOP_TRACK = LOOP_TRACK,
-- 状态
base_track_state = base_track_state,
bolt_caught_states = bolt_caught_states,
main_track_states = main_track_states,
gun_kick_state = gun_kick_state,
movement_track_states = movement_track_states,
-- 输入
INPUT_BOLT_CAUGHT = "bolt_caught",
INPUT_BOLT_NORMAL = "bolt_normal",
INPUT_INSPECT_RETREAT = "inspect_retreat"
}
-- 状态机初始化函数,在切枪的时候调用
function M:initialize(context)
context:ensureTrackLineSize(track_line_top.value)
context:ensureTracksAmount(STATIC_TRACK_LINE, static_track_top.value)
context:ensureTracksAmount(BLENDING_TRACK_LINE, blending_track_top.value)
self.movement_track_states.run.mode = -1
self.movement_track_states.walk.mode = -1
end
-- 状态机退出函数,在收枪的时候调用
function M:exit(context)
-- do some cleaning up things
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
}
end
return M
@@ -0,0 +1,63 @@
-- 混合轨道行 的 轨道,用于过热动画
local OVER_HEAT_TRACK = increment(blending_track_top)
local OVER_HEATING_TRACK = increment(blending_track_top)
-- 检查当前是否处于过热状态
local function isOverHeat(context)
return context:isOverHeat()
end
-- 过热部分
-- 过热部分的内容完全参照空挂部分
local over_heat_states = {
-- normal 是不过热的正常状态
normal = {},
-- over_heat 是过热时的状态
over_heat = {}
}
-- 更新"不过热"状态
function over_heat_states.normal.update(this, context)
-- 如果进入过热状态,则触发 INPUT_OVER_HEAT
if (isOverHeat(context)) then
context:trigger(this.INPUT_OVER_HEAT)
end
end
-- 进入"不过热"状态
function over_heat_states.normal.entry(this, context)
-- 直接进入 update 检查是否要切进过热状态
this.over_heat_states.normal.update(this, context)
end
-- 转出"不过热"状态
function over_heat_states.normal.transition(this, context, input)
-- 收到过热信号则进入过热状态
if (input == this.INPUT_OVER_HEAT) then
return this.over_heat_states.over_heat
end
end
-- 进入"过热"状态
function over_heat_states.over_heat.entry(this, context)
-- 在混合轨道播放过热动画
context:runAnimation("over_heat", context:getTrack(BLENDING_TRACK_LINE, OVER_HEAT_TRACK), false, LOOP, 0)
end
-- 更新"过热"状态
function over_heat_states.over_heat.update(this, context)
-- 如果热量下降,不再过热,触发 INPUT_OVER_HEAT_NORMAL
if (not isOverHeat(context)) then
context:trigger(this.INPUT_OVER_HEAT_NORMAL)
end
end
-- 转出"过热"状态
function over_heat_states.over_heat.transition(this, context, input)
-- 收到"恢复正常"信号
if (input == this.INPUT_OVER_HEAT_NORMAL) then
-- 停止过热动画
context:stopAnimation(context:getTrack(BLENDING_TRACK_LINE, OVER_HEAT_TRACK))
return this.over_heat_states.normal
end
end
@@ -0,0 +1,264 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 }),
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
@@ -0,0 +1,251 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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 == 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 == 3) then
context:runAnimation("inspect_xmag_3", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1 or ext == 2) then
context:runAnimation("inspect_xmag_12", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
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
-- HOPE STATE
local shoot_animations = {
"shoot_1", "shoot_2" , "shoot_3", "shoot_4", "shoot_5", "shoot_6"
}
local shoot_animation_index = 1
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- HOPE STATE END
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
idle = idle_state,
main_track_states = setmetatable({
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
@@ -0,0 +1,69 @@
local default = require("tacz_default_state_machine")
local GUN_KICK_TRACK_LINE = default.GUN_KICK_TRACK_LINE
local gun_kick_state = default.gun_kick_state
local main_track_states = default.main_track_states
local idle_state = setmetatable({}, {__index = main_track_states.idle})
-- Shoot animations
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
-- ===========================================================================================================================================================================
-- Idle state bolt catch after last shot
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- ===========================================================================================================================================================================
-- Random shooting state
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
-- Return module
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
idle = idle_state
}, { __index = default })
return M
@@ -0,0 +1,82 @@
local default = require("tacz_default_state_machine")
local GUN_KICK_TRACK_LINE = default.GUN_KICK_TRACK_LINE
local STATIC_TRACK_LINE = default.STATIC_TRACK_LINE
local BOLT_CAUGHT_TRACK = default.BOLT_CAUGHT_TRACK
local gun_kick_state = default.gun_kick_state
local main_track_states = default.main_track_states
local idle_state = setmetatable({}, {__index = main_track_states.idle})
-- === ANIMATION LISTS ===
local shoot_animations_main = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8"
}
local shoot_animations_secondary = {
"shoot2_1", "shoot2_2", "shoot2_3", "shoot2_4",
"shoot2_5", "shoot2_6", "shoot2_7", "shoot2_8"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
-- === RANDOM SELECTORS ===
local function getRandomMainShootAnim()
return shoot_animations_main[math.random(#shoot_animations_main)]
end
local function getRandomSecondaryShootAnim()
return shoot_animations_secondary[math.random(#shoot_animations_secondary)]
end
-- === SHOOT STATE ===
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 mainTrack = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
local secondaryTrack = context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK)
if context:getAmmoCount() == 0 then
-- LAST SHOT (plays bolt caught + sound)
context:runAnimation(last_shoot_animation, mainTrack, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", secondaryTrack, true, PLAY_ONCE_HOLD, 0)
else
-- NORMAL SHOT: play 2 animations at once
local animMain = getRandomMainShootAnim()
local animSecondary = getRandomSecondaryShootAnim()
context:runAnimation(animMain, mainTrack, true, PLAY_ONCE_STOP, 0)
context:runAnimation(animSecondary, secondaryTrack, true, PLAY_ONCE_STOP, 0)
end
return nil
end
-- === IDLE STATE (bolt catch) ===
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- === RETURN MODULE ===
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
idle = idle_state
}, { __index = default })
return M
@@ -0,0 +1,296 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- "ext"表示扩容插件等级,"0"代表不装扩容插件
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
-- "ext"表示扩容插件等级,"0"代表不装扩容插件
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
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))
-- 播放 put_away 动画,并且将其过渡时长设为从上下文里传入的 put_away_time * 0.75
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
-- 设定动画进度为最后一帧
context:setAnimationProgress(track, 1, true)
-- 将动画进度向前拨动 {put_away_time}
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
-- 此处为开火模式轨道的状态,专门为快慢机动画服务,兼容其他动作,可按需求添加三种射击模式(semi、burst、auto)
local fire_mode_state = {
-- 半自动状态
semi = {},
-- 全自动状态
auto = {},
-- 掏枪状态
draw = {}
}
-- 这一块专门用来检测枪械在掏枪(播放draw)时枪械处于什么射击模式,并切换到对应模式的idle
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
-- 注意!后面关于每个开火模式对应状态之间的切换,需要按照data里填写的顺序进行转换
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)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(input == INPUT_RELOAD)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(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)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(input == INPUT_RELOAD)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(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
-- HOPE STATE
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3"
}
local shoot_animation_index = 1
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
local anim = shoot_animations[shoot_animation_index]
shoot_animation_index = (shoot_animation_index % #shoot_animations) + 1
return anim
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", true)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- HOPE STATE END
-- 用元表的方式继承默认状态机的属性
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
idle = idle_state,
main_track_states = setmetatable({
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
@@ -0,0 +1,327 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- "ext"表示扩容插件等级,"0"代表不装扩容插件
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
-- "ext"表示扩容插件等级,"0"代表不装扩容插件
if (ext == 0) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1) then
context:runAnimation("inspect_empty_xmag_12", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 2) then
context:runAnimation("inspect_empty_xmag_12", 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", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 2) then
context:runAnimation("inspect_xmag", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 3) then
context:runAnimation("inspect_xmag", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
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))
-- 播放 put_away 动画,并且将其过渡时长设为从上下文里传入的 put_away_time * 0.75
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
-- 设定动画进度为最后一帧
context:setAnimationProgress(track, 1, true)
-- 将动画进度向前拨动 {put_away_time}
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
-- 此处为开火模式轨道的状态,专门为快慢机动画服务,兼容其他动作,可按需求添加三种射击模式(semi、burst、auto)
local fire_mode_state = {
-- 半自动状态
semi = {},
-- 连射状态
burst = {},
-- 全自动状态
auto = {},
-- 掏枪状态
draw = {}
}
-- 这一块专门用来检测枪械在掏枪(播放draw)时枪械处于什么射击模式,并切换到对应模式的idle
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() == BURST) then
context:runAnimation("static_burst", context:getTrack(STATIC_TRACK_LINE, FIRE_MODE_TRACK), true, PLAY_ONCE_HOLD, 0)
return fire_mode_state.burst
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
-- 注意!后面关于每个开火模式对应状态之间的切换,需要按照data里填写的顺序进行转换
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)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(input == INPUT_RELOAD)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(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() == BURST) then
context:trigger(this.INPUT_MODE_BURST)
end
end
function fire_mode_state.auto.transition(this, context,input)
if(input == this.INPUT_MODE_BURST)then
context:runAnimation("switch_burst", context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK), false, PLAY_ONCE_STOP, 0)
return fire_mode_state.burst
end
if(input == INPUT_SHOOT)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(input == INPUT_RELOAD)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(input == INPUT_INSPECT )then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
end
function fire_mode_state.burst.update(this, context)
local track = context:getTrack(STATIC_TRACK_LINE, FIRE_MODE_TRACK)
if (context:isHolding(track)) then
context:runAnimation("static_burst", track, true, PLAY_ONCE_HOLD, 0)
end
if (context:getFireMode() == SEMI) then
context:trigger(this.INPUT_MODE_SEMI)
end
end
function fire_mode_state.burst.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)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(input == INPUT_RELOAD)then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, SWITCH_MODE_TRACK))
end
if(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
-- HOPE STATE
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8", "shoot_9"
}
local shoot_animation_index = 1
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- HOPE STATE END
-- 用元表的方式继承默认状态机的属性
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
idle = idle_state,
main_track_states = setmetatable({
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
@@ -0,0 +1,223 @@
-- 脚本位置 "{命名空间}:{路径}"require 格式 "{命名空间}_{路径}"
local default = require("tacz_manual_action_state_machine")
local STATIC_TRACK_LINE = default.STATIC_TRACK_LINE
local MAIN_TRACK = default.MAIN_TRACK
local main_track_states = default.main_track_states
-- override idle state
local idle_state = setmetatable({}, {__index = main_track_states.idle})
-- custom reload state
local reload_state = {
need_ammo = 0,
loaded_ammo = 0
}
-- override start state so we control the draw animation
local start_state = setmetatable({}, { __index = main_track_states.start })
local end_state = setmetatable({}, { __index = main_track_states["end"] })
function end_state.transition(this, context, input)
if input == INPUT_PUT_AWAY then
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
local ammo = context:getAmmoCount() or 0
local hasBarrel = context:hasBulletInBarrel()
if ammo <= 0 and not hasBarrel then
context:runAnimation("put_away_empty", track, false, PLAY_ONCE_STOP, 0.15)
else
context:runAnimation("put_away", track, false, PLAY_ONCE_STOP, 0.15)
end
return nil
end
if main_track_states["end"] and main_track_states["end"].transition then
return main_track_states["end"].transition(this, context, input)
end
return nil
end
-------------------------------------------------------
-- UTILITY
-------------------------------------------------------
local function get_ejection_time(context)
local ejection_time = context:getStateMachineParams().intro_shell_ejecting_time
if (ejection_time) then
ejection_time = ejection_time * 1000
else
ejection_time = 0
end
return ejection_time
end
-------------------------------------------------------
-- INSPECT ANIM
-------------------------------------------------------
local function runInspectAnimation(context)
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
local ammo = context:getAmmoCount()
local hasBarrel = context:hasBulletInBarrel()
if (ammo <= 0 and not hasBarrel) then
context:runAnimation("inspect_empty", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ammo <= 0) then
context:runAnimation("inspect_01", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
-------------------------------------------------------
-- START (DRAW) OVERRIDE — plays draw_empty when ammo == 0
-------------------------------------------------------
function start_state.transition(this, context, input)
-- when weapon is drawn the state machine sends INPUT_DRAW
if input == INPUT_DRAW then
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
local ammo = context:getAmmoCount() or 0
-- check chambered bullet too if you want fully empty detection:
local hasBarrel = context:hasBulletInBarrel()
if ammo <= 0 and not hasBarrel then
-- completely empty: draw_empty
context:runAnimation("draw_empty", track, false, PLAY_ONCE_STOP, 0.15)
else
-- has ammo somewhere: normal draw
context:runAnimation("draw", track, false, PLAY_ONCE_STOP, 0.15)
end
-- after draw, go to idle
return this.main_track_states.idle
end
-- any other inputs: delegate to default start transition
if main_track_states.start and main_track_states.start.transition then
return main_track_states.start.transition(this, context, input)
end
return nil
end
-------------------------------------------------------
-- IDLE TRANSITION OVERRIDE (reload + inspect)
-------------------------------------------------------
function idle_state.transition(this, context, input)
if input == INPUT_PUT_AWAY then
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
local ammo = context:getAmmoCount() or 0
local hasBarrel = context:hasBulletInBarrel()
if ammo <= 0 and not hasBarrel then
context:runAnimation("put_away_empty", track, false, PLAY_ONCE_HOLD, 0.15)
else
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, 0.15)
end
-- let default machine go to end state
return this.main_track_states["end"]
end
if input == INPUT_RELOAD then
return this.main_track_states.reload
end
if input == INPUT_INSPECT then
runInspectAnimation(context)
return this.main_track_states.inspect
end
return main_track_states.idle.transition(this, context, input)
end
-------------------------------------------------------
-- RELOAD ENTRY
-------------------------------------------------------
function reload_state.entry(this, context)
local state = this.main_track_states.reload
local ammo = context:getAmmoCount() or 0
local hasBarrel = context:hasBulletInBarrel()
local empty = (ammo <= 0 and not hasBarrel)
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
if empty then
state.timestamp = context:getCurrentTimestamp()
state.ejection_time = get_ejection_time(context)
context:runAnimation("reload_intro_empty", track, false, PLAY_ONCE_HOLD, 0.2)
else
state.timestamp = -1
state.ejection_time = 0
context:runAnimation("reload_intro", track, false, PLAY_ONCE_HOLD, 0.2)
end
state.need_ammo = context:getMaxAmmoCount() - context:getAmmoCount()
state.loaded_ammo = 0
end
-------------------------------------------------------
-- RELOAD UPDATE
-------------------------------------------------------
function reload_state.update(this, context)
local state = this.main_track_states.reload
-- empty intro shell eject
if (state.timestamp ~= -1 and context:getCurrentTimestamp() - state.timestamp > state.ejection_time) then
context:popShellFrom(0)
state.timestamp = -1
end
if (state.loaded_ammo > state.need_ammo or not context:hasAmmoToConsume()) then
context:trigger(this.INPUT_RELOAD_RETREAT)
return
end
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
if (context:isHolding(track)) then
context:runAnimation("reload_loop", track, false, PLAY_ONCE_HOLD, 0)
state.loaded_ammo = state.loaded_ammo + 1
end
end
-------------------------------------------------------
-- RELOAD TRANSITION END
-------------------------------------------------------
function reload_state.transition(this, context, input)
if (input == this.INPUT_RELOAD_RETREAT or input == INPUT_CANCEL_RELOAD) then
context:runAnimation("reload_end", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2)
return this.main_track_states.idle
end
return this.main_track_states.idle.transition(this, context, input)
end
-------------------------------------------------------
-- EXPORT MACHINE
-------------------------------------------------------
local M = setmetatable({
main_track_states = setmetatable({
start = start_state,
idle = idle_state,
reload = reload_state,
["end"] = end_state,
}, { __index = main_track_states }),
INPUT_RELOAD_RETREAT = "reload_retreat",
}, { __index = default })
function M:initialize(context)
default.initialize(self, context)
self.main_track_states.reload.need_ammo = 0
self.main_track_states.reload.loaded_ammo = 0
end
return M
@@ -0,0 +1,270 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_neutral1", "shoot_neutral2",
"shoot_UP",
"shoot_UPL",
"shoot_L",
"shoot_DOWNL",
"shoot_DOWN",
"shoot_DOWNR",
"shoot_R",
"shoot_UPR",
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 }),
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
@@ -0,0 +1,272 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_1", "shoot_2"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local current_shoot_index = 1
local function getNextShootAnimation()
local anim = shoot_animations[current_shoot_index]
current_shoot_index = current_shoot_index + 1
-- Loop back to the start if past the last animation
if current_shoot_index > #shoot_animations then
current_shoot_index = 1
end
return anim
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 }),
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
@@ -0,0 +1,261 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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 == 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 == 3) then
context:runAnimation("inspect_xmag_3", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1 or ext == 2) then
context:runAnimation("inspect_xmag_12", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
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
-- HOPE STATE
local shoot_animations = {
"shoot_1",
"shoot_2",
"shoot_3",
"shoot_4",
"shoot_5",
"shoot_6",
"shoot_7",
"shoot_8",
"shoot_9"
}
local shoot_animation_index = 1
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- HOPE STATE END
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
idle = idle_state,
main_track_states = setmetatable({
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
@@ -0,0 +1,252 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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 == 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 == 3) then
context:runAnimation("inspect_xmag_3", track, false, PLAY_ONCE_STOP, 0.2)
elseif (ext == 1 or ext == 2) then
context:runAnimation("inspect_xmag_12", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2)
end
end
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
-- HOPE STATE
local shoot_animations = {
"shoot_1"
}
local shoot_animation_index = 1
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
return nil
end
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return main_track_states.idle.transition(this, context, input)
end
-- HOPE STATE END
local M = setmetatable({
gun_kick_state = setmetatable({}, { __index = shoot_state }),
idle = idle_state,
main_track_states = setmetatable({
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
@@ -0,0 +1,73 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 require 的格式为 "{命名空间}_{路径}"
-- 注意!require 取得的内容不应该被修改,应仅调用
local default = require("tacz_default_state_machine")
local STATIC_TRACK_LINE = default.STATIC_TRACK_LINE
local GUN_KICK_TRACK_LINE = default.GUN_KICK_TRACK_LINE
local MAIN_TRACK = default.MAIN_TRACK
local main_track_states = default.main_track_states
-- main_track_states.idle 是我们要重写的状态。
local idle_state = setmetatable({}, {__index = main_track_states.idle})
local charge_state = {
can_charge = true
}
-- 常态检测(延迟扳机)
function idle_state.update(this, context)
if (context:isCharging()) then
if (charge_state.can_charge) then
context:trigger(this.INPUT_CHARING)
end
else
charge_state.can_charge = true
end
end
-- 重写 idle 状态的 transition 函数,将输入 INPUT_CHARING 重定向到新定义的 charge_state 状态
function idle_state.transition(this, context, input)
-- 进入延迟扳机状态
if (input == this.INPUT_CHARING) then
context:runAnimation("charge_in", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_HOLD, 0)
return this.main_track_states.charge
end
return main_track_states.idle.transition(this, context, input)
end
-- 进入延迟扳机状态
function charge_state.update(this, context)
if (not context:isCharging()) then
charge_state.can_charge = true
context:trigger(this.INPUT_CHARING_EXIT)
end
end
-- 离开延迟扳机状态
function charge_state.transition(this, context, input)
if (input == INPUT_SHOOT) then
context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK))
charge_state.can_charge = false
return this.main_track_states.idle
end
if (input == this.INPUT_CHARING_EXIT) then
context:runAnimation("charge_out", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.3)
return this.main_track_states.idle
end
end
-- 用元表的方式继承默认状态机的属性
local M = setmetatable({
main_track_states = setmetatable({
idle = idle_state,
charge = charge_state
}, {__index = main_track_states}),
INPUT_RELOAD_RETREAT = "reload_retreat",
INPUT_CHARING = "input_charging",
INPUT_CHARING_EXIT = "input_charging_exit"
}, {__index = default})
-- 先调用父级状态机的初始化函数,然后进行自己的初始化
function M:initialize(context)
default.initialize(self, context)
self.main_track_states.charge.can_charge = true
end
-- 导出状态机
return M
@@ -0,0 +1,294 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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 ammo = context:getAmmoCount()
-- EMPTY RELOAD
if ammo <= 0 then
context:runAnimation("reload_empty", track, false, PLAY_ONCE_STOP, 0.2)
return
end
-- TACTICAL RELOAD BASED ON AMMO
if ammo == 1 then
context:runAnimation("reload_tactical_ammo_1", track, false, PLAY_ONCE_STOP, 0.2)
elseif ammo == 2 then
context:runAnimation("reload_tactical_ammo_2", track, false, PLAY_ONCE_STOP, 0.2)
elseif ammo == 3 then
context:runAnimation("reload_tactical_ammo_3", track, false, PLAY_ONCE_STOP, 0.2)
elseif ammo == 4 then
context:runAnimation("reload_tactical_ammo_4", track, false, PLAY_ONCE_STOP, 0.2)
else
-- fallback
context:runAnimation("reload_tactical", track, false, PLAY_ONCE_STOP, 0.2)
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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
-- Track current animation index
local current_shoot_index = 1
local function getNextShootAnimation()
local anim = shoot_animations[current_shoot_index]
current_shoot_index = current_shoot_index + 1
-- Loop back to the start if past the last animation
if current_shoot_index > #shoot_animations then
current_shoot_index = 1
end
return anim
end
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)
if context:getAmmoCount() == 1 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
-- Set flag to indicate last shot was played
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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))
-- Check if ammo is 0 to play put_away_empty
if context:getAmmoCount() <= 0 then
context:runAnimation("put_away_empty", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
else
context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75)
end
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)
-- Don't trigger immediately, let the transition handle it
-- context:trigger(this.INPUT_MODE_DRAW)
end
function fire_mode_state.draw.transition(this, context, input)
-- Handle the initial draw animation based on ammo count
local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)
-- Check if ammo is 0 to play draw_empty
if context:getAmmoCount() <= 0 then
context:runAnimation("draw_empty", track, false, PLAY_ONCE_STOP, 0.2)
else
context:runAnimation("draw", track, false, PLAY_ONCE_STOP, 0.2)
end
-- Then transition to the appropriate fire mode state based on current fire mode
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
return fire_mode_state.semi -- Default fallback
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 }),
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
@@ -0,0 +1,274 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_1", "shoot_2", "shoot_3", "shoot_4",
"shoot_5", "shoot_6", "shoot_7", "shoot_8"
}
local last_shoot_animation = "shoot_last"
local bolt_catch_animation = "static_bolt_caught"
-- Track current animation index
local current_shoot_index = 1
local function getNextShootAnimation()
local anim = shoot_animations[current_shoot_index]
current_shoot_index = current_shoot_index + 1
-- Loop back to the start if past the last animation
if current_shoot_index > #shoot_animations then
current_shoot_index = 1
end
return anim
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", true)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 }),
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
@@ -0,0 +1,264 @@
-- 脚本的位置是 "{命名空间}:{路径}",那么 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
-- ========== RANDOM SHOOT ANIMATION SYSTEM ==========
local shoot_animations = {
"shoot_7a", "shoot_6a", "shoot_9a", "shoot_8a"
}
local last_shoot_animation = "shoot_last2"
local bolt_catch_animation = "static_bolt_caught"
local current_shoot_index = 1
local function getNextShootAnimation()
return shoot_animations[math.random(#shoot_animations)]
end
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)
if context:getAmmoCount() == 0 then
context:runAnimation(last_shoot_animation, track, true, PLAY_ONCE_STOP, 0)
context:runAnimation("last_shoot", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), true, PLAY_ONCE_HOLD, 0)
else
-- BURST mode: different random anim for each shot
if fireMode == BURST then
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
else
local anim = getNextShootAnimation()
context:runAnimation(anim, track, true, PLAY_ONCE_STOP, 0)
end
end
return nil
end
-- Bolt catch after last shot
local old_idle_transition = idle_state.transition
function idle_state.transition(this, context, input)
local track = context:findIdleTrack(GUN_KICK_TRACK_LINE, false)
if context:getAmmoCount() == 0
and context:getFlag("played_shoot_last")
and not context:isTrackPlaying(track) then
context:runAnimation(bolt_catch_animation, track, true, PLAY_ONCE_STOP, 0)
context:setFlag("played_shoot_last", false)
end
return old_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 }),
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