local Object = require "lib/classic" ---@alias AnimationFinishBehavior ---| '"loop"' Loop the animation over from the beginning. ---| '"stop"' Freeze the animation on the last frame. ---| '"goto"' Go to another state. ---@class State ---@field animation Animation The animation frames for this state. ---@field on_finish? AnimationFinishBehavior What to do after this state's animation finishes. Defaults to "loop". ---@field next? string The next state to go to in the case of "goto" on_finish. local Animator = Object:extend() ---Creates a new animator. ---@param states { [string]: State } A dictionary of all states in this animator. ---@param first_state string The first state of this animator. function Animator:new(states, first_state) ---The states in this animator. self.states = states ---The current state. self.state = first_state ---The timestep of the current frame. self.step = 0 ---The current frame in the current animation. self.frame = 1 --- The next state to progress to. self.next_state = nil end function Animator:update(dt) local state = self.states[self.state] local frames = state.animation.frames --Step the current timestep and return if the frame hasn't changed. self.step = self.step + dt if self.step <= frames[self.frame].duration then return end --Step the current frame and return if there are more. self.step = 0 self.frame = self.frame + 1 if self.frame <= #frames then return end --We've reached the end of the current state. if self.next_state then self.state = self.next_state self.frame = 1 self.next_state = nil elseif state.on_finish == "goto" then self.state = state.next self.frame = 1 elseif state.on_finish == "stop" then self.frame = #frames elseif state.on_finish == "loop" or state.on_finish == nil then self.frame = 1 else error("Invalid state on_finish " .. state.on_finish) end end function Animator:draw(x, y) local state = self.states[self.state] state.animation:draw(self.frame, x, y) end ---Sets the state of this animator. ---@param state string The name of the state to set. ---@param queue? boolean If true, queues the state change for after the current state. function Animator:setState(state, queue) if not self.states[state] then error("Invalid state " .. state) elseif queue then self.next_state = state else self.state = state self.step = 0 self.frame = 1 end end return Animator