Compare commits
	
		
			18 Commits
		
	
	
		
			develop
			...
			3f7205d73e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3f7205d73e | |||
| 0731339fe8 | |||
| 71d42a8fd6 | |||
| 52ab45fe53 | |||
| be32dec202 | |||
| 2d0cb12532 | |||
| b44cdab611 | |||
| b73d8de851 | |||
| 23474c19dc | |||
| a48fef0186 | |||
| 67e08c5d1e | |||
| 49e2311497 | |||
| 368fbdbc47 | |||
| 4b73492ffc | |||
| a459e8b4a5 | |||
| 8290131998 | |||
| f91f72c05d | |||
| 1d6d3ab2ea | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | *.db | ||||||
							
								
								
									
										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,7 +1,7 @@ | |||||||
| // Code generated by protoc-gen-go. DO NOT EDIT. | // Code generated by protoc-gen-go. DO NOT EDIT. | ||||||
| // versions: | // versions: | ||||||
| // 	protoc-gen-go v1.28.1 | // 	protoc-gen-go v1.36.3 | ||||||
| // 	protoc        v3.21.12 | // 	protoc        v5.29.2 | ||||||
| // source: actions.proto | // source: actions.proto | ||||||
|  |  | ||||||
| package actions | package actions | ||||||
| @ -24,15 +24,27 @@ type Action_ActionType int32 | |||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	Action_MOVE       Action_ActionType = 0 | 	Action_MOVE       Action_ActionType = 0 | ||||||
|  | 	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. | ||||||
| var ( | var ( | ||||||
| 	Action_ActionType_name = map[int32]string{ | 	Action_ActionType_name = map[int32]string{ | ||||||
| 		0: "MOVE", | 		0: "MOVE", | ||||||
|  | 		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, | ||||||
|  | 		"DISCONNECT": 2, | ||||||
|  | 		"LOGIN":      3, | ||||||
|  | 		"REGISTER":   4, | ||||||
| 	} | 	} | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -64,24 +76,24 @@ func (Action_ActionType) EnumDescriptor() ([]byte, []int) { | |||||||
| } | } | ||||||
|  |  | ||||||
| type Action struct { | type Action struct { | ||||||
| 	state         protoimpl.MessageState | 	state         protoimpl.MessageState `protogen:"open.v1"` | ||||||
| 	sizeCache     protoimpl.SizeCache |  | ||||||
| 	unknownFields protoimpl.UnknownFields |  | ||||||
|  |  | ||||||
| 	Type          Action_ActionType      `protobuf:"varint,1,opt,name=type,proto3,enum=actions.Action_ActionType" json:"type,omitempty"` | 	Type          Action_ActionType      `protobuf:"varint,1,opt,name=type,proto3,enum=actions.Action_ActionType" json:"type,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"` | ||||||
| 	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"` | ||||||
|  | 	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 | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
| } | } | ||||||
|  |  | ||||||
| func (x *Action) Reset() { | func (x *Action) Reset() { | ||||||
| 	*x = Action{} | 	*x = Action{} | ||||||
| 	if protoimpl.UnsafeEnabled { |  | ||||||
| 	mi := &file_actions_proto_msgTypes[0] | 	mi := &file_actions_proto_msgTypes[0] | ||||||
| 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
| 	ms.StoreMessageInfo(mi) | 	ms.StoreMessageInfo(mi) | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Action) String() string { | func (x *Action) String() string { | ||||||
| 	return protoimpl.X.MessageStringOf(x) | 	return protoimpl.X.MessageStringOf(x) | ||||||
| @ -91,7 +103,7 @@ func (*Action) ProtoMessage() {} | |||||||
|  |  | ||||||
| func (x *Action) ProtoReflect() protoreflect.Message { | func (x *Action) ProtoReflect() protoreflect.Message { | ||||||
| 	mi := &file_actions_proto_msgTypes[0] | 	mi := &file_actions_proto_msgTypes[0] | ||||||
| 	if protoimpl.UnsafeEnabled && 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 { | ||||||
| 			ms.StoreMessageInfo(mi) | 			ms.StoreMessageInfo(mi) | ||||||
| @ -134,33 +146,53 @@ func (x *Action) GetPlayerId() int32 { | |||||||
| 	return 0 | 	return 0 | ||||||
| } | } | ||||||
|  |  | ||||||
| type ServerMessage struct { | func (x *Action) GetChatMessage() string { | ||||||
| 	state         protoimpl.MessageState | 	if x != nil { | ||||||
| 	sizeCache     protoimpl.SizeCache | 		return x.ChatMessage | ||||||
| 	unknownFields protoimpl.UnknownFields | 	} | ||||||
|  | 	return "" | ||||||
| 	PlayerId int32          `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` // Only used when initially assigning player ID |  | ||||||
| 	Players  []*PlayerState `protobuf:"bytes,2,rep,name=players,proto3" json:"players,omitempty"`                    // Player state updates |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (x *ServerMessage) Reset() { | func (x *Action) GetUsername() string { | ||||||
| 	*x = ServerMessage{} | 	if x != nil { | ||||||
| 	if protoimpl.UnsafeEnabled { | 		return x.Username | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *Action) GetPassword() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Password | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ActionBatch struct { | ||||||
|  | 	state           protoimpl.MessageState `protogen:"open.v1"` | ||||||
|  | 	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"` | ||||||
|  | 	Tick            int64                  `protobuf:"varint,3,opt,name=tick,proto3" json:"tick,omitempty"` | ||||||
|  | 	ProtocolVersion int32                  `protobuf:"varint,4,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` | ||||||
|  | 	unknownFields   protoimpl.UnknownFields | ||||||
|  | 	sizeCache       protoimpl.SizeCache | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ActionBatch) Reset() { | ||||||
|  | 	*x = ActionBatch{} | ||||||
| 	mi := &file_actions_proto_msgTypes[1] | 	mi := &file_actions_proto_msgTypes[1] | ||||||
| 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
| 	ms.StoreMessageInfo(mi) | 	ms.StoreMessageInfo(mi) | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *ServerMessage) String() string { | func (x *ActionBatch) String() string { | ||||||
| 	return protoimpl.X.MessageStringOf(x) | 	return protoimpl.X.MessageStringOf(x) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (*ServerMessage) ProtoMessage() {} | func (*ActionBatch) ProtoMessage() {} | ||||||
|  |  | ||||||
| func (x *ServerMessage) ProtoReflect() protoreflect.Message { | func (x *ActionBatch) ProtoReflect() protoreflect.Message { | ||||||
| 	mi := &file_actions_proto_msgTypes[1] | 	mi := &file_actions_proto_msgTypes[1] | ||||||
| 	if protoimpl.UnsafeEnabled && 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 { | ||||||
| 			ms.StoreMessageInfo(mi) | 			ms.StoreMessageInfo(mi) | ||||||
| @ -170,43 +202,55 @@ func (x *ServerMessage) ProtoReflect() protoreflect.Message { | |||||||
| 	return mi.MessageOf(x) | 	return mi.MessageOf(x) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Deprecated: Use ServerMessage.ProtoReflect.Descriptor instead. | // Deprecated: Use ActionBatch.ProtoReflect.Descriptor instead. | ||||||
| func (*ServerMessage) Descriptor() ([]byte, []int) { | func (*ActionBatch) Descriptor() ([]byte, []int) { | ||||||
| 	return file_actions_proto_rawDescGZIP(), []int{1} | 	return file_actions_proto_rawDescGZIP(), []int{1} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (x *ServerMessage) GetPlayerId() int32 { | func (x *ActionBatch) GetPlayerId() int32 { | ||||||
| 	if x != nil { | 	if x != nil { | ||||||
| 		return x.PlayerId | 		return x.PlayerId | ||||||
| 	} | 	} | ||||||
| 	return 0 | 	return 0 | ||||||
| } | } | ||||||
|  |  | ||||||
| func (x *ServerMessage) GetPlayers() []*PlayerState { | func (x *ActionBatch) GetActions() []*Action { | ||||||
| 	if x != nil { | 	if x != nil { | ||||||
| 		return x.Players | 		return x.Actions | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| type PlayerState struct { | func (x *ActionBatch) GetTick() int64 { | ||||||
| 	state         protoimpl.MessageState | 	if x != nil { | ||||||
| 	sizeCache     protoimpl.SizeCache | 		return x.Tick | ||||||
| 	unknownFields protoimpl.UnknownFields | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ActionBatch) GetProtocolVersion() int32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.ProtocolVersion | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PlayerState struct { | ||||||
|  | 	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 | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
| } | } | ||||||
|  |  | ||||||
| func (x *PlayerState) Reset() { | func (x *PlayerState) Reset() { | ||||||
| 	*x = PlayerState{} | 	*x = PlayerState{} | ||||||
| 	if protoimpl.UnsafeEnabled { |  | ||||||
| 	mi := &file_actions_proto_msgTypes[2] | 	mi := &file_actions_proto_msgTypes[2] | ||||||
| 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
| 	ms.StoreMessageInfo(mi) | 	ms.StoreMessageInfo(mi) | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *PlayerState) String() string { | func (x *PlayerState) String() string { | ||||||
| 	return protoimpl.X.MessageStringOf(x) | 	return protoimpl.X.MessageStringOf(x) | ||||||
| @ -216,7 +260,7 @@ func (*PlayerState) ProtoMessage() {} | |||||||
|  |  | ||||||
| func (x *PlayerState) ProtoReflect() protoreflect.Message { | func (x *PlayerState) ProtoReflect() protoreflect.Message { | ||||||
| 	mi := &file_actions_proto_msgTypes[2] | 	mi := &file_actions_proto_msgTypes[2] | ||||||
| 	if protoimpl.UnsafeEnabled && 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 { | ||||||
| 			ms.StoreMessageInfo(mi) | 			ms.StoreMessageInfo(mi) | ||||||
| @ -252,33 +296,242 @@ func (x *PlayerState) GetY() int32 { | |||||||
| 	return 0 | 	return 0 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (x *PlayerState) GetUsername() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Username | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ChatMessage struct { | ||||||
|  | 	state         protoimpl.MessageState `protogen:"open.v1"` | ||||||
|  | 	PlayerId      int32                  `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` | ||||||
|  | 	Username      string                 `protobuf:"bytes,2,opt,name=username,proto3" json:"username,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 | ||||||
|  | 	sizeCache     protoimpl.SizeCache | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ChatMessage) Reset() { | ||||||
|  | 	*x = ChatMessage{} | ||||||
|  | 	mi := &file_actions_proto_msgTypes[3] | ||||||
|  | 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 	ms.StoreMessageInfo(mi) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ChatMessage) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*ChatMessage) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *ChatMessage) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_actions_proto_msgTypes[3] | ||||||
|  | 	if x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use ChatMessage.ProtoReflect.Descriptor instead. | ||||||
|  | func (*ChatMessage) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_actions_proto_rawDescGZIP(), []int{3} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ChatMessage) GetPlayerId() int32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.PlayerId | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ChatMessage) GetUsername() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Username | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ChatMessage) GetContent() string { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Content | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ChatMessage) GetTimestamp() int64 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Timestamp | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ServerMessage struct { | ||||||
|  | 	state           protoimpl.MessageState `protogen:"open.v1"` | ||||||
|  | 	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"` | ||||||
|  | 	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"` | ||||||
|  | 	AuthSuccess     bool                   `protobuf:"varint,5,opt,name=auth_success,json=authSuccess,proto3" json:"auth_success,omitempty"` | ||||||
|  | 	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() { | ||||||
|  | 	*x = ServerMessage{} | ||||||
|  | 	mi := &file_actions_proto_msgTypes[4] | ||||||
|  | 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 	ms.StoreMessageInfo(mi) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ServerMessage) String() string { | ||||||
|  | 	return protoimpl.X.MessageStringOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*ServerMessage) ProtoMessage() {} | ||||||
|  |  | ||||||
|  | func (x *ServerMessage) ProtoReflect() protoreflect.Message { | ||||||
|  | 	mi := &file_actions_proto_msgTypes[4] | ||||||
|  | 	if x != nil { | ||||||
|  | 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||||||
|  | 		if ms.LoadMessageInfo() == nil { | ||||||
|  | 			ms.StoreMessageInfo(mi) | ||||||
|  | 		} | ||||||
|  | 		return ms | ||||||
|  | 	} | ||||||
|  | 	return mi.MessageOf(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Deprecated: Use ServerMessage.ProtoReflect.Descriptor instead. | ||||||
|  | func (*ServerMessage) Descriptor() ([]byte, []int) { | ||||||
|  | 	return file_actions_proto_rawDescGZIP(), []int{4} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ServerMessage) GetPlayerId() int32 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.PlayerId | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ServerMessage) GetPlayers() []*PlayerState { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.Players | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ServerMessage) GetCurrentTick() int64 { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.CurrentTick | ||||||
|  | 	} | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ServerMessage) GetChatMessages() []*ChatMessage { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.ChatMessages | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ServerMessage) GetAuthSuccess() bool { | ||||||
|  | 	if x != nil { | ||||||
|  | 		return x.AuthSuccess | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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_proto protoreflect.FileDescriptor | var File_actions_proto protoreflect.FileDescriptor | ||||||
|  |  | ||||||
| var file_actions_proto_rawDesc = []byte{ | var file_actions_proto_rawDesc = []byte{ | ||||||
| 	0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, | 	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, 0x89, 0x01, 0x0a, 0x06, 0x41, 0x63, 0x74, | 	0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x97, 0x02, 0x0a, 0x06, 0x41, 0x63, 0x74, | ||||||
| 	0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, | 	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, | 	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, | 	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, | 	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, | 	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, | 	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, 0x22, 0x16, 0x0a, 0x0a, | 	0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, | ||||||
| 	0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x4f, | 	0x63, 0x68, 0x61, 0x74, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, | ||||||
| 	0x56, 0x45, 0x10, 0x00, 0x22, 0x5c, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, | 	0x28, 0x09, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, | ||||||
| 	0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, | 	0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, | ||||||
| 	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, | 	0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, | ||||||
| 	0x49, 0x64, 0x12, 0x2e, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, | 	0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, | ||||||
| 	0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x50, 0x6c, | 	0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x49, 0x0a, 0x0a, 0x41, 0x63, 0x74, 0x69, 0x6f, | ||||||
| 	0x61, 0x79, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x07, 0x70, 0x6c, 0x61, 0x79, 0x65, | 	0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x4f, 0x56, 0x45, 0x10, 0x00, 0x12, | ||||||
| 	0x72, 0x73, 0x22, 0x46, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, | 	0x08, 0x0a, 0x04, 0x43, 0x48, 0x41, 0x54, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, | ||||||
| 	0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, | 	0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x4f, 0x47, | ||||||
| 	0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, 0x0c, | 	0x49, 0x4e, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, | ||||||
| 	0x0a, 0x01, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x78, 0x12, 0x0c, 0x0a, 0x01, | 	0x10, 0x04, 0x22, 0x94, 0x01, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x74, | ||||||
| 	0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x01, 0x79, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, | 	0x63, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, | ||||||
| 	0x74, 0x65, 0x61, 0x2e, 0x62, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x62, 0x65, 0x2f, 0x62, 0x64, 0x6e, | 	0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, | ||||||
| 	0x75, 0x67, 0x67, 0x65, 0x74, 0x2f, 0x67, 0x6f, 0x6f, 0x6e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, | 	0x29, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, | ||||||
| 	0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | 	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, 0x12, 0x29, | ||||||
|  | 	0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, | ||||||
|  | 	0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, | ||||||
|  | 	0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x62, 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, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, | ||||||
|  | 	0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x7e, 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, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, | ||||||
|  | 	0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, | ||||||
|  | 	0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, | ||||||
|  | 	0x18, 0x03, 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, 0x04, 0x20, 0x01, | ||||||
|  | 	0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xad, 0x02, | ||||||
|  | 	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, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x75, | ||||||
|  | 	0x74, 0x68, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, | ||||||
|  | 	0x52, 0x0b, 0x61, 0x75, 0x74, 0x68, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x23, 0x0a, | ||||||
|  | 	0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, | ||||||
|  | 	0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, | ||||||
|  | 	0x67, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x76, | ||||||
|  | 	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x70, 0x72, | ||||||
|  | 	0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 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, | ||||||
| } | } | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| @ -294,21 +547,25 @@ func file_actions_proto_rawDescGZIP() []byte { | |||||||
| } | } | ||||||
|  |  | ||||||
| var file_actions_proto_enumTypes = make([]protoimpl.EnumInfo, 1) | var file_actions_proto_enumTypes = make([]protoimpl.EnumInfo, 1) | ||||||
| var file_actions_proto_msgTypes = make([]protoimpl.MessageInfo, 3) | var file_actions_proto_msgTypes = make([]protoimpl.MessageInfo, 5) | ||||||
| var file_actions_proto_goTypes = []interface{}{ | var file_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 | ||||||
| 	(*ServerMessage)(nil),  // 2: actions.ServerMessage | 	(*ActionBatch)(nil),    // 2: actions.ActionBatch | ||||||
| 	(*PlayerState)(nil),    // 3: actions.PlayerState | 	(*PlayerState)(nil),    // 3: actions.PlayerState | ||||||
|  | 	(*ChatMessage)(nil),    // 4: actions.ChatMessage | ||||||
|  | 	(*ServerMessage)(nil),  // 5: actions.ServerMessage | ||||||
| } | } | ||||||
| var file_actions_proto_depIdxs = []int32{ | var file_actions_proto_depIdxs = []int32{ | ||||||
| 	0, // 0: actions.Action.type:type_name -> actions.Action.ActionType | 	0, // 0: actions.Action.type:type_name -> actions.Action.ActionType | ||||||
| 	3, // 1: actions.ServerMessage.players:type_name -> actions.PlayerState | 	1, // 1: actions.ActionBatch.actions:type_name -> actions.Action | ||||||
| 	2, // [2:2] is the sub-list for method output_type | 	3, // 2: actions.ServerMessage.players:type_name -> actions.PlayerState | ||||||
| 	2, // [2:2] is the sub-list for method input_type | 	4, // 3: actions.ServerMessage.chat_messages:type_name -> actions.ChatMessage | ||||||
| 	2, // [2:2] is the sub-list for extension type_name | 	4, // [4:4] is the sub-list for method output_type | ||||||
| 	2, // [2:2] is the sub-list for extension extendee | 	4, // [4:4] is the sub-list for method input_type | ||||||
| 	0, // [0:2] is the sub-list for field type_name | 	4, // [4:4] is the sub-list for extension type_name | ||||||
|  | 	4, // [4:4] is the sub-list for extension extendee | ||||||
|  | 	0, // [0:4] is the sub-list for field type_name | ||||||
| } | } | ||||||
|  |  | ||||||
| func init() { file_actions_proto_init() } | func init() { file_actions_proto_init() } | ||||||
| @ -316,51 +573,13 @@ func file_actions_proto_init() { | |||||||
| 	if File_actions_proto != nil { | 	if File_actions_proto != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if !protoimpl.UnsafeEnabled { |  | ||||||
| 		file_actions_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { |  | ||||||
| 			switch v := v.(*Action); i { |  | ||||||
| 			case 0: |  | ||||||
| 				return &v.state |  | ||||||
| 			case 1: |  | ||||||
| 				return &v.sizeCache |  | ||||||
| 			case 2: |  | ||||||
| 				return &v.unknownFields |  | ||||||
| 			default: |  | ||||||
| 				return nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		file_actions_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { |  | ||||||
| 			switch v := v.(*ServerMessage); i { |  | ||||||
| 			case 0: |  | ||||||
| 				return &v.state |  | ||||||
| 			case 1: |  | ||||||
| 				return &v.sizeCache |  | ||||||
| 			case 2: |  | ||||||
| 				return &v.unknownFields |  | ||||||
| 			default: |  | ||||||
| 				return nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		file_actions_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { |  | ||||||
| 			switch v := v.(*PlayerState); i { |  | ||||||
| 			case 0: |  | ||||||
| 				return &v.state |  | ||||||
| 			case 1: |  | ||||||
| 				return &v.sizeCache |  | ||||||
| 			case 2: |  | ||||||
| 				return &v.unknownFields |  | ||||||
| 			default: |  | ||||||
| 				return nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	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: file_actions_proto_rawDesc, | ||||||
| 			NumEnums:      1, | 			NumEnums:      1, | ||||||
| 			NumMessages:   3, | 			NumMessages:   5, | ||||||
| 			NumExtensions: 0, | 			NumExtensions: 0, | ||||||
| 			NumServices:   0, | 			NumServices:   0, | ||||||
| 		}, | 		}, | ||||||
|  | |||||||
| @ -7,21 +7,48 @@ option go_package = "gitea.boner.be/bdnugget/goonserver/actions"; | |||||||
| message Action { | message Action { | ||||||
|     enum ActionType { |     enum ActionType { | ||||||
|         MOVE = 0; |         MOVE = 0; | ||||||
|  |         CHAT = 1; | ||||||
|  |         DISCONNECT = 2; | ||||||
|  |         LOGIN = 3; | ||||||
|  |         REGISTER = 4; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     ActionType type = 1; |     ActionType type = 1; | ||||||
|     int32 x = 2; |     int32 x = 2; | ||||||
|     int32 y = 3; |     int32 y = 3; | ||||||
|     int32 player_id = 4; |     int32 player_id = 4; | ||||||
|  |     string chat_message = 5; | ||||||
|  |     string username = 6; | ||||||
|  |     string password = 7; | ||||||
| } | } | ||||||
|  |  | ||||||
| message ServerMessage { | message ActionBatch { | ||||||
|     int32 player_id = 1; // Only used when initially assigning player ID |     int32 player_id = 1; | ||||||
|     repeated PlayerState players = 2; // Player state updates |     repeated Action actions = 2; | ||||||
|  |     int64 tick = 3; | ||||||
|  |     int32 protocol_version = 4; | ||||||
| } | } | ||||||
|  |  | ||||||
| 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 { | ||||||
|  |     int32 player_id = 1; | ||||||
|  |     string username = 2; | ||||||
|  |     string content = 3; | ||||||
|  |     int64 timestamp = 4; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message ServerMessage { | ||||||
|  |     int32 player_id = 1; | ||||||
|  |     repeated PlayerState players = 2; | ||||||
|  |     int64 current_tick = 3; | ||||||
|  |     repeated ChatMessage chat_messages = 4; | ||||||
|  |     bool auth_success = 5; | ||||||
|  |     string error_message = 6; | ||||||
|  |     int32 protocol_version = 7; | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										128
									
								
								db/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								db/db.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | |||||||
|  | package db | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/sha256" | ||||||
|  | 	"database/sql" | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"errors" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	_ "github.com/mattn/go-sqlite3" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var db *sql.DB | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	ErrUserExists         = errors.New("username already exists") | ||||||
|  | 	ErrInvalidCredentials = errors.New("invalid username or password") | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | 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= | ||||||
|  | |||||||
							
								
								
									
										324
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										324
									
								
								main.go
									
									
									
									
									
								
							| @ -1,12 +1,17 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"encoding/binary" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net" | 	"net" | ||||||
|  | 	"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" | ||||||
| ) | ) | ||||||
| @ -14,17 +19,31 @@ import ( | |||||||
| 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 | ||||||
| 	ID       int | 	ID       int | ||||||
| 	X, Y int // Position on the game grid | 	X, Y     int | ||||||
|  | 	Username string | ||||||
| } | } | ||||||
|  |  | ||||||
| var players = make(map[int]*Player) | var ( | ||||||
| var actionQueue = make(map[int][]*pb.Action) // Queue to store actions for each player | 	players      = make(map[int]*Player) | ||||||
|  | 	actionQueue  = make(map[int][]*pb.Action) // Queue to store actions for each player | ||||||
|  | 	playerConns  = make(map[int]net.Conn)     // Map to store player connections | ||||||
|  | 	mu           sync.RWMutex                 // Add mutex for protecting shared maps | ||||||
|  | 	chatHistory  = make([]*pb.ChatMessage, 0, 100) | ||||||
|  | 	chatMutex    sync.RWMutex | ||||||
|  | 	nextPlayerID = 1 // Assuming player IDs start from 1 | ||||||
|  | ) | ||||||
|  |  | ||||||
| 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) | ||||||
| @ -32,6 +51,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() | ||||||
| @ -43,69 +67,293 @@ func main() { | |||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| 	lastTick := time.Now() | 	// Main game loop | ||||||
| 	for { | 	for range ticker.C { | ||||||
| 		if time.Since(lastTick) >= tickRate { |  | ||||||
| 			lastTick = time.Now() |  | ||||||
| 			// log.Printf("Last tick: %s", lastTick) |  | ||||||
| 		processActions() | 		processActions() | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
| func handleConnection(conn net.Conn) { | func handleConnection(conn net.Conn) { | ||||||
| 	defer conn.Close() | 	defer conn.Close() | ||||||
|  |  | ||||||
| 	// Assign a new player ID and add the player to the game state | 	// Read initial message for player ID | ||||||
| 	playerID := len(players) + 1 | 	reader := bufio.NewReader(conn) | ||||||
| 	players[playerID] = &Player{ID: playerID, X: 5, Y: 5} // Start at default position |  | ||||||
|  | 	// 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 | ||||||
|  | 	} | ||||||
|  | 	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: | ||||||
|  | 		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 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	player := &Player{ | ||||||
|  | 		ID:       playerID, | ||||||
|  | 		X:        x, | ||||||
|  | 		Y:        y, | ||||||
|  | 		Username: username, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Prevent multiple logins | ||||||
|  | 	mu.Lock() | ||||||
|  | 	for _, p := range players { | ||||||
|  | 		if p.Username == username { | ||||||
|  | 			mu.Unlock() | ||||||
|  | 			response := &pb.ServerMessage{ | ||||||
|  | 				AuthSuccess:  false, | ||||||
|  | 				ErrorMessage: "Account already logged in", | ||||||
|  | 			} | ||||||
|  | 			writeMessage(conn, response) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	players[playerID] = player | ||||||
|  | 	playerConns[playerID] = conn | ||||||
|  | 	mu.Unlock() | ||||||
|  |  | ||||||
|  | 	// 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, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Ensure player state is saved on any kind of disconnect | ||||||
|  | 	defer func() { | ||||||
|  | 		if err := db.SavePlayerState(playerID, player.X, player.Y); err != nil { | ||||||
|  | 			log.Printf("Error saving state for player %d: %v", playerID, err) | ||||||
|  | 		} | ||||||
|  | 		mu.Lock() | ||||||
|  | 		delete(players, playerID) | ||||||
|  | 		delete(playerConns, playerID) | ||||||
|  | 		delete(actionQueue, playerID) | ||||||
|  | 		mu.Unlock() | ||||||
|  | 		log.Printf("Player %d disconnected", playerID) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// Send player ID to client | ||||||
|  | 	if err := writeMessage(conn, response); err != nil { | ||||||
|  | 		log.Printf("Failed to send player ID: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	fmt.Printf("Player %d connected\n", playerID) | 	fmt.Printf("Player %d connected\n", playerID) | ||||||
|  |  | ||||||
| 	// Send player ID to the client |  | ||||||
| 	serverMsg := &pb.ServerMessage{ |  | ||||||
| 		PlayerId: int32(playerID), |  | ||||||
| 	} |  | ||||||
| 	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 { |  | ||||||
| 		log.Printf("Failed to send player ID to player %d: %v", playerID, err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// 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 { | ||||||
|  | 				log.Printf("Player %d disconnected gracefully", 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 | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		action := &pb.Action{} | 		batch := &pb.ActionBatch{} | ||||||
| 		if err := proto.Unmarshal(buf[:n], action); err != nil { | 		if err := proto.Unmarshal(messageBuf, batch); err != nil { | ||||||
| 			log.Printf("Failed to unmarshal action for player %d: %v", playerID, err) | 			log.Printf("Failed to unmarshal action batch for player %d: %v", playerID, err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Queue the action for processing in the game loop | 		// Queue the actions for processing | ||||||
| 		actionQueue[playerID] = append(actionQueue[playerID], action) | 		if batch.PlayerId == int32(playerID) { | ||||||
|  | 			for _, action := range batch.Actions { | ||||||
|  | 				if action.Type == pb.Action_DISCONNECT { | ||||||
|  | 					log.Printf("Player %d requested disconnect", playerID) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			actionQueue[playerID] = append(actionQueue[playerID], batch.Actions...) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func addChatMessage(playerID int32, content string) { | ||||||
|  | 	player, exists := players[int(playerID)] | ||||||
|  | 	if !exists { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	chatMutex.Lock() | ||||||
|  | 	defer chatMutex.Unlock() | ||||||
|  |  | ||||||
|  | 	msg := &pb.ChatMessage{ | ||||||
|  | 		PlayerId:  playerID, | ||||||
|  | 		Username:  player.Username, | ||||||
|  | 		Content:   content, | ||||||
|  | 		Timestamp: time.Now().UnixNano(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(chatHistory) >= 100 { | ||||||
|  | 		chatHistory = chatHistory[1:] | ||||||
|  | 	} | ||||||
|  | 	chatHistory = append(chatHistory, msg) | ||||||
|  | } | ||||||
|  |  | ||||||
| func processActions() { | func processActions() { | ||||||
|  | 	mu.Lock() | ||||||
|  | 	defer mu.Unlock() | ||||||
|  |  | ||||||
|  | 	// Update players based on queued actions | ||||||
| 	for playerID, actions := range actionQueue { | 	for playerID, actions := range actionQueue { | ||||||
| 		player := players[playerID] | 		player := players[playerID] | ||||||
|  | 		player.Lock() | ||||||
| 		for _, action := range actions { | 		for _, action := range actions { | ||||||
| 			if action.Type == pb.Action_MOVE { | 			switch action.Type { | ||||||
|  | 			case pb.Action_MOVE: | ||||||
| 				player.X = int(action.X) | 				player.X = int(action.X) | ||||||
| 				player.Y = int(action.Y) | 				player.Y = int(action.Y) | ||||||
| 				fmt.Printf("Player %d moved to (%d, %d)\n", playerID, player.X, player.Y) | 				fmt.Printf("Player %d moved to (%d, %d)\n", playerID, player.X, player.Y) | ||||||
|  | 			case pb.Action_CHAT: | ||||||
|  | 				addChatMessage(int32(playerID), action.ChatMessage) | ||||||
|  | 				fmt.Printf("Player %d says: %s\n", playerID, action.ChatMessage) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		actionQueue[playerID] = nil // Clear the queue after processing | 		player.Unlock() | ||||||
|  | 		actionQueue[playerID] = nil // Clear the action queue after processing | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Prepare and broadcast the current game state | ||||||
|  | 	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{ | ||||||
|  | 			PlayerId: int32(id), | ||||||
|  | 			X:        int32(p.X), | ||||||
|  | 			Y:        int32(p.Y), | ||||||
|  | 			Username: p.Username, | ||||||
|  | 		}) | ||||||
|  | 		p.Unlock() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Add chat messages to the state | ||||||
|  | 	chatMutex.RLock() | ||||||
|  | 	state.ChatMessages = chatHistory[max(0, len(chatHistory)-5):] // Only send last 5 messages | ||||||
|  | 	chatMutex.RUnlock() | ||||||
|  |  | ||||||
|  | 	// Send to each connected player | ||||||
|  | 	for _, conn := range playerConns { | ||||||
|  | 		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