Files
osirose-new/packet-service/src/handlers/character.rs
raven ad6ba2c8e6 More work.
Added chat service
Updated packet service to pass the tcp stream around in a Arc type.
Updated character position data to not require multiplying the coords
Added more debug logs
Added an interceptor for gRPC comms with the chat server
Updated build and push script for the chat server changes
2025-06-06 17:52:29 -04:00

425 lines
16 KiB
Rust

use crate::auth_client::AuthClient;
use crate::character_client::CharacterClient;
use crate::connection_service::ConnectionService;
use crate::dataconsts::*;
use crate::enums;
use crate::enums::ItemType;
use crate::packet::{send_packet, Packet, PacketPayload};
use crate::packet_type::PacketType;
use crate::packets::*;
use std::collections::hash_map::DefaultHasher;
use std::error::Error;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tonic::{Code, Status};
use tracing::{debug, error, info, warn};
use utils::null_string::NullTerminatedString;
pub(crate) fn string_to_u32(s: &str) -> u32 {
let mut hasher = DefaultHasher::new();
s.hash(&mut hasher);
// Convert the 64-bit hash to a 32-bit number.
// Note: This cast might lead to collisions if there are a lot of objects,
hasher.finish() as u32
}
pub(crate) fn convert_slot_to_equip_slot(slot: i32) -> srv_char_list_reply::EquippedPosition {
match enums::EquippedPosition::from_i32(slot) {
Some(enums::EquippedPosition::Goggles) => srv_char_list_reply::EquippedPosition::Googles,
Some(enums::EquippedPosition::Helmet) => srv_char_list_reply::EquippedPosition::Helmet,
Some(enums::EquippedPosition::Armor) => srv_char_list_reply::EquippedPosition::Armor,
Some(enums::EquippedPosition::Backpack) => srv_char_list_reply::EquippedPosition::Backpack,
Some(enums::EquippedPosition::Gauntlet) => srv_char_list_reply::EquippedPosition::Gauntlet,
Some(enums::EquippedPosition::Boots) => srv_char_list_reply::EquippedPosition::Boots,
Some(enums::EquippedPosition::WeaponR) => srv_char_list_reply::EquippedPosition::WeaponR,
Some(enums::EquippedPosition::WeaponL) => srv_char_list_reply::EquippedPosition::WeaponL,
_ => srv_char_list_reply::EquippedPosition::MaxItems,
}
}
pub(crate) fn convert_slot_to_body_part(slot: i32) -> usize {
match enums::EquippedPosition::from_i32(slot) {
Some(enums::EquippedPosition::Goggles) => 6,
Some(enums::EquippedPosition::Helmet) => 2,
Some(enums::EquippedPosition::Armor) => 3,
Some(enums::EquippedPosition::Backpack) => 7,
Some(enums::EquippedPosition::Gauntlet) => 4,
Some(enums::EquippedPosition::Boots) => 5,
Some(enums::EquippedPosition::WeaponR) => 8,
Some(enums::EquippedPosition::WeaponL) => 9,
_ => 12,
}
}
pub(crate) fn convert_type_to_body_part(slot: i32) -> ItemType {
match enums::EquippedPosition::from_i32(slot) {
Some(enums::EquippedPosition::Goggles) => ItemType::ItemGoggles,
Some(enums::EquippedPosition::Helmet) => ItemType::ItemHelmet,
Some(enums::EquippedPosition::Armor) => ItemType::ItemArmor,
Some(enums::EquippedPosition::Backpack) => ItemType::ItemBackpack,
Some(enums::EquippedPosition::Gauntlet) => ItemType::ItemGauntlet,
Some(enums::EquippedPosition::Boots) => ItemType::ItemBoots,
Some(enums::EquippedPosition::WeaponR) => ItemType::ItemWeaponR,
Some(enums::EquippedPosition::WeaponL) => ItemType::ItemWeaponL,
_ => ItemType::None,
}
}
pub(crate) async fn handle_char_list_req(
stream: Arc<Mutex<TcpStream>>,
packet: Packet,
character_client: Arc<Mutex<CharacterClient>>,
connection_service: Arc<ConnectionService>,
connection_id: String,
) -> Result<(), Box<dyn Error + Send + Sync>> {
use crate::packets::cli_char_list_req::*;
use crate::packets::srv_char_list_reply::*;
let request = CliCharListReq::decode(packet.payload.as_slice());
debug!("{:?}", request);
let mut user_id = "".to_string();
let session_id;
if let Some(mut state) = connection_service.get_connection(&connection_id) {
user_id = state.user_id.expect("Missing user id in connection state");
session_id = state.session_id.expect("Missing session id in connection state");
}
// query the character service for the character list for this user
let mut character_client = character_client.lock().await;
let character_list = character_client.get_character_list(&user_id).await?;
let mut characters = vec![];
let mut character_id_list: Vec<u32> = Vec::new();
// Build the visible inventory
for character in character_list.characters {
let mut item_list: [EquippedItem; (MAX_VISIBLE_ITEMS as usize)] =
core::array::from_fn(|i| EquippedItem::default());
for item in character.items {
if item.slot < MAX_VISIBLE_ITEMS as i32 {
let slot = convert_slot_to_equip_slot(item.slot) as usize;
item_list[slot] = EquippedItem {
id: item.item_id as u16,
gem_opt: item.gem_option as u16,
socket: item.socket as i8,
grade: item.grade as u8,
};
}
}
let character_info = CharInfo {
name: NullTerminatedString(character.name),
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.looks.unwrap().face as u32,
hair: character.looks.unwrap().hair as u32,
items: item_list,
};
character_id_list.push(character.character_id.parse().unwrap());
characters.push(character_info);
}
if let Some(mut state) = connection_service.get_connection_mut(&connection_id) {
state.character_list = Some(character_id_list); // Save the real character id's for later as the client sends what is selected from 0 index. It does not use the real character idss
}
let data = SrvCharListReply { characters };
let response_packet = Packet::new(PacketType::PakccCharListReply, &data)?;
let mut locked_stream = stream.lock().await;
send_packet(&mut locked_stream, &response_packet).await?;
Ok(())
}
pub(crate) async fn handle_create_char_req(
stream: Arc<Mutex<TcpStream>>,
packet: Packet,
character_client: Arc<Mutex<CharacterClient>>,
connection_service: Arc<ConnectionService>,
connection_id: String,
) -> Result<(), Box<dyn Error + Send + Sync>> {
use crate::packets::cli_create_char_req::*;
use crate::packets::srv_create_char_reply::*;
let request = CliCreateCharReq::decode(packet.payload.as_slice())?;
debug!("{:?}", request);
let mut user_id = "".to_string();
let session_id;
if let Some(mut state) = connection_service.get_connection(&connection_id) {
user_id = state.user_id.expect("Missing user id in connection state");
session_id = state.session_id.expect("Missing session id in connection state");
}
// send the data to the character service to create the character
let mut character_client = character_client.lock().await;
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)?;
let mut locked_stream = stream.lock().await;
send_packet(&mut locked_stream, &response_packet).await?;
Ok(())
}
pub(crate) async fn handle_delete_char_req(
stream: Arc<Mutex<TcpStream>>,
packet: Packet,
character_client: Arc<Mutex<CharacterClient>>,
connection_service: Arc<ConnectionService>,
connection_id: String,
) -> Result<(), Box<dyn Error + Send + Sync>> {
use crate::packets::cli_delete_char_req::*;
use crate::packets::srv_delete_char_reply::*;
let request = CliDeleteCharReq::decode(packet.payload.as_slice())?;
debug!("{:?}", request);
let mut user_id = "".to_string();
let session_id;
let mut character_id_list: Vec<u32> = Vec::new();
if let Some(mut state) = connection_service.get_connection(&connection_id) {
user_id = state.user_id.expect("Missing user id in connection state");
session_id = state.session_id.expect("Missing session id in connection state");
character_id_list = state.character_list.expect("Missing character id list");
}
let mut character_client = character_client.lock().await;
let delete_response = character_client
.delete_character(
&user_id.to_string(),
&character_id_list[request.char_id as usize].to_string(),
request.is_delete as i32,
)
.await?;
let character_name = request.name;
let data = SrvDeleteCharReply {
remaining_time: delete_response.remaining_time as u32,
name: character_name,
};
let response_packet = Packet::new(PacketType::PakccDeleteCharReply, &data)?;
let mut locked_stream = stream.lock().await;
send_packet(&mut locked_stream, &response_packet).await?;
Ok(())
}
pub(crate) async fn handle_select_char_req(
stream: Arc<Mutex<TcpStream>>,
packet: Packet,
character_client: Arc<Mutex<CharacterClient>>,
connection_service: Arc<ConnectionService>,
connection_id: String,
) -> Result<(), Box<dyn Error + Send + Sync>> {
use crate::packets::cli_select_char_req::*;
use crate::packets::srv_billing_message::*;
use crate::packets::srv_inventory_data::*;
use crate::packets::srv_quest_data::*;
use crate::packets::srv_select_char_reply::*;
use crate::packets::srv_switch_server::*;
use crate::types::{HotbarItem, StatusEffect};
let request = CliSelectCharReq::decode(packet.payload.as_slice())?;
debug!("{:?}", request);
let mut user_id = "".to_string();
let mut character_id_list: Vec<u32> = Vec::new();
if let Some(mut state) = connection_service.get_connection_mut(&connection_id) {
user_id = state.user_id.clone().expect("Missing user id in connection state");
character_id_list = state.character_list.clone().expect("Missing character id list");
state.character_id = Some(request.char_id as i8);
}
let data = SrvSwitchServer {
port: 0,
session_id: 0,
session_seed: 0,
ip: NullTerminatedString("".to_string()),
};
let response_packet = Packet::new(PacketType::PakccSwitchServer, &data)?;
{
let mut locked_stream = stream.lock().await;
send_packet(&mut locked_stream, &response_packet).await?;
}
let mut character_client = character_client.lock().await;
let character_data = character_client
.get_character(&user_id.to_string(), character_id_list[request.char_id as usize] as u8)
.await?;
let character = character_data.character.unwrap_or_default();
let name = NullTerminatedString(character.name.clone());
let money = character.money;
let looks = character.looks.unwrap();
let position = character.position.unwrap();
let stats = character.stats.unwrap();
let skills = character.skills;
let items = character.items;
let mut equipped_item_list: [EquippedItem; (MAX_VISIBLE_ITEMS as usize)] =
core::array::from_fn(|i| EquippedItem::default());
let mut inventory: [srv_inventory_data::Item; (MAX_ITEMS as usize)] =
core::array::from_fn(|i| srv_inventory_data::Item::default());
// Build the character learned skill list
let mut skill_list: [u16; (MAX_SKILL_COUNT as usize)] = [0u16; MAX_SKILL_COUNT as usize];
for index in 0..skills.len() {
skill_list[index] = skills[index].id as u16;
}
// Build the character inventory list
for item in items {
if item.slot < MAX_VISIBLE_ITEMS as i32 {
let slot = convert_type_to_body_part(item.slot) as isize - 2;
if slot >= 0 {
equipped_item_list[slot as usize] = EquippedItem {
id: item.item_id as u16,
gem_opt: item.gem_option as u16,
socket: item.socket as i8,
grade: item.grade as u8,
};
}
}
inventory[item.slot as usize] = srv_inventory_data::Item {
header: srv_inventory_data::Header {
type_: item.item_type as u8,
id: item.item_id as u16,
is_created: item.is_created as u8,
},
data: srv_inventory_data::Data {
gem_opt: item.gem_option as u16,
life: item.life as u16,
durability: item.durability as u8,
has_socket: item.socket as u8,
is_appraised: item.is_appraised as u8,
refine: item.grade as u8,
count: item.count as u32,
},
};
}
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());
let data = SrvSelectCharReply {
race: looks.race as u8,
map: position.map_id as u16,
x: position.x,
y: position.y,
spawn: position.spawn_id as u16,
body_face: looks.face as u32,
body_hair: looks.hair as u32,
equipped_items: equipped_item_list,
stone: looks.stone as u8,
face: looks.face as u8,
hair: looks.hair as u8,
job: stats.job as u16,
faction_id: 0,
faction_rank: 0,
fame: 0,
str: stats.str as u16,
dex: stats.dex as u16,
int: stats.int as u16,
con: stats.con as u16,
charm: stats.charm as u16,
sense: stats.sense as u16,
hp: stats.hp,
mp: stats.mp,
xp: stats.xp as u32,
level: stats.level as u16,
stat_points: stats.stat_points as u32,
skill_points: stats.skill_points as u32,
body_size: stats.body_size as u8,
head_size: stats.head_size as u8,
penalty_xp: stats.penalty_xp as u32,
faction_fame: [0u16; 2],
faction_points: [0u16; 10],
guild_id: 0,
guild_contribution: 0,
guild_rank: 0,
pk_flag: 0,
stamina: stats.stamina as u16,
effects: effect_list,
pat_hp: stats.pat_hp as u16,
pat_cooldown_time: stats.pat_cooldown_time as u32,
skills: skill_list,
hotbar: hotbar_list,
tag: string_to_u32(&user_id),
name,
};
let response_packet = Packet::new(PacketType::PakwcSelectCharReply, &data)?;
{
let mut locked_stream = stream.lock().await;
send_packet(&mut locked_stream, &response_packet).await?;
}
// here we build the inventory
let data = SrvInventoryData {
zuly: money,
items: inventory,
};
let response_packet = Packet::new(PacketType::PakwcInventoryData, &data)?;
{
let mut locked_stream = stream.lock().await;
send_packet(&mut locked_stream, &response_packet).await?;
}
// Now we need to build the Quest data
let mut quests: [srv_quest_data::Quest; (MAX_QUESTS as usize)] =
core::array::from_fn(|i| srv_quest_data::Quest::default());
let mut wishlist: [srv_quest_data::Item; (MAX_WISHLIST as usize)] =
core::array::from_fn(|i| srv_quest_data::Item::default());
let data = SrvQuestData {
episodes: [0; (MAX_CONDITIONS_EPISODE as usize)],
jobs: [0; (MAX_CONDITIONS_JOB as usize)],
planets: [0; (MAX_CONDITIONS_PLANET as usize)],
unions: [0; (MAX_CONDITIONS_UNION as usize)],
quests,
switches: [0; (MAX_SWITCHES as usize)],
wishlist,
};
let response_packet = Packet::new(PacketType::PakwcQuestData, &data)?;
{
let mut locked_stream = stream.lock().await;
send_packet(&mut locked_stream, &response_packet).await?;
}
// Send the billing message (we don't use this, so we just send the defaults to allow)
let data = SrvBillingMessage {
function_type: 0x1001,
pay_flag: 2,
};
let response_packet = Packet::new(PacketType::PakwcBillingMessage, &data)?;
{
let mut locked_stream = stream.lock().await;
send_packet(&mut locked_stream, &response_packet).await?;
}
Ok(())
}