Compare commits
12 Commits
2a0f9348e9
...
testbuild-
Author | SHA1 | Date | |
---|---|---|---|
0f56916295 | |||
a1ddbadea0 | |||
e4d0b98945 | |||
509bc8b20b | |||
c40e4ae7ac | |||
863f5a939c | |||
cd68581429 | |||
b9d0d46bd6 | |||
b96c7ada7a | |||
d86cbe15a3 | |||
fb018e2a7d | |||
5ca973fdf1 |
30
Dockerfile.build
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
FROM golang:1.23
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN 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 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy build scripts
|
||||||
|
COPY . /build/
|
||||||
|
|
||||||
|
# Set execute permissions
|
||||||
|
RUN chmod +x /build/scripts/build.sh
|
||||||
|
|
||||||
|
# Build command
|
||||||
|
CMD ["make", "all"]
|
30
Makefile
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
.PHONY: all clean windows linux darwin
|
||||||
|
|
||||||
|
include scripts/platforms.mk
|
||||||
|
|
||||||
|
BINARY_NAME=goonscape
|
||||||
|
VERSION=1.0.0
|
||||||
|
BUILD_DIR=build
|
||||||
|
ASSETS_DIR=resources
|
||||||
|
|
||||||
|
all: clean $(PLATFORMS)
|
||||||
|
|
||||||
|
$(PLATFORMS):
|
||||||
|
@echo "Building for $@..."
|
||||||
|
@mkdir -p $(BUILD_DIR)/$@
|
||||||
|
@scripts/build.sh $(word 1,$(subst /, ,$@)) $(word 2,$(subst /, ,$@)) \
|
||||||
|
$(BUILD_DIR)/$@/$(BINARY_NAME)$(if $(findstring windows,$@),.exe,)
|
||||||
|
@cp -r $(ASSETS_DIR) $(BUILD_DIR)/$@/
|
||||||
|
@cd $(BUILD_DIR) && zip -r $(BINARY_NAME)-$(word 1,$(subst /, ,$@))-$(word 2,$(subst /, ,$@))-v$(VERSION).zip $@
|
||||||
|
@echo "Done building for $@"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR)
|
||||||
|
|
||||||
|
# Development build for current platform
|
||||||
|
dev:
|
||||||
|
go build -o $(BINARY_NAME)
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
test:
|
||||||
|
go test ./...
|
22
README.md
@ -41,7 +41,7 @@ go run main.go
|
|||||||
- **Mouse Click**: Move to location
|
- **Mouse Click**: Move to location
|
||||||
- **T**: Open chat
|
- **T**: Open chat
|
||||||
- **Enter**: Send chat message
|
- **Enter**: Send chat message
|
||||||
- **Escape**: Cancel chat/Close game
|
- **Escape**: Cancel chat/Close game (it does both of these at the same time so gg)
|
||||||
- **Arrow Keys**: Rotate camera
|
- **Arrow Keys**: Rotate camera
|
||||||
- **Mouse Wheel**: Zoom in/out
|
- **Mouse Wheel**: Zoom in/out
|
||||||
|
|
||||||
@ -63,6 +63,26 @@ go run main.go -addr somehost:6970 # Uses somehost:6970
|
|||||||
|
|
||||||
Note: The `-local` flag is a shorthand for `-addr localhost:6969` and cannot be used together with `-addr`.
|
Note: The `-local` flag is a shorthand for `-addr localhost:6969` and cannot be used together with `-addr`.
|
||||||
|
|
||||||
|
## Building Release Binaries
|
||||||
|
|
||||||
|
The project uses Docker to create consistent builds across platforms. To build release binaries:
|
||||||
|
|
||||||
|
1. Build the Docker image (only needed once):
|
||||||
|
```bash
|
||||||
|
sudo docker build -t goonscape-builder -f Dockerfile.build .
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create release builds:
|
||||||
|
```bash
|
||||||
|
sudo docker run -v $(pwd):/build goonscape-builder
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create zip files in the `build` directory for:
|
||||||
|
- Windows (64-bit): `goonscape-windows-amd64-v1.0.0.zip`
|
||||||
|
- Linux (64-bit): `goonscape-linux-amd64-v1.0.0.zip`
|
||||||
|
|
||||||
|
Each zip contains the binary and all required assets.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
The project uses Protocol Buffers for network communication. If you modify the `.proto` files, regenerate the Go code with:
|
The project uses Protocol Buffers for network communication. If you modify the `.proto` files, regenerate the Go code with:
|
||||||
|
BIN
build/goonscape-linux-amd64-v1.0.0.zip
Normal file
BIN
build/goonscape-windows-amd64-v1.0.0.zip
Normal file
BIN
build/linux/amd64/goonscape
Executable file
BIN
build/linux/amd64/resources/audio/GoonScape1.mp3
Normal file
BIN
build/linux/amd64/resources/audio/GoonScape2.mp3
Normal file
12
build/linux/amd64/resources/models/coomer.mtl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
117248
build/linux/amd64/resources/models/coomer.obj
Normal file
BIN
build/linux/amd64/resources/models/coomer.png
Normal file
After Width: | Height: | Size: 2.2 MiB |
12
build/linux/amd64/resources/models/goonion.mtl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
112531
build/linux/amd64/resources/models/goonion.obj
Normal file
BIN
build/linux/amd64/resources/models/goonion.png
Normal file
After Width: | Height: | Size: 2.5 MiB |
12
build/linux/amd64/resources/models/shreke.mtl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
210035
build/linux/amd64/resources/models/shreke.obj
Normal file
BIN
build/linux/amd64/resources/models/shreke.png
Normal file
After Width: | Height: | Size: 4.8 MiB |
BIN
build/linux/amd64/resources/screenshot.png
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
build/windows/amd64/goonscape.exe
Executable file
BIN
build/windows/amd64/resources/audio/GoonScape1.mp3
Normal file
BIN
build/windows/amd64/resources/audio/GoonScape2.mp3
Normal file
12
build/windows/amd64/resources/models/coomer.mtl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
117248
build/windows/amd64/resources/models/coomer.obj
Normal file
BIN
build/windows/amd64/resources/models/coomer.png
Normal file
After Width: | Height: | Size: 2.2 MiB |
12
build/windows/amd64/resources/models/goonion.mtl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
112531
build/windows/amd64/resources/models/goonion.obj
Normal file
BIN
build/windows/amd64/resources/models/goonion.png
Normal file
After Width: | Height: | Size: 2.5 MiB |
12
build/windows/amd64/resources/models/shreke.mtl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
210035
build/windows/amd64/resources/models/shreke.obj
Normal file
BIN
build/windows/amd64/resources/models/shreke.png
Normal file
After Width: | Height: | Size: 4.8 MiB |
BIN
build/windows/amd64/resources/screenshot.png
Normal file
After Width: | Height: | Size: 104 KiB |
@ -168,13 +168,12 @@ func (c *Chat) Update() (string, bool) {
|
|||||||
c.inputBuffer = c.inputBuffer[:0]
|
c.inputBuffer = c.inputBuffer[:0]
|
||||||
c.cursorPos = 0
|
c.cursorPos = 0
|
||||||
c.isTyping = false
|
c.isTyping = false
|
||||||
|
|
||||||
return message, true
|
return message, true
|
||||||
}
|
}
|
||||||
c.isTyping = false
|
c.isTyping = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if rl.IsKeyPressed(rl.KeyEscape) {
|
if rl.IsKeyPressed(rl.KeyEscape) && c.isTyping {
|
||||||
c.inputBuffer = c.inputBuffer[:0]
|
c.inputBuffer = c.inputBuffer[:0]
|
||||||
c.cursorPos = 0
|
c.cursorPos = 0
|
||||||
c.isTyping = false
|
c.isTyping = false
|
||||||
|
99
game/game.go
@ -1,6 +1,7 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/assets"
|
"gitea.boner.be/bdnugget/goonscape/assets"
|
||||||
@ -16,6 +17,8 @@ type Game struct {
|
|||||||
Models []types.ModelAsset
|
Models []types.ModelAsset
|
||||||
Music rl.Music
|
Music rl.Music
|
||||||
Chat *Chat
|
Chat *Chat
|
||||||
|
MenuOpen bool
|
||||||
|
QuitChan chan struct{} // Channel to signal shutdown
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Game {
|
func New() *Game {
|
||||||
@ -27,6 +30,7 @@ func New() *Game {
|
|||||||
Speed: 50.0,
|
Speed: 50.0,
|
||||||
TargetPath: []types.Tile{},
|
TargetPath: []types.Tile{},
|
||||||
UserData: nil,
|
UserData: nil,
|
||||||
|
QuitDone: make(chan struct{}),
|
||||||
},
|
},
|
||||||
OtherPlayers: make(map[int32]*types.Player),
|
OtherPlayers: make(map[int32]*types.Player),
|
||||||
Camera: rl.Camera3D{
|
Camera: rl.Camera3D{
|
||||||
@ -36,7 +40,8 @@ func New() *Game {
|
|||||||
Fovy: 45.0,
|
Fovy: 45.0,
|
||||||
Projection: rl.CameraPerspective,
|
Projection: rl.CameraPerspective,
|
||||||
},
|
},
|
||||||
Chat: NewChat(),
|
Chat: NewChat(),
|
||||||
|
QuitChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
game.Player.UserData = game
|
game.Player.UserData = game
|
||||||
game.Chat.userData = game
|
game.Chat.userData = game
|
||||||
@ -59,6 +64,17 @@ func (g *Game) LoadAssets() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Update(deltaTime float32) {
|
func (g *Game) Update(deltaTime float32) {
|
||||||
|
// Handle ESC for menu
|
||||||
|
if rl.IsKeyPressed(rl.KeyEscape) {
|
||||||
|
g.MenuOpen = !g.MenuOpen
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't process other inputs if menu is open
|
||||||
|
if g.MenuOpen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if message, sent := g.Chat.Update(); sent {
|
if message, sent := g.Chat.Update(); sent {
|
||||||
g.Player.Lock()
|
g.Player.Lock()
|
||||||
g.Player.ActionQueue = append(g.Player.ActionQueue, &pb.Action{
|
g.Player.ActionQueue = append(g.Player.ActionQueue, &pb.Action{
|
||||||
@ -125,7 +141,7 @@ func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
|
|||||||
|
|
||||||
rl.DrawModel(model, playerPos, 16, rl.White)
|
rl.DrawModel(model, playerPos, 16, rl.White)
|
||||||
|
|
||||||
if player.FloatingMessage != nil && time.Now().Before(player.FloatingMessage.ExpireTime) {
|
if player.FloatingMessage != nil {
|
||||||
screenPos := rl.GetWorldToScreen(rl.Vector3{
|
screenPos := rl.GetWorldToScreen(rl.Vector3{
|
||||||
X: playerPos.X,
|
X: playerPos.X,
|
||||||
Y: playerPos.Y + 24.0,
|
Y: playerPos.Y + 24.0,
|
||||||
@ -133,8 +149,6 @@ func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
|
|||||||
}, g.Camera)
|
}, g.Camera)
|
||||||
|
|
||||||
player.FloatingMessage.ScreenPos = screenPos
|
player.FloatingMessage.ScreenPos = screenPos
|
||||||
} else if player.FloatingMessage != nil {
|
|
||||||
player.FloatingMessage = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(player.TargetPath) > 0 {
|
if len(player.TargetPath) > 0 {
|
||||||
@ -168,6 +182,7 @@ func (g *Game) Render() {
|
|||||||
}
|
}
|
||||||
rl.EndMode3D()
|
rl.EndMode3D()
|
||||||
|
|
||||||
|
// Draw floating messages
|
||||||
drawFloatingMessage := func(msg *types.FloatingMessage) {
|
drawFloatingMessage := func(msg *types.FloatingMessage) {
|
||||||
if msg == nil || time.Now().After(msg.ExpireTime) {
|
if msg == nil || time.Now().After(msg.ExpireTime) {
|
||||||
return
|
return
|
||||||
@ -196,8 +211,17 @@ func (g *Game) Render() {
|
|||||||
drawFloatingMessage(other.FloatingMessage)
|
drawFloatingMessage(other.FloatingMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw menu if open
|
||||||
|
if g.MenuOpen {
|
||||||
|
g.DrawMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only draw chat if menu is not open
|
||||||
|
if !g.MenuOpen {
|
||||||
|
g.Chat.Draw(int32(rl.GetScreenWidth()), int32(rl.GetScreenHeight()))
|
||||||
|
}
|
||||||
|
|
||||||
rl.DrawFPS(10, 10)
|
rl.DrawFPS(10, 10)
|
||||||
g.Chat.Draw(int32(rl.GetScreenWidth()), int32(rl.GetScreenHeight()))
|
|
||||||
rl.EndDrawing()
|
rl.EndDrawing()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,3 +247,68 @@ func (g *Game) HandleInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Game) DrawMenu() {
|
||||||
|
screenWidth := float32(rl.GetScreenWidth())
|
||||||
|
screenHeight := float32(rl.GetScreenHeight())
|
||||||
|
|
||||||
|
// Semi-transparent background
|
||||||
|
rl.DrawRectangle(0, 0, int32(screenWidth), int32(screenHeight), rl.ColorAlpha(rl.Black, 0.7))
|
||||||
|
|
||||||
|
// Menu title
|
||||||
|
title := "Menu"
|
||||||
|
titleSize := int32(40)
|
||||||
|
titleWidth := rl.MeasureText(title, titleSize)
|
||||||
|
rl.DrawText(title, int32(screenWidth/2)-titleWidth/2, 100, titleSize, rl.White)
|
||||||
|
|
||||||
|
// Menu buttons
|
||||||
|
buttonWidth := float32(200)
|
||||||
|
buttonHeight := float32(40)
|
||||||
|
buttonY := float32(200)
|
||||||
|
buttonSpacing := float32(60)
|
||||||
|
|
||||||
|
menuItems := []string{"Resume", "Settings", "Exit Game"}
|
||||||
|
for _, item := range menuItems {
|
||||||
|
buttonRect := rl.Rectangle{
|
||||||
|
X: screenWidth/2 - buttonWidth/2,
|
||||||
|
Y: buttonY,
|
||||||
|
Width: buttonWidth,
|
||||||
|
Height: buttonHeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check mouse hover
|
||||||
|
mousePoint := rl.GetMousePosition()
|
||||||
|
mouseHover := rl.CheckCollisionPointRec(mousePoint, buttonRect)
|
||||||
|
|
||||||
|
// Draw button
|
||||||
|
if mouseHover {
|
||||||
|
rl.DrawRectangleRec(buttonRect, rl.ColorAlpha(rl.White, 0.3))
|
||||||
|
if rl.IsMouseButtonPressed(rl.MouseLeftButton) {
|
||||||
|
switch item {
|
||||||
|
case "Resume":
|
||||||
|
g.MenuOpen = false
|
||||||
|
case "Settings":
|
||||||
|
// TODO: Implement settings
|
||||||
|
case "Exit Game":
|
||||||
|
g.Shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw button text
|
||||||
|
textSize := int32(20)
|
||||||
|
textWidth := rl.MeasureText(item, textSize)
|
||||||
|
textX := int32(buttonRect.X+buttonRect.Width/2) - textWidth/2
|
||||||
|
textY := int32(buttonRect.Y + buttonRect.Height/2 - float32(textSize)/2)
|
||||||
|
rl.DrawText(item, textX, textY, textSize, rl.White)
|
||||||
|
|
||||||
|
buttonY += buttonSpacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) Shutdown() {
|
||||||
|
close(g.QuitChan)
|
||||||
|
<-g.Player.QuitDone
|
||||||
|
rl.CloseWindow()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
4
go.mod
@ -5,7 +5,7 @@ go 1.23.0
|
|||||||
require (
|
require (
|
||||||
gitea.boner.be/bdnugget/goonserver v0.0.0-20250113131525-49e23114973c
|
gitea.boner.be/bdnugget/goonserver v0.0.0-20250113131525-49e23114973c
|
||||||
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.2
|
google.golang.org/protobuf v1.36.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -14,4 +14,4 @@ require (
|
|||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
// replace gitea.boner.be/bdnugget/goonserver => ./goonserver
|
replace gitea.boner.be/bdnugget/goonserver => ./goonserver
|
||||||
|
16
go.sum
@ -1,24 +1,12 @@
|
|||||||
gitea.boner.be/bdnugget/goonserver v0.0.0-20250113131525-49e23114973c h1:TO14y5QeQXn6sLCv6vORVdjnMn5hP/Vd+60UjqcrtFA=
|
|
||||||
gitea.boner.be/bdnugget/goonserver v0.0.0-20250113131525-49e23114973c/go.mod h1:inR1bKrr/vcTba+G1KzmmY6vssMq9oGNOk836VwPa4c=
|
|
||||||
github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE=
|
|
||||||
github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
|
||||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/gen2brain/raylib-go/raylib v0.0.0-20240930075631-c66f9e2942fe h1:mInjrbJkUglTM7tBmXG+epnPCE744aj15J7vjJwM4gs=
|
|
||||||
github.com/gen2brain/raylib-go/raylib v0.0.0-20240930075631-c66f9e2942fe/go.mod h1:BaY76bZk7nw1/kVOSQObPY1v1iwVE1KHAGMfvI6oK1Q=
|
|
||||||
github.com/gen2brain/raylib-go/raylib v0.0.0-20250109172833-6dbba4f81a9b h1:JJfspevP3YOXcSKVABizYOv++yMpTJIdPUtoDzF/RWw=
|
github.com/gen2brain/raylib-go/raylib v0.0.0-20250109172833-6dbba4f81a9b h1:JJfspevP3YOXcSKVABizYOv++yMpTJIdPUtoDzF/RWw=
|
||||||
github.com/gen2brain/raylib-go/raylib v0.0.0-20250109172833-6dbba4f81a9b/go.mod h1:BaY76bZk7nw1/kVOSQObPY1v1iwVE1KHAGMfvI6oK1Q=
|
github.com/gen2brain/raylib-go/raylib v0.0.0-20250109172833-6dbba4f81a9b/go.mod h1:BaY76bZk7nw1/kVOSQObPY1v1iwVE1KHAGMfvI6oK1Q=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
||||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
|
|
||||||
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
|
6
main.go
@ -30,6 +30,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rl.InitWindow(1024, 768, "GoonScape")
|
rl.InitWindow(1024, 768, "GoonScape")
|
||||||
|
rl.SetExitKey(0)
|
||||||
defer rl.CloseWindow()
|
defer rl.CloseWindow()
|
||||||
rl.InitAudioDevice()
|
rl.InitAudioDevice()
|
||||||
defer rl.CloseAudioDevice()
|
defer rl.CloseAudioDevice()
|
||||||
@ -51,7 +52,7 @@ func main() {
|
|||||||
game.Player.Model = game.Models[modelIndex].Model
|
game.Player.Model = game.Models[modelIndex].Model
|
||||||
game.Player.Texture = game.Models[modelIndex].Texture
|
game.Player.Texture = game.Models[modelIndex].Texture
|
||||||
|
|
||||||
go network.HandleServerCommunication(conn, playerID, game.Player, game.OtherPlayers)
|
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)
|
||||||
@ -64,4 +65,7 @@ func main() {
|
|||||||
game.Update(deltaTime)
|
game.Update(deltaTime)
|
||||||
game.Render()
|
game.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for clean shutdown
|
||||||
|
<-game.QuitChan
|
||||||
}
|
}
|
||||||
|
@ -56,89 +56,125 @@ func ConnectToServer() (net.Conn, int32, error) {
|
|||||||
return conn, playerID, nil
|
return conn, playerID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers map[int32]*types.Player) {
|
func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Player, otherPlayers map[int32]*types.Player, quitChan <-chan struct{}) {
|
||||||
// Create a buffered reader for the connection
|
|
||||||
reader := bufio.NewReader(conn)
|
reader := bufio.NewReader(conn)
|
||||||
|
|
||||||
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
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for range actionTicker.C {
|
for {
|
||||||
player.Lock()
|
select {
|
||||||
if len(player.ActionQueue) > 0 {
|
case <-quitChan:
|
||||||
actions := make([]*pb.Action, len(player.ActionQueue))
|
// Send disconnect message to server
|
||||||
copy(actions, player.ActionQueue)
|
disconnectMsg := &pb.ActionBatch{
|
||||||
|
|
||||||
batch := &pb.ActionBatch{
|
|
||||||
PlayerId: playerID,
|
PlayerId: playerID,
|
||||||
Actions: actions,
|
Actions: []*pb.Action{{
|
||||||
Tick: player.CurrentTick,
|
Type: pb.Action_DISCONNECT,
|
||||||
|
PlayerId: playerID,
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
|
writeMessage(conn, disconnectMsg)
|
||||||
|
done <- struct{}{}
|
||||||
|
return
|
||||||
|
case <-actionTicker.C:
|
||||||
|
player.Lock()
|
||||||
|
if len(player.ActionQueue) > 0 {
|
||||||
|
actions := make([]*pb.Action, len(player.ActionQueue))
|
||||||
|
copy(actions, player.ActionQueue)
|
||||||
|
|
||||||
player.ActionQueue = player.ActionQueue[:0]
|
batch := &pb.ActionBatch{
|
||||||
player.Unlock()
|
PlayerId: playerID,
|
||||||
|
Actions: actions,
|
||||||
|
Tick: player.CurrentTick,
|
||||||
|
}
|
||||||
|
|
||||||
if err := writeMessage(conn, batch); err != nil {
|
player.ActionQueue = player.ActionQueue[:0]
|
||||||
log.Printf("Failed to send actions to server: %v", err)
|
player.Unlock()
|
||||||
return
|
|
||||||
|
if err := writeMessage(conn, batch); err != nil {
|
||||||
|
log.Printf("Failed to send actions to server: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
player.Unlock()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
player.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Read message length (4 bytes)
|
select {
|
||||||
lengthBuf := make([]byte, 4)
|
case <-quitChan:
|
||||||
if _, err := io.ReadFull(reader, lengthBuf); err != nil {
|
done := make(chan struct{})
|
||||||
log.Printf("Failed to read message length: %v", err)
|
go func() {
|
||||||
return
|
<-done
|
||||||
}
|
close(player.QuitDone)
|
||||||
messageLength := binary.BigEndian.Uint32(lengthBuf)
|
}()
|
||||||
|
|
||||||
// Read the full message
|
select {
|
||||||
messageBuf := make([]byte, messageLength)
|
case <-done:
|
||||||
if _, err := io.ReadFull(reader, messageBuf); err != nil {
|
time.Sleep(100 * time.Millisecond)
|
||||||
log.Printf("Failed to read message body: %v", err)
|
case <-time.After(1 * time.Second):
|
||||||
return
|
log.Println("Shutdown timed out")
|
||||||
}
|
|
||||||
|
|
||||||
var serverMessage pb.ServerMessage
|
|
||||||
if err := proto.Unmarshal(messageBuf, &serverMessage); err != nil {
|
|
||||||
log.Printf("Failed to unmarshal server message: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
player.Lock()
|
|
||||||
player.CurrentTick = serverMessage.CurrentTick
|
|
||||||
|
|
||||||
tickDiff := serverMessage.CurrentTick - player.CurrentTick
|
|
||||||
if tickDiff > types.MaxTickDesync {
|
|
||||||
for _, state := range serverMessage.Players {
|
|
||||||
if state.PlayerId == playerID {
|
|
||||||
player.ForceResync(state)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
return
|
||||||
player.Unlock()
|
default:
|
||||||
|
// Read message length (4 bytes)
|
||||||
|
lengthBuf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadFull(reader, lengthBuf); err != nil {
|
||||||
|
log.Printf("Failed to read message length: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
messageLength := binary.BigEndian.Uint32(lengthBuf)
|
||||||
|
|
||||||
for _, state := range serverMessage.Players {
|
// Read the full message
|
||||||
if state.PlayerId == playerID {
|
messageBuf := make([]byte, messageLength)
|
||||||
|
if _, err := io.ReadFull(reader, messageBuf); err != nil {
|
||||||
|
log.Printf("Failed to read message body: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverMessage pb.ServerMessage
|
||||||
|
if err := proto.Unmarshal(messageBuf, &serverMessage); err != nil {
|
||||||
|
log.Printf("Failed to unmarshal server message: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
|
player.Lock()
|
||||||
otherPlayer.UpdatePosition(state, types.ServerTickRate)
|
player.CurrentTick = serverMessage.CurrentTick
|
||||||
} else {
|
|
||||||
otherPlayers[state.PlayerId] = types.NewPlayer(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if g, ok := player.UserData.(*game.Game); ok && len(serverMessage.ChatMessages) > 0 {
|
tickDiff := serverMessage.CurrentTick - player.CurrentTick
|
||||||
g.Chat.HandleServerMessages(serverMessage.ChatMessages)
|
if tickDiff > types.MaxTickDesync {
|
||||||
|
for _, state := range serverMessage.Players {
|
||||||
|
if state.PlayerId == playerID {
|
||||||
|
player.ForceResync(state)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
player.Unlock()
|
||||||
|
|
||||||
|
for _, state := range serverMessage.Players {
|
||||||
|
if state.PlayerId == playerID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
|
||||||
|
otherPlayer.UpdatePosition(state, types.ServerTickRate)
|
||||||
|
} else {
|
||||||
|
otherPlayers[state.PlayerId] = types.NewPlayer(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if g, ok := player.UserData.(*game.Game); ok && len(serverMessage.ChatMessages) > 0 {
|
||||||
|
g.Chat.HandleServerMessages(serverMessage.ChatMessages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
scripts/build.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Main build process
|
||||||
|
build() {
|
||||||
|
local os=$1
|
||||||
|
local arch=$2
|
||||||
|
local output=$3
|
||||||
|
|
||||||
|
# Set CGO flags for static linking
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
export GOOS=$os
|
||||||
|
export GOARCH=$arch
|
||||||
|
|
||||||
|
# Platform specific flags
|
||||||
|
if [ "$os" = "windows" ]; then
|
||||||
|
export CC=x86_64-w64-mingw32-gcc
|
||||||
|
export CXX=x86_64-w64-mingw32-g++
|
||||||
|
fi
|
||||||
|
|
||||||
|
go build -buildvcs=false -ldflags="-s -w" -o $output
|
||||||
|
}
|
||||||
|
|
||||||
|
# Call build with provided arguments
|
||||||
|
build "$1" "$2" "$3"
|
1
scripts/platforms.mk
Normal file
@ -0,0 +1 @@
|
|||||||
|
PLATFORMS=windows/amd64 linux/amd64
|
@ -27,8 +27,9 @@ type Player struct {
|
|||||||
CurrentTick int64
|
CurrentTick int64
|
||||||
LastUpdateTime time.Time
|
LastUpdateTime time.Time
|
||||||
InterpolationProgress float32
|
InterpolationProgress float32
|
||||||
UserData interface{} // Used to store reference to game
|
UserData interface{}
|
||||||
FloatingMessage *FloatingMessage
|
FloatingMessage *FloatingMessage
|
||||||
|
QuitDone chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModelAsset struct {
|
type ModelAsset struct {
|
||||||
|