From cb6ee657f0350e438030d8a04f8deb844bb0e1a312dd65fd18e7c2f6440adeab Mon Sep 17 00:00:00 2001 From: raven <7156279+RavenX8@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:41:07 -0500 Subject: [PATCH] - add: Character service can now actually create a character correctly in the database - add: character db client to allow the character service to talk to the database service - update: character.proto to make character data shared --- character-service/Cargo.toml | 2 + character-service/build.rs | 4 +- character-service/src/character_db_client.rs | 143 ++++++++++++++++++ character-service/src/character_service.rs | 76 +++++++--- character-service/src/main.rs | 11 +- .../src/grpc/character_service.rs | 11 +- packet-service/build.rs | 2 +- packet-service/src/character_client.rs | 18 ++- packet-service/src/handlers/character.rs | 32 ++-- packet-service/src/main.rs | 5 +- proto/character.proto | 42 +++-- proto/character_common.proto | 31 ++++ proto/character_db_api.proto | 13 +- 13 files changed, 318 insertions(+), 72 deletions(-) create mode 100644 character-service/src/character_db_client.rs create mode 100644 proto/character_common.proto diff --git a/character-service/Cargo.toml b/character-service/Cargo.toml index 6caf840..a2744a3 100644 --- a/character-service/Cargo.toml +++ b/character-service/Cargo.toml @@ -13,6 +13,8 @@ tracing-subscriber = "0.3.18" tonic = "0.12.3" prost = "0.13.4" warp = "0.3.7" +async-trait = "0.1.83" +serde_json = "1.0.133" [build-dependencies] tonic-build = "0.12.3" \ No newline at end of file diff --git a/character-service/build.rs b/character-service/build.rs index 951a6e0..ef399ad 100644 --- a/character-service/build.rs +++ b/character-service/build.rs @@ -4,13 +4,13 @@ fn main() { .build_server(true) // Generate gRPC server code .compile_well_known_types(true) .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") - .compile_protos(&["../proto/character.proto"], &["../proto"]) + .compile_protos(&["../proto/character_common.proto", "../proto/character.proto"], &["../proto"]) .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); // gRPC Client code tonic_build::configure() .build_server(false) // Generate gRPC client code .compile_well_known_types(true) - .compile_protos(&["../proto/user_db_api.proto", "../proto/character_db_api.proto", "../proto/auth.proto"], &["../proto"]) + .compile_protos(&["../proto/character_db_api.proto", "../proto/auth.proto"], &["../proto"]) .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); } diff --git a/character-service/src/character_db_client.rs b/character-service/src/character_db_client.rs new file mode 100644 index 0000000..0370f4e --- /dev/null +++ b/character-service/src/character_db_client.rs @@ -0,0 +1,143 @@ +use tonic::transport::Channel; +use serde::{Deserialize, Serialize}; +use crate::database::character_service_client::CharacterServiceClient; +use crate::database::{CreateCharacterRequest, CreateCharacterResponse, DeleteCharacterRequest, DeleteCharacterResponse}; + +#[derive(Clone)] +pub struct CharacterDbClient { + client: CharacterServiceClient, +} + +#[derive(Debug, Deserialize, Serialize)] +struct Item { + item_id: i32, + item_type: i32, + count: i32, + durability: f32, + slot: i32, +} + +#[derive(Debug, Deserialize, Serialize)] +struct Looks { + race: i32, + face: i32, + hair: i32, +} + +#[derive(Debug, Deserialize, Serialize)] +struct Position { + map_id: i32, + x: f64, + y: f64, + z: f64, +} + +#[derive(Debug, Deserialize, Serialize)] +struct Stats { + job: u16, + str: u16, + dex: u16, + int: u16, + con: u16, + charm: u16, + sense: u16, + max_hp: i32, + hp: i32, + max_mp: i32, + mp: i32, + xp: u32, + level: u16, + head_size: u8, + body_size: u8, + stat_points: u32, + skill_points: u32, + penalty_xp: u32, + stamina: u16, + pat_hp: u16, + pat_cooldown_time: u32, +} + +impl CharacterDbClient { + pub async fn connect(endpoint: &str) -> Result> { + let client = CharacterServiceClient::connect(endpoint.to_string()).await?; + Ok(Self { client }) + } + + pub async fn create_character(&mut self, user_id: &str, name: &str, race: i32, face: i32, hair: i32, stone: i32) -> Result> { + let mut hatid = 221; + if 0 == race { + hatid = 222; + } + + let inventory = vec![ + Item { + item_id: 30, + item_type: 3, + count: 1, + slot: 3, + durability: 45.0, + }, + Item { + item_id: 1, + item_type: 8, + count: 1, + slot: 7, + durability: 45.0, + }, + Item { + item_id: hatid, + item_type: 2, + count: 1, + slot: 12, + durability: 45.0, + }, + ]; + + let stats = Stats { + job: 0, + str: 10, + dex: 10, + int: 10, + con: 10, + charm: 10, + sense: 10, + max_hp: 81, + hp: 81, + max_mp: 55, + mp: 55, + xp: 0, + level: 1, + head_size: 1, + body_size: 1, + skill_points: 0, + stat_points: 0, + penalty_xp: 0, + stamina: 0, + pat_hp: 0, + pat_cooldown_time: 0, + }; + + let looks = Looks {race, face, hair}; + let position = Position {map_id: 20, x: 5200.00, y: 5200.00, z: 1.0}; + + let request = tonic::Request::new(CreateCharacterRequest { + user_id: user_id.parse().unwrap(), + name: name.to_string(), + inventory: serde_json::to_value(inventory)?.to_string(), + stats: serde_json::to_value(stats)?.to_string(), + looks: serde_json::to_value(looks)?.to_string(), + position: serde_json::to_value(position)?.to_string(), + }); + let response = self.client.create_character(request).await?; + Ok(response.into_inner()) + } + + pub async fn delete_character(&mut self, user_id: &str, char_id: &str) -> Result> { + let request = tonic::Request::new(DeleteCharacterRequest { + user_id: user_id.parse().unwrap(), + character_id: char_id.parse().unwrap(), + }); + let response = self.client.delete_character(request).await?; + Ok(response.into_inner()) + } +} \ No newline at end of file diff --git a/character-service/src/character_service.rs b/character-service/src/character_service.rs index d9f49cf..5cd0114 100644 --- a/character-service/src/character_service.rs +++ b/character-service/src/character_service.rs @@ -1,15 +1,23 @@ +use std::sync::Arc; use tracing::debug; use tonic::{Request, Response, Status}; -use tracing::field::debug; +use utils::null_string::NullTerminatedString; +use crate::character_db_client::CharacterDbClient; use crate::character_service::character::character_service_server::CharacterService; -use crate::character_service::character::{Character, CreateCharacterRequest, DeleteCharacterRequest, Empty, GetCharacterListRequest, GetCharacterListResponse, GetCharacterRequest}; +use crate::character_service::character::{CreateCharacterRequest, CreateCharacterResponse, DeleteCharacterRequest, DeleteCharacterResponse, Empty, GetCharacterListRequest, GetCharacterListResponse, GetCharacterRequest}; +use crate::character_service::character_common::{Character, Looks, Stats}; +pub mod character_common { + tonic::include_proto!("character_common"); +} pub mod character { + tonic::include_proto!("character"); } -#[derive(Default)] -pub struct MyCharacterService {} +pub struct MyCharacterService { + pub character_db_client: Arc, +} #[tonic::async_trait] impl CharacterService for MyCharacterService { @@ -19,30 +27,41 @@ impl CharacterService for MyCharacterService { let user_id = req.user_id; debug!("Character list for User ID: {}", user_id); + // todo: get the data from the database + // self.character_db_client.as_ref(); + // Simulated database fetch for characters let characters = vec![ Character { character_id: "1".to_string(), name: "Warrior123".to_string(), - level: 1, - race: 1, - job: 111, - last_played: 1633017600, // Example timestamp + last_played: 1633017600, delete_time: 0, - face: 1, - hair: 0, + stats: Option::from(Stats { + job: 0, + level: 0, + }), + looks: Option::from(Looks { + race: 0, + hair: 0, + face: 0, + }), items: vec![], }, Character { character_id: "2".to_string(), name: "Mage123".to_string(), - level: 20, - race: 0, - job: 211, - last_played: 1633017600, // Example timestamp + last_played: 1633017600, delete_time: 0, - face: 1, - hair: 0, + stats: Option::from(Stats { + job: 211, + level: 20, + }), + looks: Option::from(Looks { + race: 1, + hair: 0, + face: 1, + }), items: vec![], }, ]; @@ -51,15 +70,30 @@ impl CharacterService for MyCharacterService { Ok(Response::new(response)) } - async fn create_character(&self, request: Request) -> Result, Status> { - todo!() + async fn create_character(&self, request: Request) -> Result, Status> { + let req = request.into_inner(); + debug!("{:?}", req); + + let create_character_response = self.character_db_client.as_ref().clone().create_character(&req.user_id, &req.name, req.race, req.face, req.hair, req.stone) + .await + .map_err(|_| Status::aborted("Unable to create character"))?; + + let response = CreateCharacterResponse { result: create_character_response.result }; + Ok(Response::new(response)) } - async fn delete_character(&self, request: Request) -> Result, Status> { - todo!() + async fn delete_character(&self, request: Request) -> Result, Status> { + let req = request.into_inner(); + debug!("{:?}", req); + + let delete_character_response = self.character_db_client.as_ref().clone().delete_character(&req.user_id, &req.char_id).await.map_err(|_| Status::not_found("Character not found"))?; + let response = DeleteCharacterResponse { remaining_time: delete_character_response.remaining_time, name: delete_character_response.name }; + Ok(Response::new(response)) } async fn get_character(&self, request: Request) -> Result, Status> { - todo!() + let req = request.into_inner(); + debug!("{:?}", req); + Ok(Response::new(Empty{})) } } \ No newline at end of file diff --git a/character-service/src/main.rs b/character-service/src/main.rs index c4f184b..ab3041f 100644 --- a/character-service/src/main.rs +++ b/character-service/src/main.rs @@ -1,13 +1,19 @@ mod character_service; +mod character_db_client; +pub mod database { + tonic::include_proto!("character_db_api"); +} use dotenv::dotenv; use std::collections::HashMap; use std::env; use std::str::FromStr; +use std::sync::Arc; use tokio::{select, signal}; use tracing::{info, Level}; use utils::consul_registration; use utils::service_discovery::get_service_address; +use crate::character_db_client::CharacterDbClient; use crate::character_service::character::character_service_server::CharacterServiceServer; use crate::character_service::MyCharacterService; @@ -55,7 +61,10 @@ async fn main() -> Result<(), Box> { let address = full_addr.parse().expect("Invalid address"); let db_address = db_nodes.get(0).unwrap(); let db_url = format!("http://{}:{}", db_address.ServiceAddress, db_address.ServicePort); - let character_service = MyCharacterService::default(); + let character_db_client = Arc::new(CharacterDbClient::connect(&db_url).await?); + let character_service = MyCharacterService { + character_db_client + }; tonic::transport::Server::builder() .add_service(CharacterServiceServer::new(character_service)) diff --git a/database-service/src/grpc/character_service.rs b/database-service/src/grpc/character_service.rs index 8b4e154..bc6fa49 100644 --- a/database-service/src/grpc/character_service.rs +++ b/database-service/src/grpc/character_service.rs @@ -1,4 +1,4 @@ -use crate::grpc::{Character, CharacterRequest, CharacterListRequest, CharacterListResponse, CreateCharacterRequest, CreateCharacterResponse, DeleteCharacterRequest, Empty}; +use crate::grpc::{Character, CharacterRequest, CharacterListRequest, CharacterListResponse, CreateCharacterRequest, CreateCharacterResponse, DeleteCharacterRequest, DeleteCharacterResponse, Empty}; use crate::grpc::character_service_server::CharacterService; use crate::grpc::database_service::MyDatabaseService; use tonic::{Request, Response, Status}; @@ -101,6 +101,7 @@ impl CharacterService for MyDatabaseService { .map_err(|_| Status::not_found("Character not found"))?; let response = CreateCharacterResponse { + result: 0, character_id: character.id, }; @@ -110,7 +111,7 @@ impl CharacterService for MyDatabaseService { async fn delete_character( &self, request: Request, - ) -> Result, Status> { + ) -> Result, Status> { let req = request.into_inner(); let repo = &self.db.character_repo; @@ -118,6 +119,10 @@ impl CharacterService for MyDatabaseService { .await .map_err(|_| Status::internal("Failed to delete character"))?; - Ok(Response::new(Empty {})) + let response = DeleteCharacterResponse { + remaining_time: 0, + name: "".to_string(), + }; + Ok(Response::new(response)) } } diff --git a/packet-service/build.rs b/packet-service/build.rs index af2fd65..96ba673 100644 --- a/packet-service/build.rs +++ b/packet-service/build.rs @@ -4,6 +4,6 @@ fn main() { .build_server(false) // Generate gRPC server code? .compile_well_known_types(true) .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") - .compile_protos(&["../proto/auth.proto", "../proto/character.proto"], &["../proto"]) + .compile_protos(&["../proto/auth.proto", "../proto/character.proto", "../proto/character_common.proto"], &["../proto"]) .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); } diff --git a/packet-service/src/character_client.rs b/packet-service/src/character_client.rs index b68ad4d..cc86658 100644 --- a/packet-service/src/character_client.rs +++ b/packet-service/src/character_client.rs @@ -1,6 +1,7 @@ -use crate::char::character_service_client::CharacterServiceClient; -use crate::char::{GetCharacterRequest, GetCharacterListRequest, GetCharacterListResponse, Character, EquippedItem, DeleteCharacterRequest, Empty}; +use crate::character::character_service_client::CharacterServiceClient; +use crate::character::{CreateCharacterRequest, CreateCharacterResponse, DeleteCharacterRequest, DeleteCharacterResponse, Empty, GetCharacterListRequest, GetCharacterListResponse, GetCharacterRequest}; use tonic::transport::Channel; +use utils::null_string::NullTerminatedString; #[derive(Clone, Debug)] pub struct CharacterClient { @@ -22,16 +23,21 @@ impl CharacterClient { Ok(response.into_inner()) } - pub async fn create_character(&mut self, user_id: &str) -> Result> { - let request = GetCharacterListRequest { + pub async fn create_character(&mut self, user_id: &str, name: NullTerminatedString, race: u8, face: u8, hair: u8, stone: u8) -> Result> { + let request = CreateCharacterRequest { user_id: user_id.to_string(), + name: name.0, + race: race as i32, + face: face as i32, + hair: hair as i32, + stone: stone as i32, }; - let response = self.client.get_character_list(request).await?; + let response = self.client.create_character(request).await?; Ok(response.into_inner()) } - pub async fn delete_character(&mut self, user_id: &str, char_id: &str) -> Result> { + pub async fn delete_character(&mut self, user_id: &str, char_id: &str) -> Result> { let request = DeleteCharacterRequest { user_id: user_id.to_string(), char_id: char_id.to_string(), diff --git a/packet-service/src/handlers/character.rs b/packet-service/src/handlers/character.rs index 576c222..f21627d 100644 --- a/packet-service/src/handlers/character.rs +++ b/packet-service/src/handlers/character.rs @@ -56,13 +56,13 @@ pub(crate) async fn handle_char_list_req(stream: &mut TcpStream, packet: Packet, let character_info = CharInfo { name: NullTerminatedString(character.name), - race: character.race as u8, - level: character.level as u16, - job: character.job as u16, + race: character.looks.unwrap().race as u8, + level: character.stats.unwrap().level as u16, + job: character.stats.unwrap().job as u16, remain_secs_until_delete: character.delete_time as u32, platinium: 0, - face: character.face as u32, - hair: character.hair as u32, + face: character.looks.unwrap().face as u32, + hair: character.looks.unwrap().hair as u32, items: item_list, }; characters.push(character_info); @@ -89,9 +89,19 @@ pub(crate) async fn handle_create_char_req(stream: &mut TcpStream, packet: Packe // send the data to the character service to create the character let mut character_client = character_client.lock().await; - character_client.create_character(&user_id.to_string()).await?; - - let data = SrvCreateCharReply { result: srv_create_char_reply::Result::Ok, platininum: 0 }; + let create_character_response = character_client.create_character(&user_id.to_string(), request.name, request.race, request.face, request.hair, request.stone).await?; + let result = match create_character_response.result + { + 0 => srv_create_char_reply::Result::Ok, + 1 => srv_create_char_reply::Result::Failed, + 2 => srv_create_char_reply::Result::NameTaken, + 3 => srv_create_char_reply::Result::InvalidValue, + 4 => srv_create_char_reply::Result::TooManyChars, + 5 => srv_create_char_reply::Result::Blocked, + _ => srv_create_char_reply::Result::Failed + }; + + let data = SrvCreateCharReply { result, platininum: 0 }; let response_packet = Packet::new(PacketType::PakccCreateCharReply, &data)?; send_packet(stream, &response_packet).await?; @@ -110,10 +120,10 @@ pub(crate) async fn handle_delete_char_req(stream: &mut TcpStream, packet: Packe } let mut character_client = character_client.lock().await; - character_client.delete_character(&user_id.to_string(), &request.char_id.to_string()); + let delete_response = character_client.delete_character(&user_id.to_string(), &request.char_id.to_string()).await?; let character_name = request.name; - let data = SrvDeleteCharReply { remaining_time: 0, name: character_name }; + let data = SrvDeleteCharReply { remaining_time: delete_response.remaining_time as u32, name: character_name }; let response_packet = Packet::new(PacketType::PakccDeleteCharReply, &data)?; send_packet(stream, &response_packet).await?; @@ -133,7 +143,7 @@ pub(crate) async fn handle_select_char_req(stream: &mut TcpStream, packet: Packe } let mut character_client = character_client.lock().await; - character_client.get_character(&user_id.to_string(), request.char_id); + let character_data = character_client.get_character(&user_id.to_string(), request.char_id).await?; let mut equipped_item_list: [EquippedItem; (MAX_VISIBLE_ITEMS as usize)] = core::array::from_fn(|i| EquippedItem::default()); let mut effect_list: [StatusEffect; (MAX_STATUS_EFFECTS as usize)] = core::array::from_fn(|i| StatusEffect::default()); let mut hotbar_list: [HotbarItem; (MAX_HOTBAR_ITEMS as usize)] = core::array::from_fn(|i| HotbarItem::default()); diff --git a/packet-service/src/main.rs b/packet-service/src/main.rs index 785a201..b75c9c5 100644 --- a/packet-service/src/main.rs +++ b/packet-service/src/main.rs @@ -40,7 +40,10 @@ mod connection_service; pub mod auth { tonic::include_proto!("auth"); // Path matches the package name in auth.proto } -pub mod char { +pub mod character_common { + tonic::include_proto!("character_common"); // Path matches the package name in auth.proto +} +pub mod character { tonic::include_proto!("character"); // Path matches the package name in auth.proto } diff --git a/proto/character.proto b/proto/character.proto index 219049a..460bab2 100644 --- a/proto/character.proto +++ b/proto/character.proto @@ -2,10 +2,12 @@ syntax = "proto3"; package character; +import "character_common.proto"; + service CharacterService { rpc GetCharacterList(GetCharacterListRequest) returns (GetCharacterListResponse); - rpc CreateCharacter(CreateCharacterRequest) returns (Empty); - rpc DeleteCharacter(DeleteCharacterRequest) returns (Empty); + rpc CreateCharacter(CreateCharacterRequest) returns (CreateCharacterResponse); + rpc DeleteCharacter(DeleteCharacterRequest) returns (DeleteCharacterResponse); rpc GetCharacter(GetCharacterRequest) returns (Empty); } @@ -14,11 +16,20 @@ message GetCharacterListRequest { } message GetCharacterListResponse { - repeated Character characters = 1; + repeated character_common.Character characters = 1; } message CreateCharacterRequest { string user_id = 1; + string name = 2; + int32 race = 3; + int32 face = 4; + int32 hair = 5; + int32 stone = 6; +} + +message CreateCharacterResponse { + int32 result = 1; } message DeleteCharacterRequest { @@ -26,28 +37,13 @@ message DeleteCharacterRequest { string char_id = 2; } +message DeleteCharacterResponse { + int64 remaining_time = 1; + string name = 2; +} + message GetCharacterRequest { string user_id = 1; } -message Character { - string character_id = 1; // Unique ID for the character - string name = 2; // Name of the character - int32 level = 3; // Character's level - int32 race = 4; // Character's level - int32 job = 5; // Character's class or type - int64 last_played = 6; // Last played timestamp (Unix time) - int64 delete_time = 7; // Time until character deletion (seconds) - int32 face = 8; // Character's face - int32 hair = 9; // Character's hair - repeated EquippedItem items = 10; -} - -message EquippedItem { - int32 id = 1; - int32 gem_option = 2; - int32 socket = 3; - int32 grade = 4; -} - message Empty {} diff --git a/proto/character_common.proto b/proto/character_common.proto new file mode 100644 index 0000000..bc46058 --- /dev/null +++ b/proto/character_common.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package character_common; + +message Stats { + int32 level = 1; + int32 job = 2; +} + +message Looks { + int32 race = 1; + int32 hair = 2; + int32 face = 3; +} + +message EquippedItem { + int32 id = 1; + int32 gem_option = 2; + int32 socket = 3; + int32 grade = 4; +} + +message Character { + string character_id = 1; // Unique ID for the character + string name = 2; // Name of the character + int64 last_played = 3; // Last played timestamp (Unix time) + int64 delete_time = 4; // Time until character deletion (seconds) + Stats stats = 5; // Character's stats + Looks looks = 6; // Character's Looks + repeated EquippedItem items = 7; +} \ No newline at end of file diff --git a/proto/character_db_api.proto b/proto/character_db_api.proto index c35bd1e..ab4b822 100644 --- a/proto/character_db_api.proto +++ b/proto/character_db_api.proto @@ -6,7 +6,7 @@ service CharacterService { rpc GetCharacter (CharacterRequest) returns (Character); rpc GetCharacterList (CharacterListRequest) returns (CharacterListResponse); rpc CreateCharacter (CreateCharacterRequest) returns (CreateCharacterResponse); - rpc DeleteCharacter (DeleteCharacterRequest) returns (Empty); + rpc DeleteCharacter (DeleteCharacterRequest) returns (DeleteCharacterResponse); } message CharacterRequest { @@ -31,11 +31,18 @@ message CreateCharacterRequest { } message CreateCharacterResponse { - int32 character_id = 1; + int32 result = 1; + int32 character_id = 2; } message DeleteCharacterRequest { - int32 character_id = 1; + int32 user_id = 1; + int32 character_id = 2; +} + +message DeleteCharacterResponse { + int64 remaining_time = 1; + string name = 2; } message Character {