refactor/less-complexity #5
21
constants.go
21
constants.go
@ -2,20 +2,11 @@ package main
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
// Game world constants
|
||||||
const (
|
const (
|
||||||
MapWidth = 50
|
// Server-related constants
|
||||||
MapHeight = 50
|
ServerTickRate = 600 * time.Millisecond // RuneScape-style tick rate
|
||||||
TileSize = 32
|
ClientTickRate = 50 * time.Millisecond // Client runs at higher rate for smooth rendering
|
||||||
TileHeight = 2.0
|
MaxTickDesync = 5 // Max ticks behind before forcing resync
|
||||||
TickRate = 600 * time.Millisecond // Server tick rate (600ms)
|
DefaultPort = "6969" // Default server port
|
||||||
serverAddr = "localhost:6969"
|
|
||||||
|
|
||||||
// RuneScape-style tick rate (600ms)
|
|
||||||
ServerTickRate = 600 * time.Millisecond
|
|
||||||
|
|
||||||
// Client might run at a higher tick rate for smooth rendering
|
|
||||||
ClientTickRate = 50 * time.Millisecond
|
|
||||||
|
|
||||||
// Maximum number of ticks we can get behind before forcing a resync
|
|
||||||
MaxTickDesync = 5
|
|
||||||
)
|
)
|
||||||
|
11
game/chat.go
11
game/chat.go
@ -2,6 +2,7 @@ package game
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -54,6 +55,12 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
|
|||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
if len(messages) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Processing %d chat messages", len(messages))
|
||||||
|
|
||||||
// Convert protobuf messages to our local type
|
// Convert protobuf messages to our local type
|
||||||
for _, msg := range messages {
|
for _, msg := range messages {
|
||||||
localMsg := types.ChatMessage{
|
localMsg := types.ChatMessage{
|
||||||
@ -69,6 +76,7 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
|
|||||||
c.messages = c.messages[1:]
|
c.messages = c.messages[1:]
|
||||||
}
|
}
|
||||||
c.messages = append(c.messages, localMsg)
|
c.messages = append(c.messages, localMsg)
|
||||||
|
log.Printf("Added chat message from %s: %s", msg.Username, msg.Content)
|
||||||
|
|
||||||
// Scroll to latest message if it's not already visible
|
// Scroll to latest message if it's not already visible
|
||||||
visibleMessages := int((chatHeight - inputHeight) / messageHeight)
|
visibleMessages := int((chatHeight - inputHeight) / messageHeight)
|
||||||
@ -92,6 +100,9 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
|
|||||||
ExpireTime: time.Now().Add(6 * time.Second),
|
ExpireTime: time.Now().Add(6 * time.Second),
|
||||||
}
|
}
|
||||||
otherPlayer.Unlock()
|
otherPlayer.Unlock()
|
||||||
|
log.Printf("Added floating message to other player %d", msg.PlayerId)
|
||||||
|
} else {
|
||||||
|
log.Printf("Could not find other player %d to add floating message", msg.PlayerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
39
game/game.go
39
game/game.go
@ -24,6 +24,7 @@ type Game struct {
|
|||||||
loginScreen *LoginScreen
|
loginScreen *LoginScreen
|
||||||
isLoggedIn bool
|
isLoggedIn bool
|
||||||
cleanupOnce sync.Once
|
cleanupOnce sync.Once
|
||||||
|
frameCounter int // For periodic logging
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Game {
|
func New() *Game {
|
||||||
@ -129,7 +130,28 @@ func (g *Game) Update(deltaTime float32) {
|
|||||||
g.Player.MoveTowards(g.Player.TargetPath[0], deltaTime, GetMapGrid())
|
g.Player.MoveTowards(g.Player.TargetPath[0], deltaTime, GetMapGrid())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Periodically log information about other players
|
||||||
|
g.frameCounter++
|
||||||
|
if g.frameCounter%300 == 0 {
|
||||||
|
rl.TraceLog(rl.LogInfo, "There are %d other players", len(g.OtherPlayers))
|
||||||
|
for id, other := range g.OtherPlayers {
|
||||||
|
rl.TraceLog(rl.LogInfo, "Other player ID: %d, Position: (%f, %f, %f), Has model: %v",
|
||||||
|
id, other.PosActual.X, other.PosActual.Y, other.PosActual.Z, other.Model.Meshes != nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process other players
|
||||||
for _, other := range g.OtherPlayers {
|
for _, other := range g.OtherPlayers {
|
||||||
|
if other == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure other players have models assigned
|
||||||
|
if other.Model.Meshes == nil {
|
||||||
|
g.AssignModelToPlayer(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update other player movement
|
||||||
if len(other.TargetPath) > 0 {
|
if len(other.TargetPath) > 0 {
|
||||||
other.MoveTowards(other.TargetPath[0], deltaTime, GetMapGrid())
|
other.MoveTowards(other.TargetPath[0], deltaTime, GetMapGrid())
|
||||||
}
|
}
|
||||||
@ -167,8 +189,8 @@ func (g *Game) DrawMap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
|
func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
|
||||||
player.Lock()
|
// No need for lock in rendering, we'll use a "take snapshot" approach
|
||||||
defer player.Unlock()
|
// This avoids potential deadlocks and makes the rendering more consistent
|
||||||
|
|
||||||
// Check for invalid model
|
// Check for invalid model
|
||||||
if model.Meshes == nil || model.Meshes.VertexCount <= 0 {
|
if model.Meshes == nil || model.Meshes.VertexCount <= 0 {
|
||||||
@ -196,14 +218,14 @@ func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
|
|||||||
if player.IsMoving && len(modelAsset.Animations.Walk) > 0 {
|
if player.IsMoving && len(modelAsset.Animations.Walk) > 0 {
|
||||||
anim := modelAsset.Animations.Walk[0] // Use first walk animation
|
anim := modelAsset.Animations.Walk[0] // Use first walk animation
|
||||||
if anim.FrameCount > 0 {
|
if anim.FrameCount > 0 {
|
||||||
player.AnimationFrame = player.AnimationFrame % anim.FrameCount
|
currentFrame := player.AnimationFrame % anim.FrameCount
|
||||||
rl.UpdateModelAnimation(model, anim, player.AnimationFrame)
|
rl.UpdateModelAnimation(model, anim, currentFrame)
|
||||||
}
|
}
|
||||||
} else if len(modelAsset.Animations.Idle) > 0 {
|
} else if len(modelAsset.Animations.Idle) > 0 {
|
||||||
anim := modelAsset.Animations.Idle[0] // Use first idle animation
|
anim := modelAsset.Animations.Idle[0] // Use first idle animation
|
||||||
if anim.FrameCount > 0 {
|
if anim.FrameCount > 0 {
|
||||||
player.AnimationFrame = player.AnimationFrame % anim.FrameCount
|
currentFrame := player.AnimationFrame % anim.FrameCount
|
||||||
rl.UpdateModelAnimation(model, anim, player.AnimationFrame)
|
rl.UpdateModelAnimation(model, anim, currentFrame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -445,12 +467,15 @@ func (g *Game) AssignModelToPlayer(player *types.Player) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
modelIndex := int(player.ID) % len(g.Models)
|
// Make sure model index is positive for consistent player appearances
|
||||||
|
// Use abs value of ID to ensure consistent appearance for negative IDs
|
||||||
|
modelIndex := abs(int(player.ID)) % len(g.Models)
|
||||||
if modelIndex < 0 || modelIndex >= len(g.Models) {
|
if modelIndex < 0 || modelIndex >= len(g.Models) {
|
||||||
// Prevent out of bounds access
|
// Prevent out of bounds access
|
||||||
modelIndex = 0
|
modelIndex = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rl.TraceLog(rl.LogInfo, "Assigning model %d to player %d", modelIndex, player.ID)
|
||||||
modelAsset := g.Models[modelIndex]
|
modelAsset := g.Models[modelIndex]
|
||||||
|
|
||||||
// Validate model before assigning
|
// Validate model before assigning
|
||||||
|
2
main.go
2
main.go
@ -51,7 +51,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize window with error handling
|
// Initialize window with error handling
|
||||||
rl.SetConfigFlags(rl.FlagMsaa4xHint) // Enable MSAA for smoother rendering
|
rl.SetConfigFlags(rl.FlagMsaa4xHint | rl.FlagWindowResizable) // Enable MSAA and make window resizable
|
||||||
rl.InitWindow(1024, 768, "GoonScape")
|
rl.InitWindow(1024, 768, "GoonScape")
|
||||||
|
|
||||||
rl.SetExitKey(0)
|
rl.SetExitKey(0)
|
||||||
|
@ -18,10 +18,11 @@ import (
|
|||||||
|
|
||||||
const protoVersion = 1
|
const protoVersion = 1
|
||||||
|
|
||||||
var serverAddr = "boner.be:6969"
|
var serverAddr = "boner.be:6969" // Default server address
|
||||||
|
|
||||||
func SetServerAddr(addr string) {
|
func SetServerAddr(addr string) {
|
||||||
serverAddr = addr
|
serverAddr = addr
|
||||||
|
log.Printf("Server address set to: %s", serverAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectToServer(username, password string, isRegistering bool) (net.Conn, int32, error) {
|
func ConnectToServer(username, password string, isRegistering bool) (net.Conn, int32, error) {
|
||||||
@ -175,22 +176,33 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
|
|||||||
lengthBuf := make([]byte, 4)
|
lengthBuf := make([]byte, 4)
|
||||||
if _, err := io.ReadFull(reader, lengthBuf); err != nil {
|
if _, err := io.ReadFull(reader, lengthBuf); err != nil {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
|
log.Printf("Network read error: %v", err)
|
||||||
errChan <- fmt.Errorf("failed to read message length: %v", err)
|
errChan <- fmt.Errorf("failed to read message length: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Connection closed by server")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
messageLength := binary.BigEndian.Uint32(lengthBuf)
|
messageLength := binary.BigEndian.Uint32(lengthBuf)
|
||||||
|
|
||||||
|
// Sanity check message size to prevent potential memory issues
|
||||||
|
if messageLength > 1024*1024 { // 1MB max message size
|
||||||
|
log.Printf("Message size too large: %d bytes", messageLength)
|
||||||
|
errChan <- fmt.Errorf("message size too large: %d bytes", messageLength)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
messageBuf := make([]byte, messageLength)
|
messageBuf := make([]byte, messageLength)
|
||||||
if _, err := io.ReadFull(reader, messageBuf); err != nil {
|
if _, err := io.ReadFull(reader, messageBuf); err != nil {
|
||||||
log.Printf("Failed to read message body: %v", err)
|
log.Printf("Failed to read message body: %v", err)
|
||||||
|
errChan <- fmt.Errorf("failed to read message body: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverMessage pb.ServerMessage
|
var serverMessage pb.ServerMessage
|
||||||
if err := proto.Unmarshal(messageBuf, &serverMessage); err != nil {
|
if err := proto.Unmarshal(messageBuf, &serverMessage); err != nil {
|
||||||
log.Printf("Failed to unmarshal server message: %v", err)
|
log.Printf("Failed to unmarshal server message: %v", err)
|
||||||
continue
|
continue // Skip this message but don't quit
|
||||||
}
|
}
|
||||||
|
|
||||||
player.Lock()
|
player.Lock()
|
||||||
@ -207,7 +219,11 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
|
|||||||
}
|
}
|
||||||
player.Unlock()
|
player.Unlock()
|
||||||
|
|
||||||
|
// Process player states
|
||||||
|
validPlayerIds := make(map[int32]bool)
|
||||||
for _, state := range serverMessage.Players {
|
for _, state := range serverMessage.Players {
|
||||||
|
validPlayerIds[state.PlayerId] = true
|
||||||
|
|
||||||
if state.PlayerId == playerID {
|
if state.PlayerId == playerID {
|
||||||
player.Lock()
|
player.Lock()
|
||||||
// Update initial position if not set
|
// Update initial position if not set
|
||||||
@ -223,21 +239,26 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update or create other players
|
||||||
if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
|
if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
|
||||||
otherPlayer.UpdatePosition(state, types.ServerTickRate)
|
otherPlayer.UpdatePosition(state, types.ServerTickRate)
|
||||||
} else {
|
} else {
|
||||||
|
log.Printf("Creating new player with ID: %d", state.PlayerId)
|
||||||
otherPlayers[state.PlayerId] = types.NewPlayer(state)
|
otherPlayers[state.PlayerId] = types.NewPlayer(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove players that are no longer in the server state
|
// Remove players no longer in the server state
|
||||||
for id := range otherPlayers {
|
for id := range otherPlayers {
|
||||||
if id != playerID {
|
if id != playerID && !validPlayerIds[id] {
|
||||||
|
log.Printf("Removing player with ID: %d", id)
|
||||||
delete(otherPlayers, id)
|
delete(otherPlayers, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle chat messages
|
||||||
if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 {
|
if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 {
|
||||||
|
log.Printf("Received %d chat messages from server", len(serverMessage.ChatMessages))
|
||||||
handler.HandleServerMessages(serverMessage.ChatMessages)
|
handler.HandleServerMessages(serverMessage.ChatMessages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Player struct {
|
type Player struct {
|
||||||
sync.RWMutex
|
sync.RWMutex // Keep this for network operations
|
||||||
Model rl.Model
|
Model rl.Model
|
||||||
Texture rl.Texture2D
|
Texture rl.Texture2D
|
||||||
PosActual rl.Vector3
|
PosActual rl.Vector3
|
||||||
@ -31,9 +31,7 @@ type Player struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) {
|
func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) {
|
||||||
p.Lock()
|
// No need for lock here as this is called from a single thread (game loop)
|
||||||
defer p.Unlock()
|
|
||||||
|
|
||||||
targetPos := rl.Vector3{
|
targetPos := rl.Vector3{
|
||||||
X: float32(target.X * TileSize),
|
X: float32(target.X * TileSize),
|
||||||
Y: mapGrid[target.X][target.Y].Height * TileHeight,
|
Y: mapGrid[target.X][target.Y].Height * TileHeight,
|
||||||
@ -53,7 +51,7 @@ func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) {
|
|||||||
|
|
||||||
oldFrame := p.AnimationFrame
|
oldFrame := p.AnimationFrame
|
||||||
p.AnimationFrame += int32(deltaTime * 60)
|
p.AnimationFrame += int32(deltaTime * 60)
|
||||||
rl.TraceLog(rl.LogInfo, "Walk frame update: %d -> %d (delta: %f)",
|
rl.TraceLog(rl.LogDebug, "Walk frame update: %d -> %d (delta: %f)",
|
||||||
oldFrame, p.AnimationFrame, deltaTime)
|
oldFrame, p.AnimationFrame, deltaTime)
|
||||||
} else {
|
} else {
|
||||||
wasMoving := p.IsMoving
|
wasMoving := p.IsMoving
|
||||||
@ -65,7 +63,7 @@ func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) {
|
|||||||
|
|
||||||
oldFrame := p.AnimationFrame
|
oldFrame := p.AnimationFrame
|
||||||
p.AnimationFrame += int32(deltaTime * 60)
|
p.AnimationFrame += int32(deltaTime * 60)
|
||||||
rl.TraceLog(rl.LogInfo, "Idle frame update: %d -> %d (delta: %f)",
|
rl.TraceLog(rl.LogDebug, "Idle frame update: %d -> %d (delta: %f)",
|
||||||
oldFrame, p.AnimationFrame, deltaTime)
|
oldFrame, p.AnimationFrame, deltaTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +114,7 @@ func (p *Player) UpdatePosition(state *pb.PlayerState, tickRate time.Duration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) ForceResync(state *pb.PlayerState) {
|
func (p *Player) ForceResync(state *pb.PlayerState) {
|
||||||
|
// Keep this lock since it's called from the network goroutine
|
||||||
p.Lock()
|
p.Lock()
|
||||||
defer p.Unlock()
|
defer p.Unlock()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user