Compare commits
6 Commits
feature/ch
...
b44cdab611
Author | SHA1 | Date | |
---|---|---|---|
b44cdab611 | |||
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 &
|
||||
```
|
@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.2
|
||||
// protoc-gen-go v1.36.3
|
||||
// protoc v5.29.2
|
||||
// source: actions.proto
|
||||
|
||||
@ -25,6 +25,7 @@ type Action_ActionType int32
|
||||
const (
|
||||
Action_MOVE Action_ActionType = 0
|
||||
Action_CHAT Action_ActionType = 1
|
||||
Action_DISCONNECT Action_ActionType = 2
|
||||
)
|
||||
|
||||
// Enum value maps for Action_ActionType.
|
||||
@ -32,10 +33,12 @@ var (
|
||||
Action_ActionType_name = map[int32]string{
|
||||
0: "MOVE",
|
||||
1: "CHAT",
|
||||
2: "DISCONNECT",
|
||||
}
|
||||
Action_ActionType_value = map[string]int32{
|
||||
"MOVE": 0,
|
||||
"CHAT": 1,
|
||||
"DISCONNECT": 2,
|
||||
}
|
||||
)
|
||||
|
||||
@ -394,7 +397,7 @@ var File_actions_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_actions_proto_rawDesc = []byte{
|
||||
0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||
0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xb6, 0x01, 0x0a, 0x06, 0x41, 0x63, 0x74,
|
||||
0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc6, 0x01, 0x0a, 0x06, 0x41, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0e, 0x32, 0x1a, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
|
||||
@ -404,9 +407,10 @@ var file_actions_proto_rawDesc = []byte{
|
||||
0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c,
|
||||
0x63, 0x68, 0x61, 0x74, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
|
||||
0x20, 0x0a, 0x0a, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a,
|
||||
0x30, 0x0a, 0x0a, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a,
|
||||
0x04, 0x4d, 0x4f, 0x56, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x48, 0x41, 0x54, 0x10,
|
||||
0x01, 0x22, 0x69, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68,
|
||||
0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10,
|
||||
0x02, 0x22, 0x69, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68,
|
||||
0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a,
|
||||
0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f,
|
||||
|
@ -8,6 +8,7 @@ message Action {
|
||||
enum ActionType {
|
||||
MOVE = 0;
|
||||
CHAT = 1;
|
||||
DISCONNECT = 2;
|
||||
}
|
||||
|
||||
ActionType type = 1;
|
||||
|
86
main.go
86
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,13 +111,22 @@ 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
|
||||
}
|
||||
|
||||
// Queue the actions for processing
|
||||
if batch.PlayerId == int32(playerID) {
|
||||
for _, action := range batch.Actions {
|
||||
if action.Type == pb.Action_DISCONNECT {
|
||||
log.Printf("Player %d disconnected gracefully", playerID)
|
||||
delete(players, playerID)
|
||||
delete(playerConns, playerID)
|
||||
delete(actionQueue, playerID)
|
||||
return
|
||||
}
|
||||
}
|
||||
actionQueue[playerID] = append(actionQueue[playerID], batch.Actions...)
|
||||
}
|
||||
}
|
||||
@ -154,8 +175,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 +189,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