10 Commits

Author SHA1 Message Date
d5bb464d9f Add system messages to chat 2025-01-20 00:03:50 +01:00
4549ee7517 Fix cgo bullshit in build 2025-01-19 23:28:04 +01:00
31ae9c525f mod 2025-01-19 23:22:09 +01:00
06913a5217 Update buildscript and readme 2025-01-19 22:50:50 +01:00
49663c9094 rm build artefacts from git tracking 2025-01-19 22:25:48 +01:00
a843680b09 Prepare v1.1.0 release with auth and db functionality 2025-01-19 22:20:17 +01:00
7183df4a8b Merge pull request 'feature/db' (#4) from feature/db into master
Reviewed-on: #4
2025-01-19 21:07:43 +00:00
33e355200d Update goonserver 2025-01-19 22:07:04 +01:00
e45066b2a8 Based db and accounts and log in where you logged out 2025-01-19 21:52:56 +01:00
bb01dccf2b Getting somewhere lol 2025-01-19 21:23:47 +01:00
41 changed files with 426 additions and 879771 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
# Build artifacts
build/
goonscape
goonscape.exe
# IDE files
.vscode/
.idea/
*.swp
# OS files
.DS_Store
Thumbs.db

25
.woodpecker.yml Normal file
View File

@ -0,0 +1,25 @@
pipeline:
build:
image: golang:1.23
commands:
# Install build dependencies
- apt-get update && apt-get install -y gcc-mingw-w64 cmake zip libasound2-dev mesa-common-dev libx11-dev libxrandr-dev libxi-dev xorg-dev libgl1-mesa-dev libglu1-mesa-dev libwayland-dev wayland-protocols libxkbcommon-dev
# Build for all platforms
- make all
when:
event: tag
tag: v*
# Optional: Create Gitea release with built artifacts
release:
image: plugins/gitea-release
settings:
api_key:
from_secret: gitea_token
base_url: https://gitea.boner.be
files: build/*.zip
when:
event: tag
tag: v*

View File

@ -3,7 +3,7 @@
include scripts/platforms.mk include scripts/platforms.mk
BINARY_NAME=goonscape BINARY_NAME=goonscape
VERSION=1.0.0 VERSION=1.1.0
BUILD_DIR=build BUILD_DIR=build
ASSETS_DIR=resources ASSETS_DIR=resources

View File

@ -16,24 +16,57 @@ A multiplayer isometric game inspired by Oldschool RuneScape, built with Go and
## Prerequisites ## Prerequisites
- Go 1.23 or higher - Go 1.23 or higher
- GCC (for CGO/SQLite support)
- OpenGL development libraries
- Raylib dependencies (see [raylib-go](https://github.com/gen2brain/raylib-go#requirements)) - Raylib dependencies (see [raylib-go](https://github.com/gen2brain/raylib-go#requirements))
## Installation ## Installation
1. Clone the repository: ### Pre-built Binaries
The easiest way to get started is to download the latest release from:
```
https://gitea.boner.be/bdnugget/goonscape/releases
```
Choose the appropriate zip file for your platform:
- Windows: `goonscape-windows-amd64-v1.1.0.zip`
- Linux: `goonscape-linux-amd64-v1.1.0.zip`
Extract the zip and run the executable.
### Quick Start
For development:
```bash
# Run directly (recommended for development)
go run main.go
# Run with local server
go run main.go -local
```
### Server Setup
The server requires CGO for SQLite support:
```bash
# Enable CGO
go env -w CGO_ENABLED=1
# Clone and build server
git clone https://gitea.boner.be/bdnugget/goonserver.git
cd goonserver
go build
```
### Client Installation
Then install or build:
```bash
# Install the client
go install gitea.boner.be/bdnugget/goonscape@latest
```
Or build from source:
```bash ```bash
git clone https://gitea.boner.be/bdnugget/goonscape.git git clone https://gitea.boner.be/bdnugget/goonscape.git
cd goonscape cd goonscape
``` go build
2. Install dependencies:
```bash
go mod tidy
```
3. Build and run:
```bash
go run main.go
``` ```
## Controls ## Controls

Binary file not shown.

View File

@ -1,12 +0,0 @@
# Blender 3.6.0 MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd coomer.png

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

View File

@ -1,12 +0,0 @@
# Blender 3.6.0 MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd goonion.png

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -1,12 +0,0 @@
# Blender 3.6.0 MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd shreke.png

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

View File

@ -1,12 +0,0 @@
# Blender 3.6.0 MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd coomer.png

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

View File

@ -1,12 +0,0 @@
# Blender 3.6.0 MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd goonion.png

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -1,12 +0,0 @@
# Blender 3.6.0 MTL File: 'None'
# www.blender.org
newmtl Material.001
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd shreke.png

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

View File

@ -53,6 +53,7 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
for _, msg := range messages { for _, msg := range messages {
localMsg := types.ChatMessage{ localMsg := types.ChatMessage{
PlayerID: msg.PlayerId, PlayerID: msg.PlayerId,
Username: msg.Username,
Content: msg.Content, Content: msg.Content,
Time: time.Unix(0, msg.Timestamp), Time: time.Unix(0, msg.Timestamp),
} }
@ -117,8 +118,14 @@ func (c *Chat) Draw(screenWidth, screenHeight int32) {
for i := startIdx; i < endIdx; i++ { for i := startIdx; i < endIdx; i++ {
msg := c.messages[i] msg := c.messages[i]
text := fmt.Sprintf("[%d]: %s", msg.PlayerID, msg.Content) var color rl.Color
rl.DrawText(text, int32(chatX)+5, int32(messageY), 20, rl.White) if msg.PlayerID == 0 { // System message
color = rl.Gold
} else {
color = rl.White
}
text := fmt.Sprintf("%s: %s", msg.Username, msg.Content)
rl.DrawText(text, int32(chatX)+5, int32(messageY), 20, color)
messageY += messageHeight messageY += messageHeight
} }

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"gitea.boner.be/bdnugget/goonscape/assets" "gitea.boner.be/bdnugget/goonscape/assets"
"gitea.boner.be/bdnugget/goonscape/network"
"gitea.boner.be/bdnugget/goonscape/types" "gitea.boner.be/bdnugget/goonscape/types"
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"
@ -19,19 +20,13 @@ type Game struct {
Chat *Chat Chat *Chat
MenuOpen bool MenuOpen bool
QuitChan chan struct{} // Channel to signal shutdown QuitChan chan struct{} // Channel to signal shutdown
loginScreen *LoginScreen
isLoggedIn bool
} }
func New() *Game { func New() *Game {
InitWorld() InitWorld()
game := &Game{ game := &Game{
Player: &types.Player{
PosActual: rl.NewVector3(5*types.TileSize, 0, 5*types.TileSize),
PosTile: GetTile(5, 5),
Speed: 50.0,
TargetPath: []types.Tile{},
UserData: nil,
QuitDone: make(chan struct{}),
},
OtherPlayers: make(map[int32]*types.Player), OtherPlayers: make(map[int32]*types.Player),
Camera: rl.Camera3D{ Camera: rl.Camera3D{
Position: rl.NewVector3(0, 10, 10), Position: rl.NewVector3(0, 10, 10),
@ -40,10 +35,10 @@ func New() *Game {
Fovy: 45.0, Fovy: 45.0,
Projection: rl.CameraPerspective, Projection: rl.CameraPerspective,
}, },
Chat: NewChat(), Chat: NewChat(),
QuitChan: make(chan struct{}), QuitChan: make(chan struct{}),
loginScreen: NewLoginScreen(),
} }
game.Player.UserData = game
game.Chat.userData = game game.Chat.userData = game
return game return game
} }
@ -64,6 +59,35 @@ func (g *Game) LoadAssets() error {
} }
func (g *Game) Update(deltaTime float32) { func (g *Game) Update(deltaTime float32) {
if !g.isLoggedIn {
username, password, isRegistering, submitted := g.loginScreen.Update()
if submitted {
conn, playerID, err := network.ConnectToServer(username, password, isRegistering)
if err != nil {
g.loginScreen.SetError(err.Error())
return
}
// Assign model based on player ID
modelIndex := int(playerID) % len(g.Models)
g.Player = &types.Player{
Speed: 50.0,
TargetPath: []types.Tile{},
UserData: g,
QuitDone: make(chan struct{}),
ID: playerID,
Model: g.Models[modelIndex].Model,
Texture: g.Models[modelIndex].Texture,
}
go network.HandleServerCommunication(conn, playerID, g.Player, g.OtherPlayers, g.QuitChan)
g.isLoggedIn = true
return
}
g.loginScreen.Draw()
return
}
// Handle ESC for menu // Handle ESC for menu
if rl.IsKeyPressed(rl.KeyEscape) { if rl.IsKeyPressed(rl.KeyEscape) {
g.MenuOpen = !g.MenuOpen g.MenuOpen = !g.MenuOpen
@ -174,11 +198,23 @@ func (g *Game) Render() {
rl.BeginDrawing() rl.BeginDrawing()
rl.ClearBackground(rl.RayWhite) rl.ClearBackground(rl.RayWhite)
if !g.isLoggedIn {
g.loginScreen.Draw()
rl.EndDrawing()
return
}
rl.BeginMode3D(g.Camera) rl.BeginMode3D(g.Camera)
g.DrawMap() g.DrawMap()
g.DrawPlayer(g.Player, g.Player.Model) g.DrawPlayer(g.Player, g.Player.Model)
for id, other := range g.OtherPlayers { for id, other := range g.OtherPlayers {
g.DrawPlayer(other, g.Models[int(id)%len(g.Models)].Model) if other.Model.Meshes == nil {
// Assign model based on player ID for consistency
modelIndex := int(id) % len(g.Models)
other.Model = g.Models[modelIndex].Model
other.Texture = g.Models[modelIndex].Texture
}
g.DrawPlayer(other, other.Model)
} }
rl.EndMode3D() rl.EndMode3D()
@ -312,3 +348,7 @@ func (g *Game) Shutdown() {
rl.CloseWindow() rl.CloseWindow()
os.Exit(0) os.Exit(0)
} }
func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) {
g.Chat.HandleServerMessages(messages)
}

185
game/login.go Normal file
View File

@ -0,0 +1,185 @@
package game
import (
rl "github.com/gen2brain/raylib-go/raylib"
)
type LoginScreen struct {
username string
password string
errorMessage string
isRegistering bool
focusedField int // 0 = username, 1 = password
}
func NewLoginScreen() *LoginScreen {
return &LoginScreen{}
}
func (l *LoginScreen) Draw() {
screenWidth := float32(rl.GetScreenWidth())
screenHeight := float32(rl.GetScreenHeight())
// Draw background
rl.DrawRectangle(0, 0, int32(screenWidth), int32(screenHeight), rl.RayWhite)
// Draw title
title := "GoonScape"
if l.isRegistering {
title += " - Register"
} else {
title += " - Login"
}
titleSize := int32(40)
titleWidth := rl.MeasureText(title, titleSize)
rl.DrawText(title, int32(screenWidth/2)-titleWidth/2, 100, titleSize, rl.Black)
// Draw input fields
inputWidth := float32(200)
inputHeight := float32(30)
inputX := screenWidth/2 - inputWidth/2
// Username field
rl.DrawRectangleRec(rl.Rectangle{
X: inputX, Y: 200,
Width: inputWidth, Height: inputHeight,
}, rl.LightGray)
rl.DrawText(l.username, int32(inputX)+5, 205, 20, rl.Black)
if l.focusedField == 0 {
rl.DrawRectangleLinesEx(rl.Rectangle{
X: inputX - 2, Y: 198,
Width: inputWidth + 4, Height: inputHeight + 4,
}, 2, rl.Blue)
}
// Password field
rl.DrawRectangleRec(rl.Rectangle{
X: inputX, Y: 250,
Width: inputWidth, Height: inputHeight,
}, rl.LightGray)
masked := ""
for range l.password {
masked += "*"
}
rl.DrawText(masked, int32(inputX)+5, 255, 20, rl.Black)
if l.focusedField == 1 {
rl.DrawRectangleLinesEx(rl.Rectangle{
X: inputX - 2, Y: 248,
Width: inputWidth + 4, Height: inputHeight + 4,
}, 2, rl.Blue)
}
// Draw error message
if l.errorMessage != "" {
msgWidth := rl.MeasureText(l.errorMessage, 20)
rl.DrawText(l.errorMessage, int32(screenWidth/2)-msgWidth/2, 300, 20, rl.Red)
}
// Draw buttons
buttonWidth := float32(100)
buttonHeight := float32(30)
buttonY := float32(350)
// Login/Register button
actionBtn := rl.Rectangle{
X: screenWidth/2 - buttonWidth - 10,
Y: buttonY,
Width: buttonWidth,
Height: buttonHeight,
}
rl.DrawRectangleRec(actionBtn, rl.Blue)
actionText := "Login"
if l.isRegistering {
actionText = "Register"
}
actionWidth := rl.MeasureText(actionText, 20)
rl.DrawText(actionText,
int32(actionBtn.X+actionBtn.Width/2)-actionWidth/2,
int32(actionBtn.Y+5),
20, rl.White)
// Switch mode button
switchBtn := rl.Rectangle{
X: screenWidth/2 + 10,
Y: buttonY,
Width: buttonWidth,
Height: buttonHeight,
}
rl.DrawRectangleRec(switchBtn, rl.DarkGray)
switchText := "Register"
if l.isRegistering {
switchText = "Login"
}
switchWidth := rl.MeasureText(switchText, 20)
rl.DrawText(switchText,
int32(switchBtn.X+switchBtn.Width/2)-switchWidth/2,
int32(switchBtn.Y+5),
20, rl.White)
}
func (l *LoginScreen) Update() (string, string, bool, bool) {
// Handle input field focus
if rl.IsMouseButtonPressed(rl.MouseLeftButton) {
mousePos := rl.GetMousePosition()
screenWidth := float32(rl.GetScreenWidth())
inputWidth := float32(200)
inputX := screenWidth/2 - inputWidth/2
// Check username field
if mousePos.X >= inputX && mousePos.X <= inputX+inputWidth &&
mousePos.Y >= 200 && mousePos.Y <= 230 {
l.focusedField = 0
}
// Check password field
if mousePos.X >= inputX && mousePos.X <= inputX+inputWidth &&
mousePos.Y >= 250 && mousePos.Y <= 280 {
l.focusedField = 1
}
// Check buttons
buttonWidth := float32(100)
if mousePos.Y >= 350 && mousePos.Y <= 380 {
// Action button
if mousePos.X >= screenWidth/2-buttonWidth-10 &&
mousePos.X <= screenWidth/2-10 {
return l.username, l.password, l.isRegistering, true
}
// Switch mode button
if mousePos.X >= screenWidth/2+10 &&
mousePos.X <= screenWidth/2+buttonWidth+10 {
l.isRegistering = !l.isRegistering
}
}
}
// Handle text input
key := rl.GetCharPressed()
for key > 0 {
if l.focusedField == 0 && len(l.username) < 12 {
l.username += string(key)
} else if l.focusedField == 1 && len(l.password) < 20 {
l.password += string(key)
}
key = rl.GetCharPressed()
}
// Handle backspace
if rl.IsKeyPressed(rl.KeyBackspace) {
if l.focusedField == 0 && len(l.username) > 0 {
l.username = l.username[:len(l.username)-1]
} else if l.focusedField == 1 && len(l.password) > 0 {
l.password = l.password[:len(l.password)-1]
}
}
// Handle tab to switch fields
if rl.IsKeyPressed(rl.KeyTab) {
l.focusedField = (l.focusedField + 1) % 2
}
return "", "", false, false
}
func (l *LoginScreen) SetError(msg string) {
l.errorMessage = msg
}

2
go.mod
View File

@ -3,7 +3,7 @@ module gitea.boner.be/bdnugget/goonscape
go 1.23.0 go 1.23.0
require ( require (
gitea.boner.be/bdnugget/goonserver v0.0.0-20250113131525-49e23114973c gitea.boner.be/bdnugget/goonserver v1.1.0
github.com/gen2brain/raylib-go/raylib v0.0.0-20250109172833-6dbba4f81a9b github.com/gen2brain/raylib-go/raylib v0.0.0-20250109172833-6dbba4f81a9b
google.golang.org/protobuf v1.36.3 google.golang.org/protobuf v1.36.3
) )

37
main.go
View File

@ -11,20 +11,21 @@ import (
) )
func main() { func main() {
local := flag.Bool("local", false, "Use local server instead of remote") // Parse command line flags
addr := flag.String("addr", "boner.be:6969", "Server address (hostname:port or hostname)") local := flag.Bool("local", false, "Connect to local server")
addr := flag.String("addr", "", "Server address (host or host:port)")
flag.Parse() flag.Parse()
if *local && *addr != "boner.be:6969" { // Set server address based on flags
log.Fatal("Cannot use both -local and -addr flags")
}
if *local { if *local {
if *addr != "" {
log.Fatal("Cannot use -local and -addr together")
}
network.SetServerAddr("localhost:6969") network.SetServerAddr("localhost:6969")
} else if *addr != "" { } else if *addr != "" {
// If only hostname is provided, append default port // If port is not specified, append default port
if !strings.Contains(*addr, ":") { if !strings.Contains(*addr, ":") {
*addr = *addr + ":6969" *addr += ":6969"
} }
network.SetServerAddr(*addr) network.SetServerAddr(*addr)
} }
@ -32,36 +33,24 @@ func main() {
rl.InitWindow(1024, 768, "GoonScape") rl.InitWindow(1024, 768, "GoonScape")
rl.SetExitKey(0) rl.SetExitKey(0)
defer rl.CloseWindow() defer rl.CloseWindow()
rl.InitAudioDevice() rl.InitAudioDevice()
defer rl.CloseAudioDevice() defer rl.CloseAudioDevice()
rl.SetTargetFPS(60)
game := game.New() game := game.New()
if err := game.LoadAssets(); err != nil { if err := game.LoadAssets(); err != nil {
log.Fatalf("Failed to load assets: %v", err) log.Fatalf("Failed to load assets: %v", err)
} }
defer game.Cleanup() defer game.Cleanup()
conn, playerID, err := network.ConnectToServer()
if err != nil {
log.Fatalf("Failed to connect to server: %v", err)
}
defer conn.Close()
game.Player.ID = playerID
modelIndex := int(playerID) % len(game.Models)
game.Player.Model = game.Models[modelIndex].Model
game.Player.Texture = game.Models[modelIndex].Texture
go network.HandleServerCommunication(conn, playerID, game.Player, game.OtherPlayers, game.QuitChan)
rl.PlayMusicStream(game.Music) rl.PlayMusicStream(game.Music)
rl.SetMusicVolume(game.Music, 0.5) rl.SetMusicVolume(game.Music, 0.5)
rl.SetTargetFPS(60)
for !rl.WindowShouldClose() { for !rl.WindowShouldClose() {
rl.UpdateMusicStream(game.Music)
deltaTime := rl.GetFrameTime() deltaTime := rl.GetFrameTime()
rl.UpdateMusicStream(game.Music)
game.Update(deltaTime) game.Update(deltaTime)
game.Render() game.Render()
} }

View File

@ -3,56 +3,89 @@ package network
import ( import (
"bufio" "bufio"
"encoding/binary" "encoding/binary"
"fmt"
"io" "io"
"log" "log"
"net" "net"
"time" "time"
"gitea.boner.be/bdnugget/goonscape/game"
"gitea.boner.be/bdnugget/goonscape/types" "gitea.boner.be/bdnugget/goonscape/types"
pb "gitea.boner.be/bdnugget/goonserver/actions" pb "gitea.boner.be/bdnugget/goonserver/actions"
rl "github.com/gen2brain/raylib-go/raylib"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
const protoVersion = 1
var serverAddr = "boner.be:6969" var serverAddr = "boner.be:6969"
func SetServerAddr(addr string) { func SetServerAddr(addr string) {
serverAddr = addr serverAddr = addr
} }
func ConnectToServer() (net.Conn, int32, error) { func ConnectToServer(username, password string, isRegistering bool) (net.Conn, int32, error) {
conn, err := net.Dial("tcp", serverAddr) conn, err := net.Dial("tcp", serverAddr)
if err != nil { if err != nil {
log.Printf("Failed to dial server: %v", err) log.Printf("Failed to dial server: %v", err)
return nil, 0, err return nil, 0, err
} }
log.Println("Connected to server. Waiting for player ID...") log.Println("Connected to server. Authenticating...")
reader := bufio.NewReader(conn)
// Read message length (4 bytes) // Send auth message
authAction := &pb.Action{
Type: pb.Action_LOGIN,
Username: username,
Password: password,
}
if isRegistering {
authAction.Type = pb.Action_REGISTER
}
authBatch := &pb.ActionBatch{
Actions: []*pb.Action{authAction},
ProtocolVersion: protoVersion,
}
if err := writeMessage(conn, authBatch); err != nil {
conn.Close()
return nil, 0, fmt.Errorf("failed to send auth: %v", err)
}
// Read server response
reader := bufio.NewReader(conn)
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) conn.Close()
return nil, 0, err return nil, 0, fmt.Errorf("failed to read auth response: %v", err)
} }
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) conn.Close()
return nil, 0, err return nil, 0, fmt.Errorf("failed to read auth response body: %v", err)
} }
var response pb.ServerMessage var response pb.ServerMessage
if err := proto.Unmarshal(messageBuf, &response); err != nil { if err := proto.Unmarshal(messageBuf, &response); err != nil {
log.Printf("Failed to unmarshal server response: %v", err) conn.Close()
return nil, 0, err return nil, 0, fmt.Errorf("failed to unmarshal auth response: %v", err)
}
if response.ProtocolVersion > protoVersion {
conn.Close()
return nil, 0, fmt.Errorf("server requires newer protocol version (server: %d, client: %d)",
response.ProtocolVersion, protoVersion)
}
if !response.AuthSuccess {
conn.Close()
return nil, 0, fmt.Errorf(response.ErrorMessage)
} }
playerID := response.GetPlayerId() playerID := response.GetPlayerId()
log.Printf("Successfully connected with player ID: %d", playerID) log.Printf("Successfully authenticated with player ID: %d", playerID)
return conn, playerID, nil return conn, playerID, nil
} }
@ -67,6 +100,9 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
// Create a channel to signal when goroutines are done // Create a channel to signal when goroutines are done
done := make(chan struct{}) done := make(chan struct{})
// Create a set of current players to track disconnects
currentPlayers := make(map[int32]bool)
go func() { go func() {
for { for {
select { select {
@ -161,7 +197,19 @@ 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()
// Update initial position if not set
if player.PosActual.X == 0 && player.PosActual.Z == 0 {
player.PosActual = rl.Vector3{
X: float32(state.X * types.TileSize),
Y: 0,
Z: float32(state.Y * types.TileSize),
}
player.PosTile = types.Tile{X: int(state.X), Y: int(state.Y)}
}
player.Unlock()
continue continue
} }
@ -172,8 +220,15 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
} }
} }
if g, ok := player.UserData.(*game.Game); ok && len(serverMessage.ChatMessages) > 0 { // Remove players that are no longer in the server state
g.Chat.HandleServerMessages(serverMessage.ChatMessages) for id := range otherPlayers {
if !currentPlayers[id] {
delete(otherPlayers, id)
}
}
if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 {
handler.HandleServerMessages(serverMessage.ChatMessages)
} }
} }
} }

View File

@ -6,12 +6,15 @@ build() {
local arch=$2 local arch=$2
local output=$3 local output=$3
# Set CGO flags for static linking # Set GOOS and GOARCH for cross-compilation
export CGO_ENABLED=1
export GOOS=$os export GOOS=$os
export GOARCH=$arch export GOARCH=$arch
# Platform specific flags # Disable CGO only for cross-compilation
if [ "$os" != "$(go env GOOS)" ] || [ "$arch" != "$(go env GOARCH)" ]; then
export CGO_ENABLED=0
fi
if [ "$os" = "windows" ]; then if [ "$os" = "windows" ]; then
export CC=x86_64-w64-mingw32-gcc export CC=x86_64-w64-mingw32-gcc
export CXX=x86_64-w64-mingw32-g++ export CXX=x86_64-w64-mingw32-g++

View File

@ -39,6 +39,7 @@ type ModelAsset struct {
type ChatMessage struct { type ChatMessage struct {
PlayerID int32 PlayerID int32
Username string
Content string Content string
Time time.Time Time time.Time
} }
@ -49,6 +50,10 @@ type FloatingMessage struct {
ScreenPos rl.Vector2 // Store the screen position for 2D rendering ScreenPos rl.Vector2 // Store the screen position for 2D rendering
} }
type ChatMessageHandler interface {
HandleServerMessages([]*pb.ChatMessage)
}
const ( const (
MapWidth = 50 MapWidth = 50
MapHeight = 50 MapHeight = 50