Broken bullshit

This commit is contained in:
2025-02-14 13:35:09 +01:00
parent a1aeb71512
commit 53cc9bca6b
20 changed files with 590 additions and 207 deletions

View File

@ -2,6 +2,7 @@ package game
import (
"fmt"
"sync"
"time"
"gitea.boner.be/bdnugget/goonscape/types"
@ -19,6 +20,7 @@ const (
)
type Chat struct {
sync.RWMutex
messages []types.ChatMessage
inputBuffer []rune
isTyping bool
@ -49,6 +51,8 @@ func (c *Chat) AddMessage(playerID int32, content string) {
}
func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
c.Lock()
defer c.Unlock()
// Convert protobuf messages to our local type
for _, msg := range messages {
localMsg := types.ChatMessage{
@ -80,13 +84,14 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
ExpireTime: time.Now().Add(6 * time.Second),
}
game.Player.Unlock()
} else if otherPlayer, exists := game.OtherPlayers[msg.PlayerId]; exists {
otherPlayer.Lock()
otherPlayer.FloatingMessage = &types.FloatingMessage{
} else if otherPlayer, exists := game.OtherPlayers.Load(msg.PlayerId); exists {
other := otherPlayer.(*types.Player)
other.Lock()
other.FloatingMessage = &types.FloatingMessage{
Content: msg.Content,
ExpireTime: time.Now().Add(6 * time.Second),
}
otherPlayer.Unlock()
other.Unlock()
}
}
}
@ -94,6 +99,8 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
}
func (c *Chat) Draw(screenWidth, screenHeight int32) {
c.RLock()
defer c.RUnlock()
// Calculate chat window width based on screen width
chatWindowWidth := screenWidth - (chatMargin * 2)

38
game/context.go Normal file
View File

@ -0,0 +1,38 @@
package game
import (
"context"
"sync"
)
type GameContext struct {
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
assetsLock sync.RWMutex
}
func NewGameContext() *GameContext {
ctx, cancel := context.WithCancel(context.Background())
return &GameContext{
ctx: ctx,
cancel: cancel,
}
}
func (gc *GameContext) Shutdown() {
gc.cancel()
gc.wg.Wait()
}
func (gc *GameContext) LoadAssets(fn func() error) error {
gc.assetsLock.Lock()
defer gc.assetsLock.Unlock()
return fn()
}
func (gc *GameContext) UnloadAssets(fn func()) {
gc.assetsLock.Lock()
defer gc.assetsLock.Unlock()
fn()
}

View File

@ -1,33 +1,44 @@
package game
import (
"fmt"
"os"
"sync"
"time"
"sync/atomic"
"gitea.boner.be/bdnugget/goonscape/assets"
"gitea.boner.be/bdnugget/goonscape/config"
"gitea.boner.be/bdnugget/goonscape/logging"
"gitea.boner.be/bdnugget/goonscape/network"
"gitea.boner.be/bdnugget/goonscape/types"
pb "gitea.boner.be/bdnugget/goonserver/actions"
rl "github.com/gen2brain/raylib-go/raylib"
)
var audioMutex sync.Mutex
var audioInitOnce sync.Once
type Game struct {
ctx *GameContext
Player *types.Player
OtherPlayers map[int32]*types.Player
OtherPlayers sync.Map // Using sync.Map for concurrent access
Camera rl.Camera3D
Models []types.ModelAsset
Music rl.Music
Chat *Chat
MenuOpen bool
MenuOpen atomic.Bool
QuitChan chan struct{} // Channel to signal shutdown
loginScreen *LoginScreen
isLoggedIn bool
isLoggedIn atomic.Bool
}
func New() *Game {
InitWorld()
game := &Game{
OtherPlayers: make(map[int32]*types.Player),
ctx: NewGameContext(),
OtherPlayers: sync.Map{},
Camera: rl.Camera3D{
Position: rl.NewVector3(0, 10, 10),
Target: rl.NewVector3(0, 0, 0),
@ -44,22 +55,38 @@ func New() *Game {
}
func (g *Game) LoadAssets() error {
audioMutex.Lock()
defer audioMutex.Unlock()
logging.Info.Println("Loading game assets")
var err error
// Load models first
g.Models, err = assets.LoadModels()
if err != nil {
logging.Error.Printf("Failed to load models: %v", err)
return err
}
g.Music, err = assets.LoadMusic("resources/audio/GoonScape2.mp3")
if err != nil {
return err
// Load music only if enabled
if config.Current.PlayMusic {
logging.Info.Println("Loading music stream")
g.Music = rl.LoadMusicStream("resources/audio/GoonScape2.mp3")
if g.Music.CtxType == 0 {
logging.Error.Println("Failed to load music stream")
return fmt.Errorf("failed to load music stream")
}
logging.Info.Println("Music stream loaded successfully")
} else {
logging.Info.Println("Music disabled by config")
}
logging.Info.Println("Assets loaded successfully")
return nil
}
func (g *Game) Update(deltaTime float32) {
if !g.isLoggedIn {
if !g.isLoggedIn.Load() {
username, password, isRegistering, submitted := g.loginScreen.Update()
if submitted {
conn, playerID, err := network.ConnectToServer(username, password, isRegistering)
@ -77,8 +104,8 @@ func (g *Game) Update(deltaTime float32) {
}
g.AssignModelToPlayer(g.Player)
go network.HandleServerCommunication(conn, playerID, g.Player, g.OtherPlayers, g.QuitChan)
g.isLoggedIn = true
go network.HandleServerCommunication(conn, playerID, g.Player, &g.OtherPlayers, g.QuitChan)
g.isLoggedIn.Store(true)
return
}
g.loginScreen.Draw()
@ -87,12 +114,12 @@ func (g *Game) Update(deltaTime float32) {
// Handle ESC for menu
if rl.IsKeyPressed(rl.KeyEscape) {
g.MenuOpen = !g.MenuOpen
g.MenuOpen.Store(!g.MenuOpen.Load())
return
}
// Don't process other inputs if menu is open
if g.MenuOpen {
if g.MenuOpen.Load() {
return
}
@ -109,14 +136,20 @@ func (g *Game) Update(deltaTime float32) {
g.HandleInput()
if len(g.Player.TargetPath) > 0 {
g.Player.MoveTowards(g.Player.TargetPath[0], deltaTime, GetMapGrid())
g.Player.Lock()
if len(g.Player.TargetPath) > 0 {
g.Player.MoveTowards(g.Player.TargetPath[0], deltaTime, GetMapGrid())
}
g.Player.Unlock()
}
for _, other := range g.OtherPlayers {
g.OtherPlayers.Range(func(key, value any) bool {
other := value.(*types.Player)
if len(other.TargetPath) > 0 {
other.MoveTowards(other.TargetPath[0], deltaTime, GetMapGrid())
}
}
return true
})
UpdateCamera(&g.Camera, g.Player.PosActual, deltaTime)
}
@ -153,6 +186,11 @@ func (g *Game) DrawPlayer(player *types.Player) {
player.Lock()
defer player.Unlock()
if player.Model.Meshes == nil {
logging.Error.Println("Player model not initialized")
return
}
grid := GetMapGrid()
modelIndex := int(player.ID) % len(g.Models)
modelAsset := g.Models[modelIndex]
@ -210,24 +248,36 @@ func (g *Game) DrawPlayer(player *types.Player) {
}
func (g *Game) Render() {
rl.BeginDrawing()
rl.ClearBackground(rl.RayWhite)
if !rl.IsWindowReady() {
logging.Error.Println("Window not ready for rendering")
return
}
if !g.isLoggedIn {
rl.BeginDrawing()
defer func() {
if rl.IsWindowReady() {
rl.EndDrawing()
}
}()
if !g.isLoggedIn.Load() {
g.loginScreen.Draw()
rl.EndDrawing()
return
}
rl.BeginMode3D(g.Camera)
g.DrawMap()
g.DrawPlayer(g.Player)
for _, other := range g.OtherPlayers {
g.OtherPlayers.Range(func(key, value any) bool {
other := value.(*types.Player)
if other.Model.Meshes == nil {
g.AssignModelToPlayer(other)
}
g.DrawPlayer(other)
}
return true
})
rl.EndMode3D()
// Draw floating messages
@ -255,27 +305,41 @@ func (g *Game) Render() {
drawFloatingMessage(g.Player.FloatingMessage)
}
for _, other := range g.OtherPlayers {
g.OtherPlayers.Range(func(key, value any) bool {
other := value.(*types.Player)
drawFloatingMessage(other.FloatingMessage)
}
return true
})
// Draw menu if open
if g.MenuOpen {
if g.MenuOpen.Load() {
g.DrawMenu()
}
// Only draw chat if menu is not open
if !g.MenuOpen {
if !g.MenuOpen.Load() {
g.Chat.Draw(int32(rl.GetScreenWidth()), int32(rl.GetScreenHeight()))
}
rl.DrawFPS(10, 10)
rl.EndDrawing()
}
func (g *Game) Cleanup() {
assets.UnloadModels(g.Models)
assets.UnloadMusic(g.Music)
// Unload models
if g.Models != nil {
assets.UnloadModels(g.Models)
}
// Stop and unload music if enabled
if config.Current.PlayMusic && g.Music.CtxType != 0 {
rl.StopMusicStream(g.Music)
rl.UnloadMusicStream(g.Music)
}
// Close audio device if it's ready
if rl.IsAudioDeviceReady() {
rl.CloseAudioDevice()
}
}
func (g *Game) HandleInput() {
@ -334,7 +398,7 @@ func (g *Game) DrawMenu() {
if rl.IsMouseButtonPressed(rl.MouseLeftButton) {
switch item {
case "Resume":
g.MenuOpen = false
g.MenuOpen.Store(false)
case "Settings":
// TODO: Implement settings
case "Exit Game":
@ -369,7 +433,37 @@ func (g *Game) AssignModelToPlayer(player *types.Player) {
modelIndex := int(player.ID) % len(g.Models)
modelAsset := g.Models[modelIndex]
// Just use the original model - don't try to copy it
player.Model = modelAsset.Model
player.Texture = modelAsset.Texture
player.AnimationFrame = 0
}
func (g *Game) Run() {
if config.Current.PlayMusic {
audioInitOnce.Do(func() {
logging.Info.Println("Initializing audio device")
rl.InitAudioDevice()
if !rl.IsAudioDeviceReady() {
logging.Error.Println("Failed to initialize audio device")
}
})
defer func() {
logging.Info.Println("Closing audio device")
rl.CloseAudioDevice()
}()
}
logging.Info.Println("Starting game loop")
for !rl.WindowShouldClose() {
deltaTime := rl.GetFrameTime()
if config.Current.PlayMusic {
rl.UpdateMusicStream(g.Music)
}
g.Update(deltaTime)
g.Render()
}
logging.Info.Println("Game loop ended")
logging.Info.Println("Closing quit channel")
close(g.QuitChan)
}

View File

@ -83,6 +83,7 @@ func heuristic(a, b types.Tile) float32 {
}
func distance(a, b types.Tile) float32 {
_, _ = a, b
return 1.0 // uniform cost for now
}