package types import ( "sync" "time" pb "gitea.boner.be/bdnugget/goonserver/actions" rl "github.com/gen2brain/raylib-go/raylib" ) // AnimationController manages animation state and updates type AnimationController struct { animations AnimationSet currentAnimation string // "idle" or "walk" frame int32 lastUpdate time.Time frameCount int32 } // NewAnimationController creates a new animation controller func NewAnimationController(animations AnimationSet) *AnimationController { return &AnimationController{ animations: animations, currentAnimation: "idle", frame: 0, lastUpdate: time.Now(), } } // Update updates the animation state based on movement func (ac *AnimationController) Update(deltaTime float32, isMoving bool) { // Set the current animation based on movement newAnimation := "idle" if isMoving { newAnimation = "walk" } // Reset frame counter when animation changes if ac.currentAnimation != newAnimation { ac.frame = 0 ac.currentAnimation = newAnimation } // Update the frame ac.frame += int32(deltaTime * 60) // Determine which animation set to use var frames []rl.ModelAnimation if ac.currentAnimation == "walk" && len(ac.animations.Walk) > 0 { frames = ac.animations.Walk } else if len(ac.animations.Idle) > 0 { frames = ac.animations.Idle } // If we have frames, ensure we loop properly if len(frames) > 0 && frames[0].FrameCount > 0 { ac.frame = ac.frame % frames[0].FrameCount } } // GetAnimFrame returns the current animation frame func (ac *AnimationController) GetAnimFrame() int32 { return ac.frame } // GetCurrentAnimation returns the current animation type func (ac *AnimationController) GetCurrentAnimation() string { return ac.currentAnimation } type Player struct { sync.RWMutex // Keep this for network operations Model rl.Model Texture rl.Texture2D PosActual rl.Vector3 PosTile Tile TargetPath []Tile Speed float32 ActionQueue []*pb.Action ID int32 QuitDone chan struct{} CurrentTick int64 UserData interface{} FloatingMessage *FloatingMessage IsMoving bool AnimationFrame int32 LastAnimUpdate time.Time LastUpdateTime time.Time InterpolationProgress float32 PlaceholderColor rl.Color AnimController *AnimationController } func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) { // No need for lock here as this is called from a single thread (game loop) targetPos := rl.Vector3{ X: float32(target.X * TileSize), Y: mapGrid[target.X][target.Y].Height * TileHeight, Z: float32(target.Y * TileSize), } direction := rl.Vector3Subtract(targetPos, p.PosActual) distance := rl.Vector3Length(direction) if distance > 1.0 { p.IsMoving = true } else { p.IsMoving = false } // Update animation if controller exists if p.AnimController != nil { p.AnimController.Update(deltaTime, p.IsMoving) p.AnimationFrame = p.AnimController.GetAnimFrame() } else { // Legacy animation update for backward compatibility if p.IsMoving { if !p.IsMoving { p.AnimationFrame = 0 } p.AnimationFrame += int32(deltaTime * 60) } else { wasMoving := p.IsMoving if wasMoving { p.AnimationFrame = 0 } p.AnimationFrame += int32(deltaTime * 60) } } if distance > 0 { direction = rl.Vector3Scale(direction, p.Speed*deltaTime/distance) } if distance > 1.0 { p.PosActual = rl.Vector3Add(p.PosActual, direction) } else { p.PosActual = targetPos p.PosTile = target if len(p.TargetPath) > 1 { p.TargetPath = p.TargetPath[1:] } } } func NewPlayer(state *pb.PlayerState) *Player { return &Player{ PosActual: rl.Vector3{ X: float32(state.X * TileSize), Y: float32(state.Y * TileHeight), Z: float32(state.Y * TileSize), }, PosTile: Tile{X: int(state.X), Y: int(state.Y)}, Speed: 50.0, ID: state.PlayerId, IsMoving: false, AnimationFrame: 0, LastAnimUpdate: time.Now(), LastUpdateTime: time.Now(), InterpolationProgress: 1.0, } } // InitializeAnimations sets up the animation controller for the player func (p *Player) InitializeAnimations(animations AnimationSet) { p.AnimController = NewAnimationController(animations) } func (p *Player) UpdatePosition(state *pb.PlayerState, tickRate time.Duration) { p.Lock() defer p.Unlock() targetTile := Tile{X: int(state.X), Y: int(state.Y)} if p.PosTile != targetTile { p.PosTile = targetTile p.LastUpdateTime = time.Now() p.InterpolationProgress = 0 p.TargetPath = []Tile{targetTile} } } func (p *Player) ForceResync(state *pb.PlayerState) { // Keep this lock since it's called from the network goroutine p.Lock() defer p.Unlock() p.PosTile = Tile{X: int(state.X), Y: int(state.Y)} p.PosActual = rl.Vector3{ X: float32(state.X * TileSize), Y: float32(state.Y * TileHeight), Z: float32(state.Y * TileSize), } p.TargetPath = nil p.ActionQueue = nil p.InterpolationProgress = 1.0 }