Compare commits
5 Commits
feature/ch
...
b73d8de851
Author | SHA1 | Date | |
---|---|---|---|
b73d8de851 | |||
23474c19dc | |||
a48fef0186 | |||
67e08c5d1e | |||
49e2311497 |
75
README.md
Normal file
75
README.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# GoonServer
|
||||||
|
|
||||||
|
The server component for GoonScape, handling multiplayer synchronization and chat.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Tick-based game state synchronization
|
||||||
|
- Player movement validation
|
||||||
|
- Global chat system
|
||||||
|
- Client connection management
|
||||||
|
- Protobuf-based network protocol
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Go 1.23 or higher
|
||||||
|
- Protocol Buffers compiler (protoc)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.boner.be/bdnugget/goonserver.git
|
||||||
|
cd goonserver
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
go mod tidy
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build and run:
|
||||||
|
```bash
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Server settings can be modified in `main.go`:
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
port = ":6969" // Port to listen on
|
||||||
|
tickRate = 600 * time.Millisecond // Server tick rate
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protocol
|
||||||
|
|
||||||
|
The server uses Protocol Buffers for client-server communication. The protocol is defined in `actions/actions.proto`:
|
||||||
|
|
||||||
|
- `Action`: Player actions (movement, chat)
|
||||||
|
- `ActionBatch`: Grouped actions from a player
|
||||||
|
- `ServerMessage`: Game state updates to clients
|
||||||
|
- `PlayerState`: Individual player state
|
||||||
|
- `ChatMessage`: Player chat messages
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
After modifying the protocol (`actions.proto`), regenerate the Go code:
|
||||||
|
```bash
|
||||||
|
protoc --go_out=. actions/actions.proto
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
The server is designed to run on a single instance. For production deployment:
|
||||||
|
|
||||||
|
1. Build the binary:
|
||||||
|
```bash
|
||||||
|
go build -o goonserver
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run with logging:
|
||||||
|
```bash
|
||||||
|
./goonserver > server.log 2>&1 &
|
||||||
|
```
|
77
main.go
77
main.go
@ -1,7 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
@ -40,6 +43,11 @@ func main() {
|
|||||||
defer ln.Close()
|
defer ln.Close()
|
||||||
fmt.Printf("Server is listening on port %s\n", port)
|
fmt.Printf("Server is listening on port %s\n", port)
|
||||||
|
|
||||||
|
// Create ticker for fixed game state updates
|
||||||
|
ticker := time.NewTicker(tickRate)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
// Handle incoming connections in a separate goroutine
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
conn, err := ln.Accept()
|
conn, err := ln.Accept()
|
||||||
@ -51,13 +59,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lastTick := time.Now()
|
// Main game loop
|
||||||
for {
|
for range ticker.C {
|
||||||
if time.Since(lastTick) >= tickRate {
|
|
||||||
lastTick = time.Now()
|
|
||||||
processActions()
|
processActions()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnection(conn net.Conn) {
|
func handleConnection(conn net.Conn) {
|
||||||
@ -76,22 +81,29 @@ func handleConnection(conn net.Conn) {
|
|||||||
PlayerId: int32(playerID),
|
PlayerId: int32(playerID),
|
||||||
CurrentTick: 0,
|
CurrentTick: 0,
|
||||||
}
|
}
|
||||||
data, err := proto.Marshal(serverMsg)
|
if err := writeMessage(conn, serverMsg); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to marshal ServerMessage for player %d: %v", playerID, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := conn.Write(data); err != nil {
|
|
||||||
log.Printf("Failed to send player ID to player %d: %v", playerID, err)
|
log.Printf("Failed to send player ID to player %d: %v", playerID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for incoming actions from this player
|
// Listen for incoming actions from this player
|
||||||
buf := make([]byte, 4096)
|
reader := bufio.NewReader(conn)
|
||||||
for {
|
for {
|
||||||
n, err := conn.Read(buf)
|
// Read message length
|
||||||
if err != nil {
|
lengthBuf := make([]byte, 4)
|
||||||
log.Printf("Error reading from player %d: %v", playerID, err)
|
if _, err := io.ReadFull(reader, lengthBuf); err != nil {
|
||||||
|
log.Printf("Error reading message length from player %d: %v", playerID, err)
|
||||||
|
delete(players, playerID)
|
||||||
|
delete(playerConns, playerID)
|
||||||
|
delete(actionQueue, playerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
messageLength := binary.BigEndian.Uint32(lengthBuf)
|
||||||
|
|
||||||
|
// Read message body
|
||||||
|
messageBuf := make([]byte, messageLength)
|
||||||
|
if _, err := io.ReadFull(reader, messageBuf); err != nil {
|
||||||
|
log.Printf("Error reading message from player %d: %v", playerID, err)
|
||||||
delete(players, playerID)
|
delete(players, playerID)
|
||||||
delete(playerConns, playerID)
|
delete(playerConns, playerID)
|
||||||
delete(actionQueue, playerID)
|
delete(actionQueue, playerID)
|
||||||
@ -99,7 +111,7 @@ func handleConnection(conn net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
batch := &pb.ActionBatch{}
|
batch := &pb.ActionBatch{}
|
||||||
if err := proto.Unmarshal(buf[:n], batch); err != nil {
|
if err := proto.Unmarshal(messageBuf, batch); err != nil {
|
||||||
log.Printf("Failed to unmarshal action batch for player %d: %v", playerID, err)
|
log.Printf("Failed to unmarshal action batch for player %d: %v", playerID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -154,8 +166,10 @@ func processActions() {
|
|||||||
currentTick := time.Now().UnixNano() / int64(tickRate)
|
currentTick := time.Now().UnixNano() / int64(tickRate)
|
||||||
state := &pb.ServerMessage{
|
state := &pb.ServerMessage{
|
||||||
CurrentTick: currentTick,
|
CurrentTick: currentTick,
|
||||||
|
Players: make([]*pb.PlayerState, 0, len(players)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert players to PlayerState
|
||||||
for id, p := range players {
|
for id, p := range players {
|
||||||
p.Lock()
|
p.Lock()
|
||||||
state.Players = append(state.Players, &pb.PlayerState{
|
state.Players = append(state.Players, &pb.PlayerState{
|
||||||
@ -166,21 +180,34 @@ func processActions() {
|
|||||||
p.Unlock()
|
p.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add chat messages to the server message
|
// Add chat messages to the state
|
||||||
chatMutex.RLock()
|
chatMutex.RLock()
|
||||||
state.ChatMessages = chatHistory
|
state.ChatMessages = chatHistory[max(0, len(chatHistory)-5):] // Only send last 5 messages
|
||||||
chatMutex.RUnlock()
|
chatMutex.RUnlock()
|
||||||
|
|
||||||
data, err := proto.Marshal(state)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to marshal game state: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send to each connected player
|
// Send to each connected player
|
||||||
for _, conn := range playerConns {
|
for _, conn := range playerConns {
|
||||||
if _, err := conn.Write(data); err != nil {
|
if err := writeMessage(conn, state); err != nil {
|
||||||
log.Printf("Failed to send update: %v", err)
|
log.Printf("Failed to send update: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to write length-prefixed messages
|
||||||
|
func writeMessage(conn net.Conn, msg proto.Message) error {
|
||||||
|
data, err := proto.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write length prefix
|
||||||
|
lengthBuf := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(lengthBuf, uint32(len(data)))
|
||||||
|
if _, err := conn.Write(lengthBuf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write message body
|
||||||
|
_, err = conn.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user