diff --git a/game/game.go b/game/game.go index 84ac39a..1a0bfc4 100644 --- a/game/game.go +++ b/game/game.go @@ -23,16 +23,6 @@ type Game struct { quitChan chan struct{} cleanupOnce sync.Once frameCounter int // For periodic logging - - // Legacy fields for backward compatibility - Player *types.Player // Use PlayerManager.LocalPlayer instead - OtherPlayers map[int32]*types.Player // Use PlayerManager.OtherPlayers instead - Models []types.ModelAsset // Use AssetManager.Models instead - Music rl.Music // Use AssetManager.Music instead - Chat *Chat // Use UIManager.Chat instead - MenuOpen bool // Use UIManager.MenuOpen instead - loginScreen *LoginScreen // Use UIManager.LoginScreen instead - isLoggedIn bool // Use UIManager.IsLoggedIn instead } func New() *Game { @@ -53,22 +43,10 @@ func New() *Game { Projection: rl.CameraPerspective, }, quitChan: make(chan struct{}), - // Initialize empty maps to avoid nil references - OtherPlayers: make(map[int32]*types.Player), } - // Initialize legacy fields (for backward compatibility) - g.Player = g.PlayerManager.LocalPlayer - g.OtherPlayers = g.PlayerManager.OtherPlayers - g.Models = g.AssetManager.Models - g.Music = g.AssetManager.Music - g.Chat = g.UIManager.Chat - g.MenuOpen = g.UIManager.MenuOpen - g.loginScreen = g.UIManager.LoginScreen - g.isLoggedIn = g.UIManager.IsLoggedIn - // Set up inter-component references - g.Chat.userData = g // Pass game instance to chat for callbacks + g.UIManager.Chat.userData = g // Pass game instance to chat for callbacks // Initialize world InitWorld() @@ -86,17 +64,12 @@ func (g *Game) LoadAssets() error { } g.AssetManager.Models = models - // Update legacy field - g.Models = models - // Try to load music - music, err := assets.LoadMusic("resources/audio/music.mp3") + music, err := assets.LoadMusic("resources/audio/GoonScape1.mp3") if err != nil { log.Printf("Warning: Failed to load music: %v", err) } else { g.AssetManager.Music = music - // Update legacy field - g.Music = music } return nil @@ -104,12 +77,10 @@ func (g *Game) LoadAssets() error { } func (g *Game) Update(deltaTime float32) { - // Legacy code to maintain compatibility + // Handle login screen if not logged in if !g.UIManager.IsLoggedIn { // Handle login username, password, isRegistering, doAuth := g.UIManager.LoginScreen.Update() - // Update legacy fields - g.isLoggedIn = g.UIManager.IsLoggedIn if doAuth { conn, playerID, err := network.ConnectToServer(username, password, isRegistering) @@ -127,9 +98,6 @@ func (g *Game) Update(deltaTime float32) { } g.AssignModelToPlayer(g.PlayerManager.LocalPlayer) - // Update the legacy Player field - g.Player = g.PlayerManager.LocalPlayer - // Set user data to allow chat message handling g.PlayerManager.LocalPlayer.UserData = g @@ -181,8 +149,13 @@ func (g *Game) Update(deltaTime float32) { rl.TraceLog(rl.LogInfo, "There are %d other players", len(g.PlayerManager.OtherPlayers)) for id, other := range g.PlayerManager.OtherPlayers { if other != nil { - 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) + // Calculate tile coordinates from absolute position + tileX := int(other.PosActual.X / float32(types.TileSize)) + tileY := int(other.PosActual.Z / float32(types.TileSize)) + + rl.TraceLog(rl.LogInfo, "Other player ID: %d, Position: (%f, %f, %f), Tile: (%d, %d), Has model: %v", + id, other.PosActual.X, other.PosActual.Y, other.PosActual.Z, + tileX, tileY, other.Model.Meshes != nil) } else { rl.TraceLog(rl.LogInfo, "Other player ID: %d is nil", id) } @@ -213,13 +186,6 @@ func (g *Game) Update(deltaTime float32) { if g.AssetManager.Music.Stream.Buffer != nil { rl.UpdateMusicStream(g.AssetManager.Music) } - - // Update legacy fields - g.Player = g.PlayerManager.LocalPlayer - g.OtherPlayers = g.PlayerManager.OtherPlayers - g.Models = g.AssetManager.Models - g.Music = g.AssetManager.Music - g.MenuOpen = g.UIManager.MenuOpen } func (g *Game) DrawMap() { @@ -251,94 +217,84 @@ func (g *Game) DrawMap() { } func (g *Game) DrawPlayer(player *types.Player, model rl.Model) { - // No need for lock in rendering, we'll use a "take snapshot" approach - // This avoids potential deadlocks and makes the rendering more consistent - - // Check for invalid model - if model.Meshes == nil || model.Meshes.VertexCount <= 0 { - // Don't try to draw invalid models + if player == nil { return } - grid := GetMapGrid() - modelIndex := int(player.ID) % len(g.Models) - if modelIndex < 0 || modelIndex >= len(g.Models) { - // Prevent out of bounds access + // Get necessary data + modelIndex := int(player.ID) % len(g.AssetManager.Models) + if modelIndex < 0 || modelIndex >= len(g.AssetManager.Models) { modelIndex = 0 } - modelAsset := g.Models[modelIndex] + modelAsset := g.AssetManager.Models[modelIndex] - const defaultHeight = 8.0 // Default height above tile, fine tune per model in types.ModelAsset + // Calculate position + const defaultHeight = 8.0 playerPos := rl.Vector3{ X: player.PosActual.X, - Y: grid[player.PosTile.X][player.PosTile.Y].Height*types.TileHeight + defaultHeight + modelAsset.YOffset, + Y: player.PosActual.Y + defaultHeight + modelAsset.YOffset, Z: player.PosActual.Z, } - // Check if model has animations - if modelAsset.Animations.Idle != nil || modelAsset.Animations.Walk != nil { - if player.IsMoving && len(modelAsset.Animations.Walk) > 0 { - anim := modelAsset.Animations.Walk[0] // Use first walk animation - if anim.FrameCount > 0 { - currentFrame := player.AnimationFrame % anim.FrameCount - rl.UpdateModelAnimation(model, anim, currentFrame) - } - } else if len(modelAsset.Animations.Idle) > 0 { - anim := modelAsset.Animations.Idle[0] // Use first idle animation - if anim.FrameCount > 0 { - currentFrame := player.AnimationFrame % anim.FrameCount - rl.UpdateModelAnimation(model, anim, currentFrame) - } - } - } - - // Use placeholder color if it's set, otherwise use white + // Simple drawing with scale parameter var drawColor rl.Color = rl.White if player.PlaceholderColor.A > 0 { drawColor = player.PlaceholderColor } - rl.DrawModel(model, playerPos, 16, drawColor) - // Draw floating messages and path indicators + // Draw the model at normal scale (16.0) + rl.DrawModel(model, playerPos, 16.0, drawColor) + + // Update floating message position if player.FloatingMessage != nil { - screenPos := rl.GetWorldToScreen(rl.Vector3{ + worldPos := rl.Vector3{ X: playerPos.X, - Y: playerPos.Y + 24.0, + Y: playerPos.Y + 24.0, // Position above head Z: playerPos.Z, - }, g.Camera) + } + player.FloatingMessage.ScreenPos = rl.GetWorldToScreen(worldPos, g.Camera) + } +} - player.FloatingMessage.ScreenPos = screenPos +func (g *Game) DrawFloatingMessages() { + var drawFloatingMessage = func(msg *types.FloatingMessage) { + if msg == nil || time.Now().After(msg.ExpireTime) { + return + } + + // Draw the message with RuneScape-style coloring (black outline with yellow text) + text := msg.Content + textWidth := rl.MeasureText(text, 20) + + // Draw black outline by offsetting the text slightly in all directions + for offsetX := -2; offsetX <= 2; offsetX++ { + for offsetY := -2; offsetY <= 2; offsetY++ { + rl.DrawText(text, + int32(msg.ScreenPos.X)-textWidth/2+int32(offsetX), + int32(msg.ScreenPos.Y)+int32(offsetY), + 20, + rl.Black) + } + } + + // Draw the yellow text on top + rl.DrawText(text, int32(msg.ScreenPos.X)-textWidth/2, int32(msg.ScreenPos.Y), 20, rl.Yellow) } - if len(player.TargetPath) > 0 { - targetTile := player.TargetPath[len(player.TargetPath)-1] - targetPos := rl.Vector3{ - X: float32(targetTile.X * types.TileSize), - Y: grid[targetTile.X][targetTile.Y].Height * types.TileHeight, - Z: float32(targetTile.Y * types.TileSize), - } - rl.DrawCubeWires(targetPos, types.TileSize, types.TileHeight, types.TileSize, rl.Green) + if g.PlayerManager.LocalPlayer != nil && g.PlayerManager.LocalPlayer.FloatingMessage != nil { + drawFloatingMessage(g.PlayerManager.LocalPlayer.FloatingMessage) + } - nextTile := player.TargetPath[0] - nextPos := rl.Vector3{ - X: float32(nextTile.X * types.TileSize), - Y: grid[nextTile.X][nextTile.Y].Height * types.TileHeight, - Z: float32(nextTile.Y * types.TileSize), + for _, other := range g.PlayerManager.OtherPlayers { + if other != nil && other.FloatingMessage != nil { + drawFloatingMessage(other.FloatingMessage) } - rl.DrawCubeWires(nextPos, types.TileSize, types.TileHeight, types.TileSize, rl.Yellow) } } func (g *Game) Render() { rl.BeginDrawing() - defer func() { - // This defer will catch any panics that might occur during rendering - // and ensure EndDrawing gets called to maintain proper graphics state - if r := recover(); r != nil { - rl.TraceLog(rl.LogError, "Panic during rendering: %v", r) - } - rl.EndDrawing() - }() + defer rl.EndDrawing() rl.ClearBackground(rl.RayWhite) @@ -347,6 +303,7 @@ func (g *Game) Render() { return } + // Draw 3D elements rl.BeginMode3D(g.Camera) g.DrawMap() @@ -361,48 +318,14 @@ func (g *Game) Render() { continue } - // Make sure model is assigned - if other.Model.Meshes == nil { - g.AssignModelToPlayer(other) - // Skip this frame if assignment failed - if other.Model.Meshes == nil { - continue - } + if other.Model.Meshes != nil { + g.DrawPlayer(other, other.Model) } - g.DrawPlayer(other, other.Model) } rl.EndMode3D() - // Draw floating messages - drawFloatingMessage := func(msg *types.FloatingMessage) { - if msg == nil || time.Now().After(msg.ExpireTime) { - return - } - pos := msg.ScreenPos - text := msg.Content - textWidth := rl.MeasureText(text, 20) - - for offsetX := -2; offsetX <= 2; offsetX++ { - for offsetY := -2; offsetY <= 2; offsetY++ { - rl.DrawText(text, - int32(pos.X)-textWidth/2+int32(offsetX), - int32(pos.Y)+int32(offsetY), - 20, - rl.Black) - } - } - rl.DrawText(text, int32(pos.X)-textWidth/2, int32(pos.Y), 20, rl.Yellow) - } - - if g.PlayerManager.LocalPlayer != nil && g.PlayerManager.LocalPlayer.FloatingMessage != nil { - drawFloatingMessage(g.PlayerManager.LocalPlayer.FloatingMessage) - } - - for _, other := range g.PlayerManager.OtherPlayers { - if other != nil && other.FloatingMessage != nil { - drawFloatingMessage(other.FloatingMessage) - } - } + // Draw floating messages with RuneScape style + g.DrawFloatingMessages() // Draw menu if open if g.UIManager.MenuOpen { @@ -414,6 +337,7 @@ func (g *Game) Render() { g.UIManager.Chat.Draw(int32(rl.GetScreenWidth()), int32(rl.GetScreenHeight())) } + // Draw FPS counter rl.DrawFPS(10, 10) } @@ -464,65 +388,47 @@ func (g *Game) DrawMenu() { screenWidth := float32(rl.GetScreenWidth()) screenHeight := float32(rl.GetScreenHeight()) - // Semi-transparent background + // Draw semi-transparent background rl.DrawRectangle(0, 0, int32(screenWidth), int32(screenHeight), rl.ColorAlpha(rl.Black, 0.7)) - // Menu title - title := "Menu" - titleSize := int32(40) - titleWidth := rl.MeasureText(title, titleSize) - rl.DrawText(title, int32(screenWidth/2)-titleWidth/2, 100, titleSize, rl.White) + // Draw menu items + menuItems := []string{"Resume", "Settings", "Quit"} + menuY := screenHeight/2 - float32(len(menuItems)*40)/2 - // Menu buttons - buttonWidth := float32(200) - buttonHeight := float32(40) - buttonY := float32(200) - buttonSpacing := float32(60) - - menuItems := []string{"Resume", "Settings", "Exit Game"} - for _, item := range menuItems { - buttonRect := rl.Rectangle{ - X: screenWidth/2 - buttonWidth/2, - Y: buttonY, - Width: buttonWidth, - Height: buttonHeight, - } - - // Check mouse hover + for i, item := range menuItems { + itemY := menuY + float32(i*40) mousePoint := rl.GetMousePosition() - mouseHover := rl.CheckCollisionPointRec(mousePoint, buttonRect) + itemRect := rl.Rectangle{X: screenWidth/2 - 100, Y: itemY, Width: 200, Height: 36} - // Draw button - if mouseHover { - rl.DrawRectangleRec(buttonRect, rl.ColorAlpha(rl.White, 0.3)) - if rl.IsMouseButtonPressed(rl.MouseLeftButton) { - switch item { - case "Resume": - g.UIManager.MenuOpen = false - case "Settings": - // TODO: Implement settings - case "Exit Game": - g.Shutdown() - } - } + // Check for hover + isHover := rl.CheckCollisionPointRec(mousePoint, itemRect) + + // Draw button background + if isHover { + rl.DrawRectangleRec(itemRect, rl.ColorAlpha(rl.White, 0.3)) + } else { + rl.DrawRectangleRec(itemRect, rl.ColorAlpha(rl.White, 0.1)) } // Draw button text - textSize := int32(20) - textWidth := rl.MeasureText(item, textSize) - textX := int32(buttonRect.X+buttonRect.Width/2) - textWidth/2 - textY := int32(buttonRect.Y + buttonRect.Height/2 - float32(textSize)/2) - rl.DrawText(item, textX, textY, textSize, rl.White) + textWidth := rl.MeasureText(item, 20) + rl.DrawText(item, int32(itemRect.X+(itemRect.Width-float32(textWidth))/2), int32(itemRect.Y+8), 20, rl.White) - buttonY += buttonSpacing + // Handle click + if isHover && rl.IsMouseButtonReleased(rl.MouseLeftButton) { + switch item { + case "Resume": + g.UIManager.MenuOpen = false + case "Settings": + // TODO: Implement settings + case "Quit": + g.Shutdown() + rl.CloseWindow() + } + } } } -func (g *Game) Shutdown() { - // Use the cleanup method which has channel-closing safety - g.Cleanup() -} - func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) { // Check if Chat is properly initialized if g.UIManager != nil && g.UIManager.Chat != nil { @@ -532,6 +438,15 @@ func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) { } } +func (g *Game) QuitChan() <-chan struct{} { + return g.quitChan +} + +func (g *Game) Shutdown() { + // Use the cleanup method which has channel-closing safety + g.Cleanup() +} + func (g *Game) AssignModelToPlayer(player *types.Player) { if player == nil { return @@ -550,7 +465,3 @@ func (g *Game) AssignModelToPlayer(player *types.Player) { player.InitializeAnimations(modelAsset.Animations) } } - -func (g *Game) QuitChan() <-chan struct{} { - return g.quitChan -} diff --git a/main.go b/main.go index 117a023..519ece1 100644 --- a/main.go +++ b/main.go @@ -101,9 +101,9 @@ func main() { rl.SetTargetFPS(60) // Play music if available - if gameInstance.Music.Stream.Buffer != nil { - rl.PlayMusicStream(gameInstance.Music) - rl.SetMusicVolume(gameInstance.Music, 0.5) + if gameInstance.AssetManager.Music.Stream.Buffer != nil { + rl.PlayMusicStream(gameInstance.AssetManager.Music) + rl.SetMusicVolume(gameInstance.AssetManager.Music, 0.5) } // Handle OS signals for clean shutdown @@ -121,8 +121,8 @@ func main() { deltaTime := rl.GetFrameTime() // Update music if available - if gameInstance.Music.Stream.Buffer != nil { - rl.UpdateMusicStream(gameInstance.Music) + if gameInstance.AssetManager.Music.Stream.Buffer != nil { + rl.UpdateMusicStream(gameInstance.AssetManager.Music) } func() {