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
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
@ -40,6 +43,11 @@ func main() {
|
||||
defer ln.Close()
|
||||
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() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
@ -51,13 +59,10 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
lastTick := time.Now()
|
||||
for {
|
||||
if time.Since(lastTick) >= tickRate {
|
||||
lastTick = time.Now()
|
||||
// Main game loop
|
||||
for range ticker.C {
|
||||
processActions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleConnection(conn net.Conn) {
|
||||
@ -76,22 +81,29 @@ func handleConnection(conn net.Conn) {
|
||||
PlayerId: int32(playerID),
|
||||
CurrentTick: 0,
|
||||
}
|
||||
data, err := proto.Marshal(serverMsg)
|
||||
if err != nil {
|
||||
log.Printf("Failed to marshal ServerMessage for player %d: %v", playerID, err)
|
||||
return
|
||||
}
|
||||
if _, err := conn.Write(data); err != nil {
|
||||
if err := writeMessage(conn, serverMsg); err != nil {
|
||||
log.Printf("Failed to send player ID to player %d: %v", playerID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Listen for incoming actions from this player
|
||||
buf := make([]byte, 4096)
|
||||
reader := bufio.NewReader(conn)
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
log.Printf("Error reading from player %d: %v", playerID, err)
|
||||
// Read message length
|
||||
lengthBuf := make([]byte, 4)
|
||||
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(playerConns, playerID)
|
||||
delete(actionQueue, playerID)
|
||||
@ -99,7 +111,7 @@ func handleConnection(conn net.Conn) {
|
||||
}
|
||||
|
||||
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)
|
||||
continue
|
||||
}
|
||||
@ -154,8 +166,10 @@ func processActions() {
|
||||
currentTick := time.Now().UnixNano() / int64(tickRate)
|
||||
state := &pb.ServerMessage{
|
||||
CurrentTick: currentTick,
|
||||
Players: make([]*pb.PlayerState, 0, len(players)),
|
||||
}
|
||||
|
||||
// Convert players to PlayerState
|
||||
for id, p := range players {
|
||||
p.Lock()
|
||||
state.Players = append(state.Players, &pb.PlayerState{
|
||||
@ -166,21 +180,34 @@ func processActions() {
|
||||
p.Unlock()
|
||||
}
|
||||
|
||||
// Add chat messages to the server message
|
||||
// Add chat messages to the state
|
||||
chatMutex.RLock()
|
||||
state.ChatMessages = chatHistory
|
||||
state.ChatMessages = chatHistory[max(0, len(chatHistory)-5):] // Only send last 5 messages
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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