big ol refactor
This commit is contained in:
+141
-113
@@ -1,7 +1,7 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -13,135 +13,163 @@ import (
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
Player *types.Player
|
||||
OtherPlayers map[int32]*types.Player
|
||||
// Component-based architecture
|
||||
PlayerManager *PlayerManager
|
||||
AssetManager *AssetManager
|
||||
UIManager *UIManager
|
||||
|
||||
// Core game state
|
||||
Camera rl.Camera3D
|
||||
Models []types.ModelAsset
|
||||
Music rl.Music
|
||||
Chat *Chat
|
||||
MenuOpen bool
|
||||
quitChan chan struct{}
|
||||
loginScreen *LoginScreen
|
||||
isLoggedIn bool
|
||||
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 {
|
||||
InitWorld()
|
||||
game := &Game{
|
||||
OtherPlayers: make(map[int32]*types.Player),
|
||||
// Create managers
|
||||
playerManager := NewPlayerManager()
|
||||
assetManager := NewAssetManager()
|
||||
uiManager := NewUIManager()
|
||||
|
||||
g := &Game{
|
||||
PlayerManager: playerManager,
|
||||
AssetManager: assetManager,
|
||||
UIManager: uiManager,
|
||||
Camera: rl.Camera3D{
|
||||
Position: rl.NewVector3(0, 10, 10),
|
||||
Target: rl.NewVector3(0, 0, 0),
|
||||
Up: rl.NewVector3(0, 1, 0),
|
||||
Position: rl.NewVector3(0.0, 20.0, 0.0),
|
||||
Target: rl.NewVector3(0.0, 0.0, 0.0),
|
||||
Up: rl.NewVector3(0.0, 1.0, 0.0),
|
||||
Fovy: 45.0,
|
||||
Projection: rl.CameraPerspective,
|
||||
},
|
||||
Chat: NewChat(),
|
||||
quitChan: make(chan struct{}),
|
||||
loginScreen: NewLoginScreen(),
|
||||
quitChan: make(chan struct{}),
|
||||
}
|
||||
game.Chat.userData = game
|
||||
return game
|
||||
|
||||
// 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
|
||||
|
||||
// Initialize world
|
||||
InitWorld()
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *Game) LoadAssets() error {
|
||||
var loadErr error
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
loadErr = fmt.Errorf("panic during asset loading: %v", r)
|
||||
// Cleanup any partially loaded assets
|
||||
g.Cleanup()
|
||||
return SafeExecute(func() error {
|
||||
// Load models
|
||||
var err error
|
||||
models, err := assets.LoadModels()
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to load models: %v", err)
|
||||
}
|
||||
}()
|
||||
g.AssetManager.Models = models
|
||||
|
||||
// Load models with better error handling
|
||||
g.Models, loadErr = assets.LoadModels()
|
||||
if loadErr != nil {
|
||||
return fmt.Errorf("failed to load models: %v", loadErr)
|
||||
}
|
||||
// Update legacy field
|
||||
g.Models = models
|
||||
|
||||
// Verify model loading
|
||||
for i, model := range g.Models {
|
||||
if model.Model.Meshes == nil {
|
||||
return fmt.Errorf("model %d failed to load properly", i)
|
||||
// Try to load music
|
||||
music, err := assets.LoadMusic("resources/audio/music.mp3")
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to load music: %v", err)
|
||||
} else {
|
||||
g.AssetManager.Music = music
|
||||
// Update legacy field
|
||||
g.Music = music
|
||||
}
|
||||
}
|
||||
|
||||
// Load music with better error handling
|
||||
g.Music, loadErr = assets.LoadMusic("resources/audio/GoonScape2.mp3")
|
||||
if loadErr != nil {
|
||||
return fmt.Errorf("failed to load music: %v", loadErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Game) Update(deltaTime float32) {
|
||||
if !g.isLoggedIn {
|
||||
username, password, isRegistering, submitted := g.loginScreen.Update()
|
||||
if submitted {
|
||||
// Legacy code to maintain compatibility
|
||||
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)
|
||||
if err != nil {
|
||||
g.loginScreen.SetError(err.Error())
|
||||
g.UIManager.LoginScreen.SetError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
g.Player = &types.Player{
|
||||
g.PlayerManager.LocalPlayer = &types.Player{
|
||||
Speed: 50.0,
|
||||
TargetPath: []types.Tile{},
|
||||
UserData: g,
|
||||
QuitDone: make(chan struct{}),
|
||||
ID: playerID,
|
||||
}
|
||||
g.AssignModelToPlayer(g.Player)
|
||||
g.AssignModelToPlayer(g.PlayerManager.LocalPlayer)
|
||||
|
||||
go network.HandleServerCommunication(conn, playerID, g.Player, g.OtherPlayers, g.quitChan)
|
||||
g.isLoggedIn = true
|
||||
go network.HandleServerCommunication(conn, playerID, g.PlayerManager.LocalPlayer, g.PlayerManager.OtherPlayers, g.quitChan)
|
||||
g.UIManager.IsLoggedIn = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle ESC for menu
|
||||
if rl.IsKeyPressed(rl.KeyEscape) {
|
||||
g.MenuOpen = !g.MenuOpen
|
||||
g.UIManager.MenuOpen = !g.UIManager.MenuOpen
|
||||
return
|
||||
}
|
||||
|
||||
// Don't process other inputs if menu is open
|
||||
if g.MenuOpen {
|
||||
if g.UIManager.MenuOpen {
|
||||
return
|
||||
}
|
||||
|
||||
if message, sent := g.Chat.Update(); sent {
|
||||
g.Player.Lock()
|
||||
g.Player.ActionQueue = append(g.Player.ActionQueue, &pb.Action{
|
||||
if message, sent := g.UIManager.Chat.Update(); sent {
|
||||
g.PlayerManager.LocalPlayer.Lock()
|
||||
g.PlayerManager.LocalPlayer.ActionQueue = append(g.PlayerManager.LocalPlayer.ActionQueue, &pb.Action{
|
||||
Type: pb.Action_CHAT,
|
||||
ChatMessage: message,
|
||||
PlayerId: g.Player.ID,
|
||||
PlayerId: g.PlayerManager.LocalPlayer.ID,
|
||||
})
|
||||
g.Player.Unlock()
|
||||
g.PlayerManager.LocalPlayer.Unlock()
|
||||
}
|
||||
|
||||
g.HandleInput()
|
||||
|
||||
if len(g.Player.TargetPath) > 0 {
|
||||
g.Player.MoveTowards(g.Player.TargetPath[0], deltaTime, GetMapGrid())
|
||||
if len(g.PlayerManager.LocalPlayer.TargetPath) > 0 {
|
||||
g.PlayerManager.LocalPlayer.MoveTowards(g.PlayerManager.LocalPlayer.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, "There are %d other players", len(g.PlayerManager.OtherPlayers))
|
||||
for id, other := range g.PlayerManager.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.PlayerManager.OtherPlayers {
|
||||
if other == nil {
|
||||
continue
|
||||
}
|
||||
@@ -157,7 +185,19 @@ func (g *Game) Update(deltaTime float32) {
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCamera(&g.Camera, g.Player.PosActual, deltaTime)
|
||||
UpdateCamera(&g.Camera, g.PlayerManager.LocalPlayer.PosActual, deltaTime)
|
||||
|
||||
// Update music if available
|
||||
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() {
|
||||
@@ -280,8 +320,8 @@ func (g *Game) Render() {
|
||||
|
||||
rl.ClearBackground(rl.RayWhite)
|
||||
|
||||
if !g.isLoggedIn {
|
||||
g.loginScreen.Draw()
|
||||
if !g.UIManager.IsLoggedIn {
|
||||
g.UIManager.LoginScreen.Draw()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -289,12 +329,12 @@ func (g *Game) Render() {
|
||||
g.DrawMap()
|
||||
|
||||
// Draw player only if valid
|
||||
if g.Player != nil && g.Player.Model.Meshes != nil {
|
||||
g.DrawPlayer(g.Player, g.Player.Model)
|
||||
if g.PlayerManager.LocalPlayer != nil && g.PlayerManager.LocalPlayer.Model.Meshes != nil {
|
||||
g.DrawPlayer(g.PlayerManager.LocalPlayer, g.PlayerManager.LocalPlayer.Model)
|
||||
}
|
||||
|
||||
// Draw other players with defensive checks
|
||||
for _, other := range g.OtherPlayers {
|
||||
for _, other := range g.PlayerManager.OtherPlayers {
|
||||
if other == nil {
|
||||
continue
|
||||
}
|
||||
@@ -332,24 +372,24 @@ func (g *Game) Render() {
|
||||
rl.DrawText(text, int32(pos.X)-textWidth/2, int32(pos.Y), 20, rl.Yellow)
|
||||
}
|
||||
|
||||
if g.Player != nil && g.Player.FloatingMessage != nil {
|
||||
drawFloatingMessage(g.Player.FloatingMessage)
|
||||
if g.PlayerManager.LocalPlayer != nil && g.PlayerManager.LocalPlayer.FloatingMessage != nil {
|
||||
drawFloatingMessage(g.PlayerManager.LocalPlayer.FloatingMessage)
|
||||
}
|
||||
|
||||
for _, other := range g.OtherPlayers {
|
||||
for _, other := range g.PlayerManager.OtherPlayers {
|
||||
if other != nil && other.FloatingMessage != nil {
|
||||
drawFloatingMessage(other.FloatingMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw menu if open
|
||||
if g.MenuOpen {
|
||||
if g.UIManager.MenuOpen {
|
||||
g.DrawMenu()
|
||||
}
|
||||
|
||||
// Only draw chat if menu is not open
|
||||
if !g.MenuOpen && g.Chat != nil {
|
||||
g.Chat.Draw(int32(rl.GetScreenWidth()), int32(rl.GetScreenHeight()))
|
||||
if !g.UIManager.MenuOpen && g.UIManager.Chat != nil {
|
||||
g.UIManager.Chat.Draw(int32(rl.GetScreenWidth()), int32(rl.GetScreenHeight()))
|
||||
}
|
||||
|
||||
rl.DrawFPS(10, 10)
|
||||
@@ -357,35 +397,37 @@ func (g *Game) Render() {
|
||||
|
||||
func (g *Game) Cleanup() {
|
||||
g.cleanupOnce.Do(func() {
|
||||
// Stop music first
|
||||
if g.Music.Stream.Buffer != nil {
|
||||
rl.StopMusicStream(g.Music)
|
||||
rl.UnloadMusicStream(g.Music)
|
||||
}
|
||||
|
||||
// Unload textures
|
||||
for _, model := range g.Models {
|
||||
// Cleanup models
|
||||
for _, model := range g.AssetManager.Models {
|
||||
rl.UnloadModel(model.Model)
|
||||
if model.Texture.ID > 0 {
|
||||
rl.UnloadTexture(model.Texture)
|
||||
}
|
||||
}
|
||||
|
||||
// Unload music
|
||||
if g.AssetManager.Music.Stream.Buffer != nil {
|
||||
rl.UnloadMusicStream(g.AssetManager.Music)
|
||||
}
|
||||
|
||||
close(g.quitChan)
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Game) HandleInput() {
|
||||
clickedTile, clicked := g.GetTileAtMouse()
|
||||
if clicked {
|
||||
path := FindPath(GetTile(g.Player.PosTile.X, g.Player.PosTile.Y), clickedTile)
|
||||
path := FindPath(GetTile(g.PlayerManager.LocalPlayer.PosTile.X, g.PlayerManager.LocalPlayer.PosTile.Y), clickedTile)
|
||||
if len(path) > 1 {
|
||||
g.Player.Lock()
|
||||
g.Player.TargetPath = path[1:]
|
||||
g.Player.ActionQueue = append(g.Player.ActionQueue, &pb.Action{
|
||||
g.PlayerManager.LocalPlayer.Lock()
|
||||
g.PlayerManager.LocalPlayer.TargetPath = path[1:]
|
||||
g.PlayerManager.LocalPlayer.ActionQueue = append(g.PlayerManager.LocalPlayer.ActionQueue, &pb.Action{
|
||||
Type: pb.Action_MOVE,
|
||||
X: int32(clickedTile.X),
|
||||
Y: int32(clickedTile.Y),
|
||||
PlayerId: g.Player.ID,
|
||||
PlayerId: g.PlayerManager.LocalPlayer.ID,
|
||||
})
|
||||
g.Player.Unlock()
|
||||
g.PlayerManager.LocalPlayer.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,7 +470,7 @@ func (g *Game) DrawMenu() {
|
||||
if rl.IsMouseButtonPressed(rl.MouseLeftButton) {
|
||||
switch item {
|
||||
case "Resume":
|
||||
g.MenuOpen = false
|
||||
g.UIManager.MenuOpen = false
|
||||
case "Settings":
|
||||
// TODO: Implement settings
|
||||
case "Exit Game":
|
||||
@@ -453,7 +495,7 @@ func (g *Game) Shutdown() {
|
||||
}
|
||||
|
||||
func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) {
|
||||
g.Chat.HandleServerMessages(messages)
|
||||
g.UIManager.Chat.HandleServerMessages(messages)
|
||||
}
|
||||
|
||||
func (g *Game) AssignModelToPlayer(player *types.Player) {
|
||||
@@ -461,32 +503,18 @@ func (g *Game) AssignModelToPlayer(player *types.Player) {
|
||||
return
|
||||
}
|
||||
|
||||
// Defensive check for empty models array
|
||||
if len(g.Models) == 0 {
|
||||
rl.TraceLog(rl.LogWarning, "No models available to assign to player")
|
||||
return
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Prevent out of bounds access
|
||||
modelIndex = 0
|
||||
}
|
||||
|
||||
rl.TraceLog(rl.LogInfo, "Assigning model %d to player %d", modelIndex, player.ID)
|
||||
modelAsset := g.Models[modelIndex]
|
||||
|
||||
// Validate model before assigning
|
||||
if modelAsset.Model.Meshes == nil {
|
||||
rl.TraceLog(rl.LogWarning, "Trying to assign invalid model to player %d", player.ID)
|
||||
modelAsset, found := g.AssetManager.GetModelForPlayer(player.ID)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
player.Model = modelAsset.Model
|
||||
player.Texture = modelAsset.Texture
|
||||
player.PlaceholderColor = modelAsset.PlaceholderColor
|
||||
|
||||
// Initialize animations if available
|
||||
if len(modelAsset.Animations.Idle) > 0 || len(modelAsset.Animations.Walk) > 0 {
|
||||
player.InitializeAnimations(modelAsset.Animations)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) QuitChan() <-chan struct{} {
|
||||
|
||||
Reference in New Issue
Block a user