Compare commits
No commits in common. "7be859d58fe719749ae4cde92ebc11ab87250319" and "220a45147551126767edd556a56cdb182872ad48" have entirely different histories.
7be859d58f
...
220a451475
@ -85,19 +85,3 @@ func LoadModels() ([]types.ModelAsset, error) {
|
|||||||
func LoadMusic(filename string) (rl.Music, error) {
|
func LoadMusic(filename string) (rl.Music, error) {
|
||||||
return rl.LoadMusicStream(filename), nil
|
return rl.LoadMusicStream(filename), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnloadModels(models []types.ModelAsset) {
|
|
||||||
for _, model := range models {
|
|
||||||
if model.Animation != nil {
|
|
||||||
for i := int32(0); i < model.AnimFrames; i++ {
|
|
||||||
rl.UnloadModelAnimation(model.Animation[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rl.UnloadModel(model.Model)
|
|
||||||
rl.UnloadTexture(model.Texture)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnloadMusic(music rl.Music) {
|
|
||||||
rl.UnloadMusicStream(music)
|
|
||||||
}
|
|
||||||
|
@ -2,6 +2,7 @@ package game
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/types"
|
"gitea.boner.be/bdnugget/goonscape/types"
|
||||||
@ -25,6 +26,7 @@ type Chat struct {
|
|||||||
cursorPos int
|
cursorPos int
|
||||||
scrollOffset int
|
scrollOffset int
|
||||||
userData interface{}
|
userData interface{}
|
||||||
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChat() *Chat {
|
func NewChat() *Chat {
|
||||||
@ -49,6 +51,9 @@ func (c *Chat) AddMessage(playerID int32, content string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
|
func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
// 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{
|
||||||
@ -94,6 +99,9 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chat) Draw(screenWidth, screenHeight int32) {
|
func (c *Chat) Draw(screenWidth, screenHeight int32) {
|
||||||
|
c.mutex.RLock()
|
||||||
|
defer c.mutex.RUnlock()
|
||||||
|
|
||||||
// Calculate chat window width based on screen width
|
// Calculate chat window width based on screen width
|
||||||
chatWindowWidth := screenWidth - (chatMargin * 2)
|
chatWindowWidth := screenWidth - (chatMargin * 2)
|
||||||
|
|
||||||
|
68
game/game.go
68
game/game.go
@ -1,7 +1,8 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/assets"
|
"gitea.boner.be/bdnugget/goonscape/assets"
|
||||||
@ -19,9 +20,10 @@ type Game struct {
|
|||||||
Music rl.Music
|
Music rl.Music
|
||||||
Chat *Chat
|
Chat *Chat
|
||||||
MenuOpen bool
|
MenuOpen bool
|
||||||
QuitChan chan struct{} // Channel to signal shutdown
|
quitChan chan struct{}
|
||||||
loginScreen *LoginScreen
|
loginScreen *LoginScreen
|
||||||
isLoggedIn bool
|
isLoggedIn bool
|
||||||
|
cleanupOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Game {
|
func New() *Game {
|
||||||
@ -36,7 +38,7 @@ func New() *Game {
|
|||||||
Projection: rl.CameraPerspective,
|
Projection: rl.CameraPerspective,
|
||||||
},
|
},
|
||||||
Chat: NewChat(),
|
Chat: NewChat(),
|
||||||
QuitChan: make(chan struct{}),
|
quitChan: make(chan struct{}),
|
||||||
loginScreen: NewLoginScreen(),
|
loginScreen: NewLoginScreen(),
|
||||||
}
|
}
|
||||||
game.Chat.userData = game
|
game.Chat.userData = game
|
||||||
@ -44,15 +46,32 @@ func New() *Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) LoadAssets() error {
|
func (g *Game) LoadAssets() error {
|
||||||
var err error
|
var loadErr error
|
||||||
g.Models, err = assets.LoadModels()
|
defer func() {
|
||||||
if err != nil {
|
if r := recover(); r != nil {
|
||||||
return err
|
loadErr = fmt.Errorf("panic during asset loading: %v", r)
|
||||||
|
// Cleanup any partially loaded assets
|
||||||
|
g.Cleanup()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Load models with better error handling
|
||||||
|
g.Models, loadErr = assets.LoadModels()
|
||||||
|
if loadErr != nil {
|
||||||
|
return fmt.Errorf("failed to load models: %v", loadErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Music, err = assets.LoadMusic("resources/audio/GoonScape2.mp3")
|
// Verify model loading
|
||||||
if err != nil {
|
for i, model := range g.Models {
|
||||||
return err
|
if model.Model.Meshes == nil {
|
||||||
|
return fmt.Errorf("model %d failed to load properly", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
@ -77,11 +96,9 @@ func (g *Game) Update(deltaTime float32) {
|
|||||||
}
|
}
|
||||||
g.AssignModelToPlayer(g.Player)
|
g.AssignModelToPlayer(g.Player)
|
||||||
|
|
||||||
go network.HandleServerCommunication(conn, playerID, g.Player, g.OtherPlayers, g.QuitChan)
|
go network.HandleServerCommunication(conn, playerID, g.Player, g.OtherPlayers, g.quitChan)
|
||||||
g.isLoggedIn = true
|
g.isLoggedIn = true
|
||||||
return
|
|
||||||
}
|
}
|
||||||
g.loginScreen.Draw()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,8 +291,20 @@ func (g *Game) Render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Cleanup() {
|
func (g *Game) Cleanup() {
|
||||||
assets.UnloadModels(g.Models)
|
g.cleanupOnce.Do(func() {
|
||||||
assets.UnloadMusic(g.Music)
|
// Stop music first
|
||||||
|
if g.Music.Stream.Buffer != nil {
|
||||||
|
rl.StopMusicStream(g.Music)
|
||||||
|
rl.UnloadMusicStream(g.Music)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload textures
|
||||||
|
for _, model := range g.Models {
|
||||||
|
if model.Texture.ID > 0 {
|
||||||
|
rl.UnloadTexture(model.Texture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) HandleInput() {
|
func (g *Game) HandleInput() {
|
||||||
@ -355,10 +384,7 @@ func (g *Game) DrawMenu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Shutdown() {
|
func (g *Game) Shutdown() {
|
||||||
close(g.QuitChan)
|
close(g.quitChan)
|
||||||
<-g.Player.QuitDone
|
|
||||||
rl.CloseWindow()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) {
|
func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) {
|
||||||
@ -373,3 +399,7 @@ func (g *Game) AssignModelToPlayer(player *types.Player) {
|
|||||||
player.Model = modelAsset.Model
|
player.Model = modelAsset.Model
|
||||||
player.Texture = modelAsset.Texture
|
player.Texture = modelAsset.Texture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Game) QuitChan() <-chan struct{} {
|
||||||
|
return g.quitChan
|
||||||
|
}
|
||||||
|
82
main.go
82
main.go
@ -3,7 +3,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/game"
|
"gitea.boner.be/bdnugget/goonscape/game"
|
||||||
"gitea.boner.be/bdnugget/goonscape/network"
|
"gitea.boner.be/bdnugget/goonscape/network"
|
||||||
@ -11,11 +14,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("Recovered from panic in main: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Parse command line flags
|
// Parse command line flags
|
||||||
|
verbose := flag.Bool("v", false, "Also show info logs (spammy)")
|
||||||
local := flag.Bool("local", false, "Connect to local server")
|
local := flag.Bool("local", false, "Connect to local server")
|
||||||
addr := flag.String("addr", "", "Server address (host or host:port)")
|
addr := flag.String("addr", "", "Server address (host or host:port)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
if *verbose {
|
||||||
|
rl.SetTraceLogLevel(rl.LogTrace)
|
||||||
|
} else {
|
||||||
|
rl.SetTraceLogLevel(rl.LogWarning)
|
||||||
|
}
|
||||||
|
|
||||||
// Set server address based on flags
|
// Set server address based on flags
|
||||||
if *local {
|
if *local {
|
||||||
if *addr != "" {
|
if *addr != "" {
|
||||||
@ -32,29 +48,63 @@ func main() {
|
|||||||
|
|
||||||
rl.InitWindow(1024, 768, "GoonScape")
|
rl.InitWindow(1024, 768, "GoonScape")
|
||||||
rl.SetExitKey(0)
|
rl.SetExitKey(0)
|
||||||
defer rl.CloseWindow()
|
|
||||||
|
|
||||||
rl.InitAudioDevice()
|
rl.InitAudioDevice()
|
||||||
defer rl.CloseAudioDevice()
|
|
||||||
|
gameInstance := game.New()
|
||||||
|
if err := gameInstance.LoadAssets(); err != nil {
|
||||||
|
log.Printf("Failed to load assets: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
gameInstance.Cleanup()
|
||||||
|
rl.CloseWindow()
|
||||||
|
rl.CloseAudioDevice()
|
||||||
|
}()
|
||||||
|
|
||||||
rl.SetTargetFPS(60)
|
rl.SetTargetFPS(60)
|
||||||
|
|
||||||
game := game.New()
|
rl.PlayMusicStream(gameInstance.Music)
|
||||||
if err := game.LoadAssets(); err != nil {
|
rl.SetMusicVolume(gameInstance.Music, 0.5)
|
||||||
log.Fatalf("Failed to load assets: %v", err)
|
|
||||||
|
// 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()
|
||||||
}
|
}
|
||||||
defer game.Cleanup()
|
}()
|
||||||
|
|
||||||
rl.PlayMusicStream(game.Music)
|
|
||||||
rl.SetMusicVolume(game.Music, 0.5)
|
|
||||||
|
|
||||||
|
// Keep game loop in main thread for Raylib
|
||||||
for !rl.WindowShouldClose() {
|
for !rl.WindowShouldClose() {
|
||||||
deltaTime := rl.GetFrameTime()
|
deltaTime := rl.GetFrameTime()
|
||||||
rl.UpdateMusicStream(game.Music)
|
rl.UpdateMusicStream(gameInstance.Music)
|
||||||
game.Update(deltaTime)
|
|
||||||
game.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for clean shutdown
|
func() {
|
||||||
<-game.QuitChan
|
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:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/types"
|
"gitea.boner.be/bdnugget/goonscape/types"
|
||||||
@ -91,19 +92,32 @@ func ConnectToServer(username, password string, isRegistering bool) (net.Conn, i
|
|||||||
|
|
||||||
func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers map[int32]*types.Player, quitChan <-chan struct{}) {
|
func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers map[int32]*types.Player, quitChan <-chan struct{}) {
|
||||||
reader := bufio.NewReader(conn)
|
reader := bufio.NewReader(conn)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("Recovered from panic in HandleServerCommunication: %v", r)
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
if player.QuitDone != nil {
|
||||||
|
close(player.QuitDone)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
actionTicker := time.NewTicker(types.ClientTickRate)
|
actionTicker := time.NewTicker(types.ClientTickRate)
|
||||||
defer actionTicker.Stop()
|
defer actionTicker.Stop()
|
||||||
defer conn.Close()
|
|
||||||
defer close(player.QuitDone)
|
|
||||||
|
|
||||||
// Create a channel to signal when goroutines are done
|
// Create error channel for goroutine communication
|
||||||
|
errChan := make(chan error, 1)
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
|
|
||||||
// Create a set of current players to track disconnects
|
// Start message sending goroutine
|
||||||
currentPlayers := make(map[int32]bool)
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("Recovered from panic in message sender: %v", r)
|
||||||
|
errChan <- fmt.Errorf("message sender panic: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-quitChan:
|
case <-quitChan:
|
||||||
@ -118,23 +132,23 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
|
|||||||
writeMessage(conn, disconnectMsg)
|
writeMessage(conn, disconnectMsg)
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
return
|
return
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
case <-actionTicker.C:
|
case <-actionTicker.C:
|
||||||
player.Lock()
|
player.Lock()
|
||||||
if len(player.ActionQueue) > 0 {
|
if len(player.ActionQueue) > 0 {
|
||||||
actions := make([]*pb.Action, len(player.ActionQueue))
|
actions := make([]*pb.Action, len(player.ActionQueue))
|
||||||
copy(actions, player.ActionQueue)
|
copy(actions, player.ActionQueue)
|
||||||
|
|
||||||
batch := &pb.ActionBatch{
|
batch := &pb.ActionBatch{
|
||||||
PlayerId: playerID,
|
PlayerId: playerID,
|
||||||
Actions: actions,
|
Actions: actions,
|
||||||
Tick: player.CurrentTick,
|
Tick: player.CurrentTick,
|
||||||
}
|
}
|
||||||
|
|
||||||
player.ActionQueue = player.ActionQueue[:0]
|
player.ActionQueue = player.ActionQueue[:0]
|
||||||
player.Unlock()
|
player.Unlock()
|
||||||
|
|
||||||
if err := writeMessage(conn, batch); err != nil {
|
if err := writeMessage(conn, batch); err != nil {
|
||||||
log.Printf("Failed to send actions to server: %v", err)
|
errChan <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -144,32 +158,29 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Main message receiving loop
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("Recovered from panic in message receiver: %v", r)
|
||||||
|
errChan <- fmt.Errorf("message receiver panic: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-quitChan:
|
case <-quitChan:
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
<-done
|
|
||||||
close(player.QuitDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
log.Println("Shutdown timed out")
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
// Read message length (4 bytes)
|
|
||||||
lengthBuf := make([]byte, 4)
|
lengthBuf := make([]byte, 4)
|
||||||
if _, err := io.ReadFull(reader, lengthBuf); err != nil {
|
if _, err := io.ReadFull(reader, lengthBuf); err != nil {
|
||||||
log.Printf("Failed to read message length: %v", err)
|
if err != io.EOF {
|
||||||
|
errChan <- fmt.Errorf("failed to read message length: %v", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
messageLength := binary.BigEndian.Uint32(lengthBuf)
|
messageLength := binary.BigEndian.Uint32(lengthBuf)
|
||||||
|
|
||||||
// Read the full message
|
|
||||||
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)
|
||||||
@ -197,7 +208,6 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
|
|||||||
player.Unlock()
|
player.Unlock()
|
||||||
|
|
||||||
for _, state := range serverMessage.Players {
|
for _, state := range serverMessage.Players {
|
||||||
currentPlayers[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
|
||||||
@ -222,7 +232,7 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
|
|||||||
|
|
||||||
// Remove players that are no longer in the server state
|
// Remove players that are no longer in the server state
|
||||||
for id := range otherPlayers {
|
for id := range otherPlayers {
|
||||||
if !currentPlayers[id] {
|
if id != playerID {
|
||||||
delete(otherPlayers, id)
|
delete(otherPlayers, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,6 +242,23 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for error or quit signal
|
||||||
|
select {
|
||||||
|
case <-quitChan:
|
||||||
|
// Send disconnect message
|
||||||
|
disconnectMsg := &pb.ActionBatch{
|
||||||
|
PlayerId: playerID,
|
||||||
|
Actions: []*pb.Action{{
|
||||||
|
Type: pb.Action_DISCONNECT,
|
||||||
|
PlayerId: playerID,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
writeMessage(conn, disconnectMsg)
|
||||||
|
case err := <-errChan:
|
||||||
|
log.Printf("Network error: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to write length-prefixed messages
|
// Helper function to write length-prefixed messages
|
||||||
@ -252,3 +279,50 @@ func writeMessage(conn net.Conn, msg proto.Message) error {
|
|||||||
_, err = conn.Write(data)
|
_, err = conn.Write(data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Connection struct {
|
||||||
|
conn net.Conn
|
||||||
|
playerID int32
|
||||||
|
quitChan chan struct{}
|
||||||
|
quitDone chan struct{}
|
||||||
|
closeOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnection(username, password string, isRegistering bool) (*Connection, error) {
|
||||||
|
conn, playerID, err := ConnectToServer(username, password, isRegistering)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Connection{
|
||||||
|
conn: conn,
|
||||||
|
playerID: playerID,
|
||||||
|
quitChan: make(chan struct{}),
|
||||||
|
quitDone: make(chan struct{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) Close() {
|
||||||
|
c.closeOnce.Do(func() {
|
||||||
|
close(c.quitChan)
|
||||||
|
// Wait with timeout for network cleanup
|
||||||
|
select {
|
||||||
|
case <-c.quitDone:
|
||||||
|
// Clean shutdown completed
|
||||||
|
case <-time.After(500 * time.Millisecond):
|
||||||
|
log.Println("Network cleanup timed out")
|
||||||
|
}
|
||||||
|
c.conn.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) PlayerID() int32 {
|
||||||
|
return c.playerID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) Start(player *types.Player, otherPlayers map[int32]*types.Player) {
|
||||||
|
go HandleServerCommunication(c.conn, c.playerID, player, otherPlayers, c.quitChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) QuitChan() <-chan struct{} {
|
||||||
|
return c.quitChan
|
||||||
|
}
|
||||||
|
@ -1,12 +1,34 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
||||||
rl "github.com/gen2brain/raylib-go/raylib"
|
rl "github.com/gen2brain/raylib-go/raylib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Player struct {
|
||||||
|
sync.RWMutex
|
||||||
|
Model rl.Model
|
||||||
|
Texture rl.Texture2D
|
||||||
|
PosActual rl.Vector3
|
||||||
|
PosTile Tile
|
||||||
|
TargetPath []Tile
|
||||||
|
Speed float32
|
||||||
|
ActionQueue []*pb.Action
|
||||||
|
ID int32
|
||||||
|
QuitDone chan struct{}
|
||||||
|
CurrentTick int64
|
||||||
|
UserData interface{}
|
||||||
|
FloatingMessage *FloatingMessage
|
||||||
|
IsMoving bool
|
||||||
|
AnimationFrame int32
|
||||||
|
LastAnimUpdate time.Time
|
||||||
|
LastUpdateTime time.Time
|
||||||
|
InterpolationProgress float32
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) {
|
func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) {
|
||||||
p.Lock()
|
p.Lock()
|
||||||
defer p.Unlock()
|
defer p.Unlock()
|
||||||
@ -74,6 +96,8 @@ func NewPlayer(state *pb.PlayerState) *Player {
|
|||||||
IsMoving: false,
|
IsMoving: false,
|
||||||
AnimationFrame: 0,
|
AnimationFrame: 0,
|
||||||
LastAnimUpdate: time.Now(),
|
LastAnimUpdate: time.Now(),
|
||||||
|
LastUpdateTime: time.Now(),
|
||||||
|
InterpolationProgress: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
||||||
@ -14,27 +13,6 @@ type Tile struct {
|
|||||||
Walkable bool
|
Walkable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Player struct {
|
|
||||||
sync.Mutex
|
|
||||||
PosActual rl.Vector3
|
|
||||||
PosTile Tile
|
|
||||||
TargetPath []Tile
|
|
||||||
ActionQueue []*pb.Action
|
|
||||||
Speed float32
|
|
||||||
Model rl.Model
|
|
||||||
Texture rl.Texture2D
|
|
||||||
ID int32
|
|
||||||
CurrentTick int64
|
|
||||||
LastUpdateTime time.Time
|
|
||||||
LastAnimUpdate time.Time
|
|
||||||
InterpolationProgress float32
|
|
||||||
UserData interface{}
|
|
||||||
FloatingMessage *FloatingMessage
|
|
||||||
QuitDone chan struct{}
|
|
||||||
AnimationFrame int32
|
|
||||||
IsMoving bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type AnimationSet struct {
|
type AnimationSet struct {
|
||||||
Idle []rl.ModelAnimation
|
Idle []rl.ModelAnimation
|
||||||
Walk []rl.ModelAnimation
|
Walk []rl.ModelAnimation
|
||||||
|
Loading…
x
Reference in New Issue
Block a user