diff --git a/actions/actions.pb.go b/actions/actions.pb.go index 3e1ada6..9682a3d 100644 --- a/actions/actions.pb.go +++ b/actions/actions.pb.go @@ -26,6 +26,8 @@ const ( 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. @@ -34,11 +36,15 @@ var ( 0: "MOVE", 1: "CHAT", 2: "DISCONNECT", + 3: "LOGIN", + 4: "REGISTER", } Action_ActionType_value = map[string]int32{ "MOVE": 0, "CHAT": 1, "DISCONNECT": 2, + "LOGIN": 3, + "REGISTER": 4, } ) @@ -76,6 +82,8 @@ type Action struct { 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"` 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 } @@ -145,6 +153,20 @@ func (x *Action) GetChatMessage() string { 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 { state protoimpl.MessageState `protogen:"open.v1"` PlayerId int32 `protobuf:"varint,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` @@ -331,6 +353,8 @@ type ServerMessage struct { 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"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -393,11 +417,25 @@ func (x *ServerMessage) GetChatMessages() []*ChatMessage { 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 "" +} + var File_actions_proto protoreflect.FileDescriptor var file_actions_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, - 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xc6, 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, 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, @@ -406,43 +444,53 @@ var file_actions_proto_rawDesc = []byte{ 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, - 0x30, 0x0a, 0x0a, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, - 0x04, 0x4d, 0x4f, 0x56, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x48, 0x41, 0x54, 0x10, - 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, - 0x02, 0x22, 0x69, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x49, 0x64, 0x12, 0x29, 0x0a, - 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, - 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, + 0x28, 0x09, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x49, 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, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, + 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x4c, 0x4f, 0x47, + 0x49, 0x4e, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, + 0x10, 0x04, 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, 0x82, 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, 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, + 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, 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 ( diff --git a/actions/actions.proto b/actions/actions.proto index 6b3c234..93a9456 100644 --- a/actions/actions.proto +++ b/actions/actions.proto @@ -9,6 +9,8 @@ message Action { MOVE = 0; CHAT = 1; DISCONNECT = 2; + LOGIN = 3; + REGISTER = 4; } ActionType type = 1; @@ -16,6 +18,8 @@ message Action { int32 y = 3; int32 player_id = 4; string chat_message = 5; + string username = 6; + string password = 7; } message ActionBatch { @@ -41,4 +45,6 @@ message ServerMessage { repeated PlayerState players = 2; int64 current_tick = 3; repeated ChatMessage chat_messages = 4; + bool auth_success = 5; + string error_message = 6; } diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..371e193 --- /dev/null +++ b/db/db.go @@ -0,0 +1,122 @@ +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 +} diff --git a/go.mod b/go.mod index 3559eac..02c6a14 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module gitea.boner.be/bdnugget/goonserver go 1.23.0 -require google.golang.org/protobuf v1.36.3 +require ( + github.com/mattn/go-sqlite3 v1.14.24 + google.golang.org/protobuf v1.36.3 +) diff --git a/go.sum b/go.sum index be0e561..f75bbee 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ 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/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= diff --git a/goonserver.db b/goonserver.db new file mode 100644 index 0000000..375d429 Binary files /dev/null and b/goonserver.db differ diff --git a/main.go b/main.go index 4b02336..0751149 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "time" pb "gitea.boner.be/bdnugget/goonserver/actions" + "gitea.boner.be/bdnugget/goonserver/db" "google.golang.org/protobuf/proto" ) @@ -27,15 +28,20 @@ type Player struct { } var ( - 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 + 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() { + if err := db.InitDB("goonserver.db"); err != nil { + log.Fatalf("Failed to initialize database: %v", err) + } + ln, err := net.Listen("tcp", port) if err != nil { log.Fatalf("Failed to listen on port %s: %v", port, err) @@ -68,34 +74,110 @@ func main() { func handleConnection(conn net.Conn) { defer conn.Close() - mu.Lock() - playerID := len(players) + 1 - newPlayer := &Player{ID: playerID, X: 5, Y: 5} - players[playerID] = newPlayer - playerConns[playerID] = conn - mu.Unlock() - fmt.Printf("Player %d connected\n", playerID) + // Read initial message for player ID + reader := bufio.NewReader(conn) - // Send player ID to the client - serverMsg := &pb.ServerMessage{ - PlayerId: int32(playerID), - CurrentTick: 0, + // 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 } - if err := writeMessage(conn, serverMsg); err != nil { - log.Printf("Failed to send player ID to player %d: %v", playerID, err) + 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 + + 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 + } + + player := &Player{ + ID: playerID, + X: x, + Y: y, + } + + mu.Lock() + players[playerID] = player + playerConns[playerID] = conn + mu.Unlock() + + // 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) + // Listen for incoming actions from this player - reader := bufio.NewReader(conn) for { // Read message length lengthBuf := make([]byte, 4) if _, err := io.ReadFull(reader, lengthBuf); err != nil { - log.Printf("Error reading message length from player %d: %v", playerID, err) - delete(players, playerID) - delete(playerConns, playerID) - delete(actionQueue, playerID) + 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) @@ -104,9 +186,6 @@ func handleConnection(conn net.Conn) { messageBuf := make([]byte, messageLength) if _, err := io.ReadFull(reader, messageBuf); err != nil { log.Printf("Error reading message from player %d: %v", playerID, err) - delete(players, playerID) - delete(playerConns, playerID) - delete(actionQueue, playerID) return } @@ -120,10 +199,7 @@ func handleConnection(conn net.Conn) { if batch.PlayerId == int32(playerID) { for _, action := range batch.Actions { if action.Type == pb.Action_DISCONNECT { - log.Printf("Player %d disconnected gracefully", playerID) - delete(players, playerID) - delete(playerConns, playerID) - delete(actionQueue, playerID) + log.Printf("Player %d requested disconnect", playerID) return } }