package main import ( "flag" "log" "os" "os/signal" "strings" "syscall" "time" "gitea.boner.be/bdnugget/goonscape/game" "gitea.boner.be/bdnugget/goonscape/network" rl "github.com/gen2brain/raylib-go/raylib" ) func main() { // Set up panic recovery at the top level defer func() { if r := recover(); r != nil { log.Printf("Recovered from fatal panic in main: %v", r) // Give the user a chance to see the error time.Sleep(5 * time.Second) } }() // Parse command line flags verbose := flag.Bool("v", false, "Also show info logs (spammy)") local := flag.Bool("local", false, "Connect to local server") addr := flag.String("addr", "", "Server address (host or host:port)") flag.Parse() if *verbose { rl.SetTraceLogLevel(rl.LogTrace) } else { rl.SetTraceLogLevel(rl.LogWarning) } // Set server address based on flags if *local { if *addr != "" { log.Fatal("Cannot use -local and -addr together") } network.SetServerAddr("localhost:6969") } else if *addr != "" { // If port is not specified, append default port if !strings.Contains(*addr, ":") { *addr += ":6969" } network.SetServerAddr(*addr) } // Initialize window with error handling rl.SetConfigFlags(rl.FlagMsaa4xHint) // Enable MSAA for smoother rendering rl.InitWindow(1024, 768, "GoonScape") rl.SetExitKey(0) // Initialize audio with error handling if !rl.IsAudioDeviceReady() { rl.InitAudioDevice() if !rl.IsAudioDeviceReady() { log.Println("Warning: Failed to initialize audio device, continuing without audio") } } // Use a maximum of 3 attempts to load assets var gameInstance *game.Game var loadErr error maxAttempts := 3 for attempt := 1; attempt <= maxAttempts; attempt++ { gameInstance = game.New() loadErr = gameInstance.LoadAssets() if loadErr == nil { break } log.Printf("Attempt %d/%d: Failed to load assets: %v", attempt, maxAttempts, loadErr) if attempt < maxAttempts { log.Println("Retrying...") gameInstance.Cleanup() // Cleanup before retrying time.Sleep(500 * time.Millisecond) } } if loadErr != nil { log.Printf("Failed to load assets after %d attempts. Starting with default assets.", maxAttempts) } defer func() { if gameInstance != nil { gameInstance.Cleanup() } rl.CloseWindow() if rl.IsAudioDeviceReady() { rl.CloseAudioDevice() } }() rl.SetTargetFPS(60) // Play music if available if gameInstance.Music.Stream.Buffer != nil { rl.PlayMusicStream(gameInstance.Music) rl.SetMusicVolume(gameInstance.Music, 0.5) } // Handle OS signals for clean shutdown sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) go func() { <-sigChan if gameInstance != nil { gameInstance.Shutdown() } }() // Keep game loop in main thread for Raylib for !rl.WindowShouldClose() { deltaTime := rl.GetFrameTime() // Update music if available if gameInstance.Music.Stream.Buffer != nil { rl.UpdateMusicStream(gameInstance.Music) } func() { defer func() { if r := recover(); r != nil { log.Printf("Recovered from panic in game update: %v", r) } }() gameInstance.Update(deltaTime) }() func() { defer func() { if r := recover(); r != nil { log.Printf("Recovered from panic in game render: %v", r) } }() gameInstance.Render() }() // Check if game requested shutdown select { case <-gameInstance.QuitChan(): return default: } } }