goonscape/types/player.go
2025-04-16 12:13:29 +02:00

197 lines
5.1 KiB
Go

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
}