Compare commits
18 Commits
feature/ch
...
master
Author | SHA1 | Date | |
---|---|---|---|
00aa302229 | |||
c720b66818 | |||
f9ec811b10 | |||
d25ee09155 | |||
e3c570349c | |||
27da845b11 | |||
3f7205d73e | |||
0731339fe8 | |||
71d42a8fd6 | |||
52ab45fe53 | |||
be32dec202 | |||
2d0cb12532 | |||
b44cdab611 | |||
b73d8de851 | |||
23474c19dc | |||
a48fef0186 | |||
67e08c5d1e | |||
49e2311497 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.db
|
24
LICENSE
24
LICENSE
@ -1,11 +1,21 @@
|
|||||||
“Commons Clause” License Condition v1.0
|
MIT License
|
||||||
|
|
||||||
The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition.
|
Copyright (c) 2025 bdnugget
|
||||||
|
|
||||||
Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, right to Sell the Software.
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Cause License Condition notice.
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
Software: GoonServer (for Goonscape)
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
License: Commons Clause v1.0
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
Licensor: bdnugget
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
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,8 +1,8 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.2
|
// protoc-gen-go v1.36.6
|
||||||
// protoc v5.29.2
|
// protoc v6.30.1
|
||||||
// source: actions.proto
|
// source: actions/actions.proto
|
||||||
|
|
||||||
package actions
|
package actions
|
||||||
|
|
||||||
@ -11,6 +11,7 @@ import (
|
|||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -23,8 +24,11 @@ const (
|
|||||||
type Action_ActionType int32
|
type Action_ActionType int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Action_MOVE Action_ActionType = 0
|
Action_MOVE Action_ActionType = 0
|
||||||
Action_CHAT Action_ActionType = 1
|
Action_CHAT Action_ActionType = 1
|
||||||
|
Action_DISCONNECT Action_ActionType = 2
|
||||||
|
Action_LOGIN Action_ActionType = 3
|
||||||
|
Action_REGISTER Action_ActionType = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for Action_ActionType.
|
// Enum value maps for Action_ActionType.
|
||||||
@ -32,10 +36,16 @@ var (
|
|||||||
Action_ActionType_name = map[int32]string{
|
Action_ActionType_name = map[int32]string{
|
||||||
0: "MOVE",
|
0: "MOVE",
|
||||||
1: "CHAT",
|
1: "CHAT",
|
||||||
|
2: "DISCONNECT",
|
||||||
|
3: "LOGIN",
|
||||||
|
4: "REGISTER",
|
||||||
}
|
}
|
||||||
Action_ActionType_value = map[string]int32{
|
Action_ActionType_value = map[string]int32{
|
||||||
"MOVE": 0,
|
"MOVE": 0,
|
||||||
"CHAT": 1,
|
"CHAT": 1,
|
||||||
|
"DISCONNECT": 2,
|
||||||
|
"LOGIN": 3,
|
||||||
|
"REGISTER": 4,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -50,11 +60,11 @@ func (x Action_ActionType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (Action_ActionType) Descriptor() protoreflect.EnumDescriptor {
|
func (Action_ActionType) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_actions_proto_enumTypes[0].Descriptor()
|
return file_actions_actions_proto_enumTypes[0].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Action_ActionType) Type() protoreflect.EnumType {
|
func (Action_ActionType) Type() protoreflect.EnumType {
|
||||||
return &file_actions_proto_enumTypes[0]
|
return &file_actions_actions_proto_enumTypes[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x Action_ActionType) Number() protoreflect.EnumNumber {
|
func (x Action_ActionType) Number() protoreflect.EnumNumber {
|
||||||
@ -63,7 +73,7 @@ func (x Action_ActionType) Number() protoreflect.EnumNumber {
|
|||||||
|
|
||||||
// Deprecated: Use Action_ActionType.Descriptor instead.
|
// Deprecated: Use Action_ActionType.Descriptor instead.
|
||||||
func (Action_ActionType) EnumDescriptor() ([]byte, []int) {
|
func (Action_ActionType) EnumDescriptor() ([]byte, []int) {
|
||||||
return file_actions_proto_rawDescGZIP(), []int{0, 0}
|
return file_actions_actions_proto_rawDescGZIP(), []int{0, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Action struct {
|
type Action struct {
|
||||||
@ -73,13 +83,15 @@ type Action struct {
|
|||||||
Y int32 `protobuf:"varint,3,opt,name=y,proto3" json:"y,omitempty"`
|
Y int32 `protobuf:"varint,3,opt,name=y,proto3" json:"y,omitempty"`
|
||||||
PlayerId int32 `protobuf:"varint,4,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
PlayerId int32 `protobuf:"varint,4,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
||||||
ChatMessage string `protobuf:"bytes,5,opt,name=chat_message,json=chatMessage,proto3" json:"chat_message,omitempty"`
|
ChatMessage string `protobuf:"bytes,5,opt,name=chat_message,json=chatMessage,proto3" json:"chat_message,omitempty"`
|
||||||
|
Username string `protobuf:"bytes,6,opt,name=username,proto3" json:"username,omitempty"`
|
||||||
|
Password string `protobuf:"bytes,7,opt,name=password,proto3" json:"password,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Action) Reset() {
|
func (x *Action) Reset() {
|
||||||
*x = Action{}
|
*x = Action{}
|
||||||
mi := &file_actions_proto_msgTypes[0]
|
mi := &file_actions_actions_proto_msgTypes[0]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -91,7 +103,7 @@ func (x *Action) String() string {
|
|||||||
func (*Action) ProtoMessage() {}
|
func (*Action) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Action) ProtoReflect() protoreflect.Message {
|
func (x *Action) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_actions_proto_msgTypes[0]
|
mi := &file_actions_actions_proto_msgTypes[0]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -104,7 +116,7 @@ func (x *Action) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Action.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Action.ProtoReflect.Descriptor instead.
|
||||||
func (*Action) Descriptor() ([]byte, []int) {
|
func (*Action) Descriptor() ([]byte, []int) {
|
||||||
return file_actions_proto_rawDescGZIP(), []int{0}
|
return file_actions_actions_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Action) GetType() Action_ActionType {
|
func (x *Action) GetType() Action_ActionType {
|
||||||
@ -142,18 +154,34 @@ func (x *Action) GetChatMessage() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Action) GetUsername() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Username
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Action) GetPassword() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Password
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type ActionBatch struct {
|
type ActionBatch struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
PlayerId int32 `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
PlayerId int32 `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
||||||
Actions []*Action `protobuf:"bytes,2,rep,name=actions,proto3" json:"actions,omitempty"`
|
Actions []*Action `protobuf:"bytes,2,rep,name=actions,proto3" json:"actions,omitempty"`
|
||||||
Tick int64 `protobuf:"varint,3,opt,name=tick,proto3" json:"tick,omitempty"`
|
Tick int64 `protobuf:"varint,3,opt,name=tick,proto3" json:"tick,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
ProtocolVersion int32 `protobuf:"varint,4,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"`
|
||||||
sizeCache protoimpl.SizeCache
|
LastSeenMessageTimestamp int64 `protobuf:"varint,5,opt,name=last_seen_message_timestamp,json=lastSeenMessageTimestamp,proto3" json:"last_seen_message_timestamp,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ActionBatch) Reset() {
|
func (x *ActionBatch) Reset() {
|
||||||
*x = ActionBatch{}
|
*x = ActionBatch{}
|
||||||
mi := &file_actions_proto_msgTypes[1]
|
mi := &file_actions_actions_proto_msgTypes[1]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -165,7 +193,7 @@ func (x *ActionBatch) String() string {
|
|||||||
func (*ActionBatch) ProtoMessage() {}
|
func (*ActionBatch) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ActionBatch) ProtoReflect() protoreflect.Message {
|
func (x *ActionBatch) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_actions_proto_msgTypes[1]
|
mi := &file_actions_actions_proto_msgTypes[1]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -178,7 +206,7 @@ func (x *ActionBatch) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ActionBatch.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ActionBatch.ProtoReflect.Descriptor instead.
|
||||||
func (*ActionBatch) Descriptor() ([]byte, []int) {
|
func (*ActionBatch) Descriptor() ([]byte, []int) {
|
||||||
return file_actions_proto_rawDescGZIP(), []int{1}
|
return file_actions_actions_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ActionBatch) GetPlayerId() int32 {
|
func (x *ActionBatch) GetPlayerId() int32 {
|
||||||
@ -202,18 +230,33 @@ func (x *ActionBatch) GetTick() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ActionBatch) GetProtocolVersion() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ProtocolVersion
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ActionBatch) GetLastSeenMessageTimestamp() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.LastSeenMessageTimestamp
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
type PlayerState struct {
|
type PlayerState struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
PlayerId int32 `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
PlayerId int32 `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
||||||
X int32 `protobuf:"varint,2,opt,name=x,proto3" json:"x,omitempty"`
|
X int32 `protobuf:"varint,2,opt,name=x,proto3" json:"x,omitempty"`
|
||||||
Y int32 `protobuf:"varint,3,opt,name=y,proto3" json:"y,omitempty"`
|
Y int32 `protobuf:"varint,3,opt,name=y,proto3" json:"y,omitempty"`
|
||||||
|
Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PlayerState) Reset() {
|
func (x *PlayerState) Reset() {
|
||||||
*x = PlayerState{}
|
*x = PlayerState{}
|
||||||
mi := &file_actions_proto_msgTypes[2]
|
mi := &file_actions_actions_proto_msgTypes[2]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -225,7 +268,7 @@ func (x *PlayerState) String() string {
|
|||||||
func (*PlayerState) ProtoMessage() {}
|
func (*PlayerState) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *PlayerState) ProtoReflect() protoreflect.Message {
|
func (x *PlayerState) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_actions_proto_msgTypes[2]
|
mi := &file_actions_actions_proto_msgTypes[2]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -238,7 +281,7 @@ func (x *PlayerState) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use PlayerState.ProtoReflect.Descriptor instead.
|
// Deprecated: Use PlayerState.ProtoReflect.Descriptor instead.
|
||||||
func (*PlayerState) Descriptor() ([]byte, []int) {
|
func (*PlayerState) Descriptor() ([]byte, []int) {
|
||||||
return file_actions_proto_rawDescGZIP(), []int{2}
|
return file_actions_actions_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PlayerState) GetPlayerId() int32 {
|
func (x *PlayerState) GetPlayerId() int32 {
|
||||||
@ -262,18 +305,26 @@ func (x *PlayerState) GetY() int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *PlayerState) GetUsername() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Username
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type ChatMessage struct {
|
type ChatMessage struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
PlayerId int32 `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
PlayerId int32 `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
||||||
Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
|
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
|
||||||
Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"`
|
||||||
|
Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ChatMessage) Reset() {
|
func (x *ChatMessage) Reset() {
|
||||||
*x = ChatMessage{}
|
*x = ChatMessage{}
|
||||||
mi := &file_actions_proto_msgTypes[3]
|
mi := &file_actions_actions_proto_msgTypes[3]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -285,7 +336,7 @@ func (x *ChatMessage) String() string {
|
|||||||
func (*ChatMessage) ProtoMessage() {}
|
func (*ChatMessage) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ChatMessage) ProtoReflect() protoreflect.Message {
|
func (x *ChatMessage) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_actions_proto_msgTypes[3]
|
mi := &file_actions_actions_proto_msgTypes[3]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -298,7 +349,7 @@ func (x *ChatMessage) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ChatMessage.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ChatMessage.ProtoReflect.Descriptor instead.
|
||||||
func (*ChatMessage) Descriptor() ([]byte, []int) {
|
func (*ChatMessage) Descriptor() ([]byte, []int) {
|
||||||
return file_actions_proto_rawDescGZIP(), []int{3}
|
return file_actions_actions_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ChatMessage) GetPlayerId() int32 {
|
func (x *ChatMessage) GetPlayerId() int32 {
|
||||||
@ -308,6 +359,13 @@ func (x *ChatMessage) GetPlayerId() int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ChatMessage) GetUsername() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Username
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *ChatMessage) GetContent() string {
|
func (x *ChatMessage) GetContent() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Content
|
return x.Content
|
||||||
@ -323,18 +381,21 @@ func (x *ChatMessage) GetTimestamp() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServerMessage struct {
|
type ServerMessage struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
PlayerId int32 `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
PlayerId int32 `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
||||||
Players []*PlayerState `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty"`
|
Players []*PlayerState `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty"`
|
||||||
CurrentTick int64 `protobuf:"varint,3,opt,name=current_tick,json=currentTick,proto3" json:"current_tick,omitempty"`
|
CurrentTick int64 `protobuf:"varint,3,opt,name=current_tick,json=currentTick,proto3" json:"current_tick,omitempty"`
|
||||||
ChatMessages []*ChatMessage `protobuf:"bytes,4,rep,name=chat_messages,json=chatMessages,proto3" json:"chat_messages,omitempty"`
|
ChatMessages []*ChatMessage `protobuf:"bytes,4,rep,name=chat_messages,json=chatMessages,proto3" json:"chat_messages,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
AuthSuccess bool `protobuf:"varint,5,opt,name=auth_success,json=authSuccess,proto3" json:"auth_success,omitempty"`
|
||||||
sizeCache protoimpl.SizeCache
|
ErrorMessage string `protobuf:"bytes,6,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
|
||||||
|
ProtocolVersion int32 `protobuf:"varint,7,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ServerMessage) Reset() {
|
func (x *ServerMessage) Reset() {
|
||||||
*x = ServerMessage{}
|
*x = ServerMessage{}
|
||||||
mi := &file_actions_proto_msgTypes[4]
|
mi := &file_actions_actions_proto_msgTypes[4]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -346,7 +407,7 @@ func (x *ServerMessage) String() string {
|
|||||||
func (*ServerMessage) ProtoMessage() {}
|
func (*ServerMessage) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ServerMessage) ProtoReflect() protoreflect.Message {
|
func (x *ServerMessage) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_actions_proto_msgTypes[4]
|
mi := &file_actions_actions_proto_msgTypes[4]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -359,7 +420,7 @@ func (x *ServerMessage) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ServerMessage.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ServerMessage.ProtoReflect.Descriptor instead.
|
||||||
func (*ServerMessage) Descriptor() ([]byte, []int) {
|
func (*ServerMessage) Descriptor() ([]byte, []int) {
|
||||||
return file_actions_proto_rawDescGZIP(), []int{4}
|
return file_actions_actions_proto_rawDescGZIP(), []int{4}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ServerMessage) GetPlayerId() int32 {
|
func (x *ServerMessage) GetPlayerId() int32 {
|
||||||
@ -390,72 +451,88 @@ func (x *ServerMessage) GetChatMessages() []*ChatMessage {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var File_actions_proto protoreflect.FileDescriptor
|
func (x *ServerMessage) GetAuthSuccess() bool {
|
||||||
|
if x != nil {
|
||||||
var file_actions_proto_rawDesc = []byte{
|
return x.AuthSuccess
|
||||||
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,
|
return false
|
||||||
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,
|
|
||||||
0x79, 0x70, 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01,
|
|
||||||
0x78, 0x12, 0x0c, 0x0a, 0x01, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x79, 0x12,
|
|
||||||
0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01,
|
|
||||||
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,
|
|
||||||
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,
|
|
||||||
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,
|
|
||||||
0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
|
||||||
0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x63, 0x6b,
|
|
||||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x63, 0x6b, 0x22, 0x46, 0x0a, 0x0b,
|
|
||||||
0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 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, 0x0c, 0x0a, 0x01, 0x78, 0x18, 0x02, 0x20,
|
|
||||||
0x01, 0x28, 0x05, 0x52, 0x01, 0x78, 0x12, 0x0c, 0x0a, 0x01, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
|
|
||||||
0x05, 0x52, 0x01, 0x79, 0x22, 0x62, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x74, 0x4d, 0x65, 0x73, 0x73,
|
|
||||||
0x61, 0x67, 0x65, 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, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
|
|
||||||
0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69,
|
|
||||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74,
|
|
||||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xba, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x72,
|
|
||||||
0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 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, 0x2e, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65,
|
|
||||||
0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f,
|
|
||||||
0x6e, 0x73, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x07,
|
|
||||||
0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65,
|
|
||||||
0x6e, 0x74, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63,
|
|
||||||
0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x12, 0x39, 0x0a, 0x0d, 0x63, 0x68,
|
|
||||||
0x61, 0x74, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
|
|
||||||
0x0b, 0x32, 0x14, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x43, 0x68, 0x61, 0x74,
|
|
||||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x74, 0x4d, 0x65, 0x73,
|
|
||||||
0x73, 0x61, 0x67, 0x65, 0x73, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x65, 0x61, 0x2e, 0x62,
|
|
||||||
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x62, 0x65, 0x2f, 0x62, 0x64, 0x6e, 0x75, 0x67, 0x67, 0x65, 0x74,
|
|
||||||
0x2f, 0x67, 0x6f, 0x6f, 0x6e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x61, 0x63, 0x74, 0x69,
|
|
||||||
0x6f, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ServerMessage) GetErrorMessage() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ErrorMessage
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ServerMessage) GetProtocolVersion() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ProtocolVersion
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_actions_actions_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_actions_actions_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x15actions/actions.proto\x12\aactions\"\x97\x02\n" +
|
||||||
|
"\x06Action\x12.\n" +
|
||||||
|
"\x04type\x18\x01 \x01(\x0e2\x1a.actions.Action.ActionTypeR\x04type\x12\f\n" +
|
||||||
|
"\x01x\x18\x02 \x01(\x05R\x01x\x12\f\n" +
|
||||||
|
"\x01y\x18\x03 \x01(\x05R\x01y\x12\x1b\n" +
|
||||||
|
"\tplayer_id\x18\x04 \x01(\x05R\bplayerId\x12!\n" +
|
||||||
|
"\fchat_message\x18\x05 \x01(\tR\vchatMessage\x12\x1a\n" +
|
||||||
|
"\busername\x18\x06 \x01(\tR\busername\x12\x1a\n" +
|
||||||
|
"\bpassword\x18\a \x01(\tR\bpassword\"I\n" +
|
||||||
|
"\n" +
|
||||||
|
"ActionType\x12\b\n" +
|
||||||
|
"\x04MOVE\x10\x00\x12\b\n" +
|
||||||
|
"\x04CHAT\x10\x01\x12\x0e\n" +
|
||||||
|
"\n" +
|
||||||
|
"DISCONNECT\x10\x02\x12\t\n" +
|
||||||
|
"\x05LOGIN\x10\x03\x12\f\n" +
|
||||||
|
"\bREGISTER\x10\x04\"\xd3\x01\n" +
|
||||||
|
"\vActionBatch\x12\x1b\n" +
|
||||||
|
"\tplayer_id\x18\x01 \x01(\x05R\bplayerId\x12)\n" +
|
||||||
|
"\aactions\x18\x02 \x03(\v2\x0f.actions.ActionR\aactions\x12\x12\n" +
|
||||||
|
"\x04tick\x18\x03 \x01(\x03R\x04tick\x12)\n" +
|
||||||
|
"\x10protocol_version\x18\x04 \x01(\x05R\x0fprotocolVersion\x12=\n" +
|
||||||
|
"\x1blast_seen_message_timestamp\x18\x05 \x01(\x03R\x18lastSeenMessageTimestamp\"b\n" +
|
||||||
|
"\vPlayerState\x12\x1b\n" +
|
||||||
|
"\tplayer_id\x18\x01 \x01(\x05R\bplayerId\x12\f\n" +
|
||||||
|
"\x01x\x18\x02 \x01(\x05R\x01x\x12\f\n" +
|
||||||
|
"\x01y\x18\x03 \x01(\x05R\x01y\x12\x1a\n" +
|
||||||
|
"\busername\x18\x04 \x01(\tR\busername\"~\n" +
|
||||||
|
"\vChatMessage\x12\x1b\n" +
|
||||||
|
"\tplayer_id\x18\x01 \x01(\x05R\bplayerId\x12\x1a\n" +
|
||||||
|
"\busername\x18\x02 \x01(\tR\busername\x12\x18\n" +
|
||||||
|
"\acontent\x18\x03 \x01(\tR\acontent\x12\x1c\n" +
|
||||||
|
"\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\"\xad\x02\n" +
|
||||||
|
"\rServerMessage\x12\x1b\n" +
|
||||||
|
"\tplayer_id\x18\x01 \x01(\x05R\bplayerId\x12.\n" +
|
||||||
|
"\aplayers\x18\x02 \x03(\v2\x14.actions.PlayerStateR\aplayers\x12!\n" +
|
||||||
|
"\fcurrent_tick\x18\x03 \x01(\x03R\vcurrentTick\x129\n" +
|
||||||
|
"\rchat_messages\x18\x04 \x03(\v2\x14.actions.ChatMessageR\fchatMessages\x12!\n" +
|
||||||
|
"\fauth_success\x18\x05 \x01(\bR\vauthSuccess\x12#\n" +
|
||||||
|
"\rerror_message\x18\x06 \x01(\tR\ferrorMessage\x12)\n" +
|
||||||
|
"\x10protocol_version\x18\a \x01(\x05R\x0fprotocolVersionB,Z*gitea.boner.be/bdnugget/goonserver/actionsb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_actions_proto_rawDescOnce sync.Once
|
file_actions_actions_proto_rawDescOnce sync.Once
|
||||||
file_actions_proto_rawDescData = file_actions_proto_rawDesc
|
file_actions_actions_proto_rawDescData []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_actions_proto_rawDescGZIP() []byte {
|
func file_actions_actions_proto_rawDescGZIP() []byte {
|
||||||
file_actions_proto_rawDescOnce.Do(func() {
|
file_actions_actions_proto_rawDescOnce.Do(func() {
|
||||||
file_actions_proto_rawDescData = protoimpl.X.CompressGZIP(file_actions_proto_rawDescData)
|
file_actions_actions_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_actions_actions_proto_rawDesc), len(file_actions_actions_proto_rawDesc)))
|
||||||
})
|
})
|
||||||
return file_actions_proto_rawDescData
|
return file_actions_actions_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_actions_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
var file_actions_actions_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
var file_actions_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
var file_actions_actions_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
var file_actions_proto_goTypes = []any{
|
var file_actions_actions_proto_goTypes = []any{
|
||||||
(Action_ActionType)(0), // 0: actions.Action.ActionType
|
(Action_ActionType)(0), // 0: actions.Action.ActionType
|
||||||
(*Action)(nil), // 1: actions.Action
|
(*Action)(nil), // 1: actions.Action
|
||||||
(*ActionBatch)(nil), // 2: actions.ActionBatch
|
(*ActionBatch)(nil), // 2: actions.ActionBatch
|
||||||
@ -463,7 +540,7 @@ var file_actions_proto_goTypes = []any{
|
|||||||
(*ChatMessage)(nil), // 4: actions.ChatMessage
|
(*ChatMessage)(nil), // 4: actions.ChatMessage
|
||||||
(*ServerMessage)(nil), // 5: actions.ServerMessage
|
(*ServerMessage)(nil), // 5: actions.ServerMessage
|
||||||
}
|
}
|
||||||
var file_actions_proto_depIdxs = []int32{
|
var file_actions_actions_proto_depIdxs = []int32{
|
||||||
0, // 0: actions.Action.type:type_name -> actions.Action.ActionType
|
0, // 0: actions.Action.type:type_name -> actions.Action.ActionType
|
||||||
1, // 1: actions.ActionBatch.actions:type_name -> actions.Action
|
1, // 1: actions.ActionBatch.actions:type_name -> actions.Action
|
||||||
3, // 2: actions.ServerMessage.players:type_name -> actions.PlayerState
|
3, // 2: actions.ServerMessage.players:type_name -> actions.PlayerState
|
||||||
@ -475,28 +552,27 @@ var file_actions_proto_depIdxs = []int32{
|
|||||||
0, // [0:4] is the sub-list for field type_name
|
0, // [0:4] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_actions_proto_init() }
|
func init() { file_actions_actions_proto_init() }
|
||||||
func file_actions_proto_init() {
|
func file_actions_actions_proto_init() {
|
||||||
if File_actions_proto != nil {
|
if File_actions_actions_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_actions_proto_rawDesc,
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_actions_actions_proto_rawDesc), len(file_actions_actions_proto_rawDesc)),
|
||||||
NumEnums: 1,
|
NumEnums: 1,
|
||||||
NumMessages: 5,
|
NumMessages: 5,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
GoTypes: file_actions_proto_goTypes,
|
GoTypes: file_actions_actions_proto_goTypes,
|
||||||
DependencyIndexes: file_actions_proto_depIdxs,
|
DependencyIndexes: file_actions_actions_proto_depIdxs,
|
||||||
EnumInfos: file_actions_proto_enumTypes,
|
EnumInfos: file_actions_actions_proto_enumTypes,
|
||||||
MessageInfos: file_actions_proto_msgTypes,
|
MessageInfos: file_actions_actions_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_actions_proto = out.File
|
File_actions_actions_proto = out.File
|
||||||
file_actions_proto_rawDesc = nil
|
file_actions_actions_proto_goTypes = nil
|
||||||
file_actions_proto_goTypes = nil
|
file_actions_actions_proto_depIdxs = nil
|
||||||
file_actions_proto_depIdxs = nil
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,9 @@ message Action {
|
|||||||
enum ActionType {
|
enum ActionType {
|
||||||
MOVE = 0;
|
MOVE = 0;
|
||||||
CHAT = 1;
|
CHAT = 1;
|
||||||
|
DISCONNECT = 2;
|
||||||
|
LOGIN = 3;
|
||||||
|
REGISTER = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionType type = 1;
|
ActionType type = 1;
|
||||||
@ -15,24 +18,30 @@ message Action {
|
|||||||
int32 y = 3;
|
int32 y = 3;
|
||||||
int32 player_id = 4;
|
int32 player_id = 4;
|
||||||
string chat_message = 5;
|
string chat_message = 5;
|
||||||
|
string username = 6;
|
||||||
|
string password = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ActionBatch {
|
message ActionBatch {
|
||||||
int32 player_id = 1;
|
int32 player_id = 1;
|
||||||
repeated Action actions = 2;
|
repeated Action actions = 2;
|
||||||
int64 tick = 3;
|
int64 tick = 3;
|
||||||
|
int32 protocol_version = 4;
|
||||||
|
int64 last_seen_message_timestamp = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PlayerState {
|
message PlayerState {
|
||||||
int32 player_id = 1;
|
int32 player_id = 1;
|
||||||
int32 x = 2;
|
int32 x = 2;
|
||||||
int32 y = 3;
|
int32 y = 3;
|
||||||
|
string username = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ChatMessage {
|
message ChatMessage {
|
||||||
int32 player_id = 1;
|
int32 player_id = 1;
|
||||||
string content = 2;
|
string username = 2;
|
||||||
int64 timestamp = 3;
|
string content = 3;
|
||||||
|
int64 timestamp = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ServerMessage {
|
message ServerMessage {
|
||||||
@ -40,4 +49,7 @@ message ServerMessage {
|
|||||||
repeated PlayerState players = 2;
|
repeated PlayerState players = 2;
|
||||||
int64 current_tick = 3;
|
int64 current_tick = 3;
|
||||||
repeated ChatMessage chat_messages = 4;
|
repeated ChatMessage chat_messages = 4;
|
||||||
|
bool auth_success = 5;
|
||||||
|
string error_message = 6;
|
||||||
|
int32 protocol_version = 7;
|
||||||
}
|
}
|
||||||
|
188
db/db.go
Normal file
188
db/db.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *sql.DB
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUserExists = errors.New("username already exists")
|
||||||
|
ErrInvalidCredentials = errors.New("invalid username or password")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxRegistrationsPerIP = 3 // Maximum registrations allowed per IP
|
||||||
|
registrationWindow = 24 * time.Hour // Time window for rate limiting
|
||||||
|
)
|
||||||
|
|
||||||
|
type registrationAttempt struct {
|
||||||
|
count int
|
||||||
|
firstTry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
registrationAttempts = make(map[string]*registrationAttempt)
|
||||||
|
rateLimitMutex sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func CleanupOldAttempts() {
|
||||||
|
rateLimitMutex.Lock()
|
||||||
|
defer rateLimitMutex.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
for ip, attempt := range registrationAttempts {
|
||||||
|
if now.Sub(attempt.firstTry) > registrationWindow {
|
||||||
|
delete(registrationAttempts, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckRegistrationLimit(ip string) error {
|
||||||
|
rateLimitMutex.Lock()
|
||||||
|
defer rateLimitMutex.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
attempt, exists := registrationAttempts[ip]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
registrationAttempts[ip] = ®istrationAttempt{
|
||||||
|
count: 1,
|
||||||
|
firstTry: now,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset if window has passed
|
||||||
|
if now.Sub(attempt.firstTry) > registrationWindow {
|
||||||
|
attempt.count = 1
|
||||||
|
attempt.firstTry = now
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if attempt.count >= maxRegistrationsPerIP {
|
||||||
|
return fmt.Errorf("registration limit reached for this IP. Please try again in %v",
|
||||||
|
registrationWindow-now.Sub(attempt.firstTry))
|
||||||
|
}
|
||||||
|
|
||||||
|
attempt.count++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitDB(dbPath string) error {
|
||||||
|
var err error
|
||||||
|
db, err = sql.Open("sqlite3", dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create tables if they don't exist
|
||||||
|
_, err = db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS players (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS player_states (
|
||||||
|
player_id INTEGER PRIMARY KEY,
|
||||||
|
x INTEGER NOT NULL,
|
||||||
|
y INTEGER NOT NULL,
|
||||||
|
last_seen DATETIME NOT NULL,
|
||||||
|
FOREIGN KEY(player_id) REFERENCES players(id)
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func hashPassword(password string) string {
|
||||||
|
hash := sha256.Sum256([]byte(password))
|
||||||
|
return hex.EncodeToString(hash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterPlayer(username, password string) (int, error) {
|
||||||
|
// Check if username exists
|
||||||
|
var exists bool
|
||||||
|
err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM players WHERE username = ?)", username).Scan(&exists)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return 0, ErrUserExists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new player
|
||||||
|
result, err := db.Exec(`
|
||||||
|
INSERT INTO players (username, password_hash, created_at)
|
||||||
|
VALUES (?, ?, ?)`,
|
||||||
|
username, hashPassword(password), time.Now().UTC(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := result.LastInsertId()
|
||||||
|
return int(id), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthenticatePlayer(username, password string) (int, error) {
|
||||||
|
var id int
|
||||||
|
var storedHash string
|
||||||
|
err := db.QueryRow(`
|
||||||
|
SELECT id, password_hash
|
||||||
|
FROM players
|
||||||
|
WHERE username = ?`,
|
||||||
|
username,
|
||||||
|
).Scan(&id, &storedHash)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return 0, ErrInvalidCredentials
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if storedHash != hashPassword(password) {
|
||||||
|
return 0, ErrInvalidCredentials
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SavePlayerState(playerID int, x, y int) error {
|
||||||
|
_, err := db.Exec(`
|
||||||
|
INSERT OR REPLACE INTO player_states (
|
||||||
|
player_id, x, y, last_seen
|
||||||
|
) VALUES (?, ?, ?, ?)`,
|
||||||
|
playerID, x, y, time.Now().UTC(),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadPlayerState(playerID int) (x, y int, err error) {
|
||||||
|
err = db.QueryRow(`
|
||||||
|
SELECT x, y FROM player_states
|
||||||
|
WHERE player_id = ?`,
|
||||||
|
playerID,
|
||||||
|
).Scan(&x, &y)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// Return default position for new players
|
||||||
|
return 5, 5, nil
|
||||||
|
}
|
||||||
|
return x, y, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUsername(playerID int) (string, error) {
|
||||||
|
var username string
|
||||||
|
err := db.QueryRow("SELECT username FROM players WHERE id = ?", playerID).Scan(&username)
|
||||||
|
return username, err
|
||||||
|
}
|
5
go.mod
5
go.mod
@ -2,4 +2,7 @@ module gitea.boner.be/bdnugget/goonserver
|
|||||||
|
|
||||||
go 1.23.0
|
go 1.23.0
|
||||||
|
|
||||||
require google.golang.org/protobuf v1.35.1
|
require (
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
|
google.golang.org/protobuf v1.36.3
|
||||||
|
)
|
||||||
|
6
go.sum
6
go.sum
@ -1,6 +1,8 @@
|
|||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
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=
|
||||||
|
BIN
goonserver
Executable file
BIN
goonserver
Executable file
Binary file not shown.
434
main.go
434
main.go
@ -1,26 +1,33 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
||||||
|
"gitea.boner.be/bdnugget/goonserver/db"
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
port = ":6969" // Port to listen on
|
port = ":6969" // Port to listen on
|
||||||
tickRate = 600 * time.Millisecond
|
tickRate = 600 * time.Millisecond
|
||||||
|
protoVersion = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
type Player struct {
|
type Player struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
ID int
|
ID int
|
||||||
X, Y int // Position on the game grid
|
X, Y int
|
||||||
|
Username string
|
||||||
|
LastSeenMsgTimestamp int64 // Track the last message timestamp this player has seen
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -33,6 +40,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if err := db.InitDB("goonserver.db"); err != nil {
|
||||||
|
log.Fatalf("Failed to initialize database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", port)
|
ln, err := net.Listen("tcp", port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to listen on port %s: %v", port, err)
|
log.Fatalf("Failed to listen on port %s: %v", port, err)
|
||||||
@ -40,6 +51,20 @@ 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()
|
||||||
|
|
||||||
|
// Start registration attempt cleanup goroutine
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(time.Hour)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
db.CleanupOldAttempts()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Handle incoming connections in a separate goroutine
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
conn, err := ln.Accept()
|
conn, err := ln.Accept()
|
||||||
@ -51,72 +76,271 @@ func main() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lastTick := time.Now()
|
// Main game loop
|
||||||
for {
|
for range ticker.C {
|
||||||
if time.Since(lastTick) >= tickRate {
|
processActions()
|
||||||
lastTick = time.Now()
|
|
||||||
processActions()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnection(conn net.Conn) {
|
func handleConnection(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer func() {
|
||||||
|
conn.Close()
|
||||||
|
log.Printf("Connection closed and cleanup complete")
|
||||||
|
}()
|
||||||
|
|
||||||
mu.Lock()
|
// Get client IP
|
||||||
playerID := len(players) + 1
|
remoteAddr := conn.RemoteAddr().String()
|
||||||
newPlayer := &Player{ID: playerID, X: 5, Y: 5}
|
ip, _, err := net.SplitHostPort(remoteAddr)
|
||||||
players[playerID] = newPlayer
|
|
||||||
playerConns[playerID] = conn
|
|
||||||
mu.Unlock()
|
|
||||||
fmt.Printf("Player %d connected\n", playerID)
|
|
||||||
|
|
||||||
// Send player ID to the client
|
|
||||||
serverMsg := &pb.ServerMessage{
|
|
||||||
PlayerId: int32(playerID),
|
|
||||||
CurrentTick: 0,
|
|
||||||
}
|
|
||||||
data, err := proto.Marshal(serverMsg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to marshal ServerMessage for player %d: %v", playerID, err)
|
log.Printf("Failed to parse remote address: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := conn.Write(data); err != nil {
|
|
||||||
log.Printf("Failed to send player ID to player %d: %v", playerID, err)
|
// Read initial message for player ID
|
||||||
|
reader := bufio.NewReader(conn)
|
||||||
|
|
||||||
|
// Wait for authentication
|
||||||
|
lengthBuf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadFull(reader, lengthBuf); err != nil {
|
||||||
|
log.Printf("Failed to read auth message length: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
messageLength := binary.BigEndian.Uint32(lengthBuf)
|
||||||
|
|
||||||
|
messageBuf := make([]byte, messageLength)
|
||||||
|
if _, err := io.ReadFull(reader, messageBuf); err != nil {
|
||||||
|
log.Printf("Failed to read auth message: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := &pb.ActionBatch{}
|
||||||
|
if err := proto.Unmarshal(messageBuf, batch); err != nil {
|
||||||
|
log.Printf("Failed to unmarshal auth message: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(batch.Actions) == 0 {
|
||||||
|
log.Printf("No auth action received")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
action := batch.Actions[0]
|
||||||
|
var playerID int
|
||||||
|
var authErr error
|
||||||
|
|
||||||
|
if batch.ProtocolVersion == 0 {
|
||||||
|
response := &pb.ServerMessage{
|
||||||
|
AuthSuccess: false,
|
||||||
|
ErrorMessage: "Client using outdated protocol (pre-versioning)",
|
||||||
|
ProtocolVersion: protoVersion,
|
||||||
|
}
|
||||||
|
writeMessage(conn, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if batch.ProtocolVersion < protoVersion {
|
||||||
|
response := &pb.ServerMessage{
|
||||||
|
AuthSuccess: false,
|
||||||
|
ErrorMessage: fmt.Sprintf("Client protocol version too old (client: %d, required: %d)", batch.ProtocolVersion, protoVersion),
|
||||||
|
ProtocolVersion: protoVersion,
|
||||||
|
}
|
||||||
|
writeMessage(conn, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action.Type {
|
||||||
|
case pb.Action_REGISTER:
|
||||||
|
if err := db.CheckRegistrationLimit(ip); err != nil {
|
||||||
|
response := &pb.ServerMessage{
|
||||||
|
AuthSuccess: false,
|
||||||
|
ErrorMessage: err.Error(),
|
||||||
|
}
|
||||||
|
writeMessage(conn, response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
playerID, authErr = db.RegisterPlayer(action.Username, action.Password)
|
||||||
|
case pb.Action_LOGIN:
|
||||||
|
playerID, authErr = db.AuthenticatePlayer(action.Username, action.Password)
|
||||||
|
default:
|
||||||
|
log.Printf("Invalid initial action type: %v", action.Type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send auth response
|
||||||
|
response := &pb.ServerMessage{
|
||||||
|
PlayerId: int32(playerID),
|
||||||
|
AuthSuccess: authErr == nil,
|
||||||
|
}
|
||||||
|
if authErr != nil {
|
||||||
|
response.ErrorMessage = authErr.Error()
|
||||||
|
if err := writeMessage(conn, response); err != nil {
|
||||||
|
log.Printf("Failed to send auth response: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load last known position
|
||||||
|
x, y, err := db.LoadPlayerState(playerID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error loading state for player %d: %v", playerID, err)
|
||||||
|
x, y = 5, 5 // Default position
|
||||||
|
}
|
||||||
|
|
||||||
|
username, err := db.GetUsername(playerID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting username for player %d: %v", playerID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Player %d (%s) authenticated successfully, checking for existing session", playerID, username)
|
||||||
|
|
||||||
|
// Check for existing session and force disconnect if needed
|
||||||
|
mu.Lock()
|
||||||
|
existingPlayer, alreadyLoggedIn := players[playerID]
|
||||||
|
if alreadyLoggedIn {
|
||||||
|
log.Printf("Player %d (%s) is already logged in, forcing disconnect of old session", playerID, username)
|
||||||
|
// An existing session is found - clean it up
|
||||||
|
if oldConn, exists := playerConns[playerID]; exists {
|
||||||
|
// Try to close the old connection
|
||||||
|
oldConn.Close()
|
||||||
|
delete(playerConns, playerID)
|
||||||
|
}
|
||||||
|
// Keep the player object but update its connection
|
||||||
|
existingPlayer.X = x
|
||||||
|
existingPlayer.Y = y
|
||||||
|
playerConns[playerID] = conn
|
||||||
|
mu.Unlock()
|
||||||
|
} else {
|
||||||
|
// Create a new player
|
||||||
|
player := &Player{
|
||||||
|
ID: playerID,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
Username: username,
|
||||||
|
LastSeenMsgTimestamp: 0, // Initialize to 0 to receive all messages initially
|
||||||
|
}
|
||||||
|
players[playerID] = player
|
||||||
|
playerConns[playerID] = conn
|
||||||
|
mu.Unlock()
|
||||||
|
existingPlayer = player
|
||||||
|
|
||||||
|
// Announce connection
|
||||||
|
addSystemMessage(fmt.Sprintf("%s connected", username))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure player state is saved on any kind of disconnect
|
||||||
|
defer func() {
|
||||||
|
if p, exists := players[playerID]; exists {
|
||||||
|
if err := db.SavePlayerState(playerID, p.X, p.Y); err != nil {
|
||||||
|
log.Printf("Error saving state for player %d: %v", playerID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addSystemMessage(fmt.Sprintf("%s disconnected", username))
|
||||||
|
mu.Lock()
|
||||||
|
delete(players, playerID)
|
||||||
|
delete(playerConns, playerID)
|
||||||
|
delete(actionQueue, playerID)
|
||||||
|
mu.Unlock()
|
||||||
|
log.Printf("Player %d (%s) disconnected", playerID, username)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Send initial state with correct position
|
||||||
|
response = &pb.ServerMessage{
|
||||||
|
PlayerId: int32(playerID),
|
||||||
|
AuthSuccess: true,
|
||||||
|
Players: []*pb.PlayerState{{
|
||||||
|
PlayerId: int32(playerID),
|
||||||
|
X: int32(x),
|
||||||
|
Y: int32(y),
|
||||||
|
Username: username,
|
||||||
|
}},
|
||||||
|
ProtocolVersion: protoVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send player ID to client
|
||||||
|
if err := writeMessage(conn, response); err != nil {
|
||||||
|
log.Printf("Failed to send player ID: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Player %d (%s) connected successfully", playerID, username)
|
||||||
|
|
||||||
// Listen for incoming actions from this player
|
// Listen for incoming actions from this player
|
||||||
buf := make([]byte, 4096)
|
|
||||||
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 {
|
||||||
delete(players, playerID)
|
if err == io.EOF {
|
||||||
delete(playerConns, playerID)
|
log.Printf("Player %d disconnected gracefully", playerID)
|
||||||
delete(actionQueue, playerID)
|
} else {
|
||||||
|
log.Printf("Error reading message length from player %d: %v", playerID, err)
|
||||||
|
}
|
||||||
|
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)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the last seen message timestamp
|
||||||
|
if batch.LastSeenMessageTimestamp > 0 {
|
||||||
|
existingPlayer.LastSeenMsgTimestamp = batch.LastSeenMessageTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
// Queue the actions for processing
|
// Queue the actions for processing
|
||||||
if batch.PlayerId == int32(playerID) {
|
if batch.PlayerId == int32(playerID) {
|
||||||
|
for _, action := range batch.Actions {
|
||||||
|
if action.Type == pb.Action_DISCONNECT {
|
||||||
|
log.Printf("Player %d requested disconnect", playerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
actionQueue[playerID] = append(actionQueue[playerID], batch.Actions...)
|
actionQueue[playerID] = append(actionQueue[playerID], batch.Actions...)
|
||||||
|
mu.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addChatMessage(playerID int32, content string) {
|
func addChatMessage(playerID int32, content string) {
|
||||||
|
player, exists := players[int(playerID)]
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
chatMutex.Lock()
|
chatMutex.Lock()
|
||||||
defer chatMutex.Unlock()
|
defer chatMutex.Unlock()
|
||||||
|
|
||||||
msg := &pb.ChatMessage{
|
msg := &pb.ChatMessage{
|
||||||
PlayerId: playerID,
|
PlayerId: playerID,
|
||||||
|
Username: player.Username,
|
||||||
|
Content: content,
|
||||||
|
Timestamp: time.Now().UnixNano(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(chatHistory) >= 100 {
|
||||||
|
chatHistory = chatHistory[1:]
|
||||||
|
}
|
||||||
|
chatHistory = append(chatHistory, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSystemMessage(content string) {
|
||||||
|
chatMutex.Lock()
|
||||||
|
defer chatMutex.Unlock()
|
||||||
|
|
||||||
|
msg := &pb.ChatMessage{
|
||||||
|
PlayerId: 0, // System messages use ID 0
|
||||||
|
Username: "System",
|
||||||
Content: content,
|
Content: content,
|
||||||
Timestamp: time.Now().UnixNano(),
|
Timestamp: time.Now().UnixNano(),
|
||||||
}
|
}
|
||||||
@ -129,11 +353,30 @@ func addChatMessage(playerID int32, content string) {
|
|||||||
|
|
||||||
func processActions() {
|
func processActions() {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
// Make a list of players to process first, to avoid lock contention
|
||||||
|
activePlayers := make(map[int]*Player)
|
||||||
|
for id, p := range players {
|
||||||
|
activePlayers[id] = p
|
||||||
|
}
|
||||||
|
activeConns := make(map[int]net.Conn)
|
||||||
|
for id, conn := range playerConns {
|
||||||
|
activeConns[id] = conn
|
||||||
|
}
|
||||||
|
activeQueues := make(map[int][]*pb.Action)
|
||||||
|
for id, actions := range actionQueue {
|
||||||
|
if len(actions) > 0 {
|
||||||
|
activeQueues[id] = actions
|
||||||
|
actionQueue[id] = nil // Clear the queue early to avoid double processing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
// Update players based on queued actions
|
// Process actions without holding the global lock
|
||||||
for playerID, actions := range actionQueue {
|
for playerID, actions := range activeQueues {
|
||||||
player := players[playerID]
|
player, exists := activePlayers[playerID]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
player.Lock()
|
player.Lock()
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
switch action.Type {
|
switch action.Type {
|
||||||
@ -147,40 +390,111 @@ func processActions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.Unlock()
|
player.Unlock()
|
||||||
actionQueue[playerID] = nil // Clear the action queue after processing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare and broadcast the current game state
|
// Prepare current game state
|
||||||
currentTick := time.Now().UnixNano() / int64(tickRate)
|
currentTick := time.Now().UnixNano() / int64(tickRate)
|
||||||
state := &pb.ServerMessage{
|
|
||||||
CurrentTick: currentTick,
|
|
||||||
}
|
|
||||||
|
|
||||||
for id, p := range players {
|
// Get recent messages for new connections
|
||||||
|
chatMutex.RLock()
|
||||||
|
recentMessages := chatHistory[max(0, len(chatHistory)-5):] // Get last 5 for new connections
|
||||||
|
chatMutex.RUnlock()
|
||||||
|
|
||||||
|
// To avoid holding locks too long, prepare player states first
|
||||||
|
playerStates := make([]*pb.PlayerState, 0, len(activePlayers))
|
||||||
|
for id, p := range activePlayers {
|
||||||
p.Lock()
|
p.Lock()
|
||||||
state.Players = append(state.Players, &pb.PlayerState{
|
playerStates = append(playerStates, &pb.PlayerState{
|
||||||
PlayerId: int32(id),
|
PlayerId: int32(id),
|
||||||
X: int32(p.X),
|
X: int32(p.X),
|
||||||
Y: int32(p.Y),
|
Y: int32(p.Y),
|
||||||
|
Username: p.Username,
|
||||||
})
|
})
|
||||||
p.Unlock()
|
p.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add chat messages to the server message
|
// Now send updates to each player
|
||||||
chatMutex.RLock()
|
for playerID, conn := range activeConns {
|
||||||
state.ChatMessages = chatHistory
|
player, exists := activePlayers[playerID]
|
||||||
chatMutex.RUnlock()
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
data, err := proto.Marshal(state)
|
state := &pb.ServerMessage{
|
||||||
if err != nil {
|
CurrentTick: currentTick,
|
||||||
log.Printf("Failed to marshal game state: %v", err)
|
Players: playerStates,
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Send to each connected player
|
// Add chat messages - only send those the player hasn't seen
|
||||||
for _, conn := range playerConns {
|
player.Lock()
|
||||||
if _, err := conn.Write(data); err != nil {
|
lastSeen := player.LastSeenMsgTimestamp
|
||||||
log.Printf("Failed to send update: %v", err)
|
player.Unlock()
|
||||||
|
|
||||||
|
chatMutex.RLock()
|
||||||
|
var newMessages []*pb.ChatMessage
|
||||||
|
|
||||||
|
// For new connections, send the 5 most recent messages
|
||||||
|
if lastSeen == 0 && len(recentMessages) > 0 {
|
||||||
|
newMessages = recentMessages
|
||||||
|
if len(newMessages) > 0 {
|
||||||
|
// Update the player's timestamp to the latest message
|
||||||
|
player.Lock()
|
||||||
|
player.LastSeenMsgTimestamp = newMessages[len(newMessages)-1].Timestamp
|
||||||
|
player.Unlock()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For existing connections, only send new messages
|
||||||
|
for _, msg := range chatHistory {
|
||||||
|
if msg.Timestamp > lastSeen {
|
||||||
|
newMessages = append(newMessages, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the player's timestamp if we sent them new messages
|
||||||
|
if len(newMessages) > 0 {
|
||||||
|
player.Lock()
|
||||||
|
player.LastSeenMsgTimestamp = newMessages[len(newMessages)-1].Timestamp
|
||||||
|
player.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.ChatMessages = newMessages
|
||||||
|
chatMutex.RUnlock()
|
||||||
|
|
||||||
|
// Log the number of messages we're sending
|
||||||
|
if len(newMessages) > 0 {
|
||||||
|
log.Printf("Sending %d new messages to player %d", len(newMessages), playerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the state to the player - do this without holding any locks
|
||||||
|
if err := writeMessage(conn, state); err != nil {
|
||||||
|
log.Printf("Failed to send update to player %d: %v", playerID, err)
|
||||||
|
|
||||||
|
// Handle connection errors by removing the player
|
||||||
|
mu.Lock()
|
||||||
|
delete(players, playerID)
|
||||||
|
delete(playerConns, playerID)
|
||||||
|
delete(actionQueue, playerID)
|
||||||
|
mu.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user