local track_line_top = {value = 0} local static_track_top = {value = 0} local blending_track_top = {value = 0} -- 相当于 obj.value++ local function increment(obj) obj.value = obj.value + 1 return obj.value - 1 end 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) 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 动画,并且将其剩余时长设为 context 传入的 put_away_time if (context:getFireMode() == SEMI) then context:runAnimation("put_away", track, false, PLAY_ONCE_HOLD, put_away_time * 0.75) else context:runAnimation("fanning_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) end local function isNoAmmo(context) return (not context:hasBulletInBarrel()) and (context:getAmmoCount() <= 0) end local function runInspectAnimation(context) local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK) if (isNoAmmo(context)) then context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2) else context:runAnimation("inspect", track, false, PLAY_ONCE_STOP, 0.2) end end local function runFanningInspectAnimation(context) local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK) if (isNoAmmo(context)) then context:runAnimation("inspect_1", track, false, PLAY_ONCE_STOP, 0.2) else context:runAnimation("inspect_1", track, false, PLAY_ONCE_STOP, 0.2) end end local base_track_state = { normal = {}, fanning = {} } function base_track_state.normal.entry(this, context) context:runAnimation("static_idle", context:getTrack(STATIC_TRACK_LINE, BASE_TRACK), false, LOOP, 0) end function base_track_state.normal.transition(this, context) if(context:getFireMode() == AUTO) then return this.base_track_state.fanning end end function base_track_state.fanning.entry(this, context) context:runAnimation("fanning_idle", context:getTrack(STATIC_TRACK_LINE, BASE_TRACK), false, LOOP, 0) end function base_track_state.fanning.transition(this, context) if(context:getFireMode() == SEMI) then return this.base_track_state.normal end end local bolt_caught_states = { normal = {}, bolt_caught = {} } function bolt_caught_states.normal.update(this, context) if (isNoAmmo(context) and context:getFireMode() == SEMI) then context:trigger(this.INPUT_BOLT_CAUGHT) end end function bolt_caught_states.normal.entry(this, context) this.bolt_caught_states.normal.update(this, context) end function bolt_caught_states.normal.transition(this, context, input) 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) if (not isNoAmmo(context)) then context:trigger(this.INPUT_BOLT_NORMAL) end end function bolt_caught_states.bolt_caught.transition(this, context, input) if (input == this.INPUT_BOLT_NORMAL or context:getFireMode() == AUTO) then context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK)) return this.bolt_caught_states.normal end end local main_track_states = { start = {}, idle = {}, inspect = {}, final = {}, fanning = {}, reload = { retreat = "reload_retreat", need_ammo = 0, loaded_ammo = 0 }, fanningReload = { retreat = "reload_retreat", emptyload = 0, }, intrun = -1, bayonet_counter = 0 } function main_track_states.start.transition(this, context, input) if (input == INPUT_DRAW) then if(context:getFireMode() == SEMI) then context:runAnimation("draw", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0) return this.main_track_states.idle else context:runAnimation("fanning_draw", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0) return this.main_track_states.fanning end end end function main_track_states.idle.transition(this, context, input) if (context:getFireMode() == AUTO) then context:runAnimation("fanning_switch", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2) return main_track_states.fanning end if (input == INPUT_PUT_AWAY) then runPutAwayAnimation(context) return this.main_track_states.final end if (input == INPUT_RELOAD) then return this.main_track_states.reload 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 main_track_states.intrun = 1 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 end function main_track_states.fanning.transition(this, context, input) if (context:getFireMode() == SEMI) then context:runAnimation("fanning_switch_1", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2) return main_track_states.idle end if (input == INPUT_PUT_AWAY) then runPutAwayAnimation(context) return main_track_states.final end if (input == INPUT_RELOAD) then return main_track_states.fanningReload end if (input == INPUT_INSPECT) then main_track_states.intrun = 1 context:runAnimation("fanning_inspect", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2) return main_track_states.inspect end if (input == INPUT_BAYONET_MUZZLE) then local counter = main_track_states.bayonet_counter local animationName = "melee_bayonet_" .. tostring(counter + 1) 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 main_track_states.fanning 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 main_track_states.fanning 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 main_track_states.fanning end end function main_track_states.reload.entry(this, context) if (isNoAmmo(context)) then context:runAnimation("reload_start_empty", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_HOLD, 0.2) else context:runAnimation("reload_start", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_HOLD, 0.2) end main_track_states.reload.need_ammo = context:getMaxAmmoCount() - context:getAmmoCount() main_track_states.reload.loaded_ammo = 0 end function main_track_states.reload.update(this, context) if (main_track_states.reload.loaded_ammo > main_track_states.reload.need_ammo or not context:hasAmmoToConsume()) then context:trigger(main_track_states.reload.retreat) else local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK) -- 等待 intro 结束,然后循环播放 loop 动画 if(context:isHolding(track)) then context:runAnimation("reload_loop", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_HOLD, 0.2) main_track_states.reload.loaded_ammo = main_track_states.reload.loaded_ammo + 1 end end end function main_track_states.reload.transition(this, context, input) if (input == main_track_states.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 main_track_states.idle end return main_track_states.idle.transition(this, context, input) end function main_track_states.fanningReload.entry(this, context) if (isNoAmmo(context)) then main_track_states.fanningReload.emptyload = 1 context:runAnimation("fanning_reload_start_empty", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_HOLD, 0.2) else context:runAnimation("fanning_reload_start", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_HOLD, 0.2) end main_track_states.reload.need_ammo = context:getMaxAmmoCount() - context:getAmmoCount() main_track_states.reload.loaded_ammo = 0 end function main_track_states.fanningReload.update(this, context) if (main_track_states.reload.loaded_ammo > main_track_states.reload.need_ammo or not context:hasAmmoToConsume()) then context:trigger(main_track_states.reload.retreat) else local track = context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK) -- 等待 intro 结束,然后循环播放 loop 动画 if(context:isHolding(track)) then if (main_track_states.fanningReload.emptyload == 1 and main_track_states.reload.loaded_ammo ~= main_track_states.reload.need_ammo) then context:runAnimation("fanning_reload_loop_empty", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_HOLD, 0) else context:runAnimation("fanning_reload_loop", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_HOLD, 0.2) end main_track_states.reload.loaded_ammo = main_track_states.reload.loaded_ammo + 1 end end context:runAnimation("bullet_state", context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), false, PLAY_ONCE_HOLD, 0) context:setAnimationProgress(context:getTrack(STATIC_TRACK_LINE, BOLT_CAUGHT_TRACK), (main_track_states.reload.loaded_ammo - 1)/10, false) end function main_track_states.fanningReload.transition(this, context, input) if (input == main_track_states.reload.retreat or input == INPUT_CANCEL_RELOAD) then if(main_track_states.reload.loaded_ammo > main_track_states.reload.need_ammo) then main_track_states.fanningReload.emptyload = 0 end context:runAnimation("fanning_reload_end", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2) return main_track_states.fanning end return main_track_states.fanning.transition(this, context, input) 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) if (input == this.INPUT_INSPECT_RETREAT) then if(context:getFireMode() == SEMI) then return this.main_track_states.idle else return this.main_track_states.fanning end end if (input == INPUT_SHOOT) then -- 特殊地,射击也应当打断检视 context:stopAnimation(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)) if(context:getFireMode() == SEMI) then return this.main_track_states.idle else return this.main_track_states.fanning end end if(context:getFireMode() == SEMI) then return this.main_track_states.idle.transition(this, context, input) else return this.main_track_states.fanning.transition(this, context, input) end 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:getFireMode() == AUTO) then context:runAnimation("fanning_shoot", track, true, PLAY_ONCE_STOP, 0) else context:runAnimation("shoot", track, true, PLAY_ONCE_STOP, 0) if(context:getAmmoCount() ~= 1) then context:runAnimation("bolt", context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK), false, PLAY_ONCE_STOP, 0.2) end end end return nil end local movement_track_states = { idle = {}, run = { mode = -1 }, walk = { mode = -1 } } function movement_track_states.idle.update(this, context) local track = context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK) -- 如果轨道空闲,则播放 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 if (context:isStopped(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)) or main_track_states.intrun == -1) then return movement_track_states.run else return movement_track_states.walk end 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 if(context:getFireMode() == AUTO) then context:runAnimation("run_start_1", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.2) else context:runAnimation("run_start", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.2) end end function movement_track_states.run.exit(this, context) if(context:getFireMode() == AUTO) then context:runAnimation("run_end_1", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.2) else context:runAnimation("run_end", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.2) end 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 if (context:isHolding(track)) then if(context:getFireMode() == AUTO) then context:runAnimation("run_1", track, true, LOOP, 0.2) else context:runAnimation("run", track, true, LOOP, 0.2) end 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 if(context:getFireMode() == AUTO) then context:runAnimation("run_hold_1", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.2) else context:runAnimation("run_hold", context:getTrack(BLENDING_TRACK_LINE, MOVEMENT_TRACK), true, PLAY_ONCE_HOLD, 0.2) end end else -- 如果玩家在地面,则切换回 run 动画 if (state.mode ~= 0) then state.mode = 0 if(context:getFireMode() == AUTO) then context:runAnimation("run_1", track, true, LOOP, 0.2) else context:runAnimation("run", track, true, LOOP, 0.2) end 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 or (main_track_states.intrun == 1 and not context:isStopped(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK)))) then return movement_track_states.walk end end function movement_track_states.walk.entry(this, context) 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) 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 if (context:isStopped(context:getTrack(STATIC_TRACK_LINE, MAIN_TRACK))) then main_track_states.intrun = -1 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 and main_track_states.intrun == -1) then return movement_track_states.run end end local M = { -- track lines 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 tracks 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 tracks blending_track_top = blending_track_top, MOVEMENT_TRACK = MOVEMENT_TRACK, LOOP_TRACK = LOOP_TRACK, -- states 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, -- inputs 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.normal, self.bolt_caught_states.normal, self.main_track_states.start, self.gun_kick_state, self.movement_track_states.idle } end return M