diff --git a/common/src/lib.rs b/common/src/lib.rs index afbdaed..f6afd88 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,2 +1,3 @@ pub mod resources; pub mod sr_tools; +pub mod structs; diff --git a/common/src/sr_tools.rs b/common/src/sr_tools.rs index 6bf2c96..e195bd9 100644 --- a/common/src/sr_tools.rs +++ b/common/src/sr_tools.rs @@ -1,528 +1,17 @@ -use proto::*; +use proto::{Avatar, AvatarSkillTree, MultiPathAvatarTypeInfo}; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; -// AVATAR -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AvatarJson { - // #[serde(alias = "ownerUid")] - // pub owner_uid: u32, - #[serde(alias = "avatarId")] - pub avatar_id: u32, - pub data: AvatarData, - pub level: u32, - pub promotion: u32, - #[serde(alias = "use_technique")] - #[serde(alias = "useTechnique")] - pub techniques: Vec, - #[serde(alias = "spValue")] - pub sp_value: Option, - #[serde(alias = "spMax")] - pub sp_max: Option, -} +use crate::structs::{ + // AllowedLanguages, + avatar::{AvatarJson, MultiPathAvatar}, + battle::BattleConfig, + lightcone::Lightcone, + persistent::Persistent, + relic::Relic, + scene::{Position, Scene}, +}; -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AvatarData { - pub rank: u32, - pub skills: HashMap, -} - -impl AvatarJson { - pub fn to_avatar_proto(&self, lightcone: Option<&Lightcone>, relics: Vec<&Relic>) -> Avatar { - // TODO: HARDCODED - let base_avatar_id = if self.avatar_id > 8000 { - 8001 - } else if self.avatar_id == 1001 || self.avatar_id == 1224 { - 1001 - } else { - self.avatar_id - }; - - Avatar { - base_avatar_id, - level: self.level, - promotion: self.promotion, - rank: self.data.rank, - skilltree_list: self - .data - .skills - .iter() - .map(|v| AvatarSkillTree { - point_id: *v.0, - level: *v.1, - }) - .collect::>(), - equipment_unique_id: if let Some(lc) = lightcone { - // TODO: HARDCODED LIGHTCONE ID - 2000 + lc.internal_uid - } else { - 0 - }, - first_met_timestamp: 1712924677, - equip_relic_list: relics - .iter() - .map(|v| v.to_equipment_relic_proto()) - .collect::>(), - ..Default::default() - } - } - - pub fn to_battle_avatar_proto( - &self, - index: u32, - lightcone: Option<&Lightcone>, - relics: Vec<&Relic>, - ) -> (BattleAvatar, Vec) { - let battle_avatar = BattleAvatar { - index, - avatar_type: AvatarType::AvatarUpgradeAvailableType.into(), - id: self.avatar_id, - level: self.level, - rank: self.data.rank, - skilltree_list: self - .data - .skills - .iter() - .map(|v| AvatarSkillTree { - point_id: *v.0, - level: *v.1, - }) - .collect::>(), - equipment_list: if let Some(lc) = lightcone { - vec![lc.to_battle_equipment_proto()] - } else { - vec![] - }, - hp: 10_000, - promotion: self.promotion, - relic_list: relics - .iter() - .map(|v| v.to_battle_relic_proto()) - .collect::>(), - world_level: 6, - sp_bar: Some(SpBarInfo { - cur_sp: self.sp_value.unwrap_or(10_000), - max_sp: self.sp_max.unwrap_or(10_000), - }), - ..Default::default() - }; - - let mut battle_buff = Vec::::new(); - for buff_id in &self.techniques { - battle_buff.push(BattleBuff { - wave_flag: 0xffffffff, - owner_id: index, - level: 1, - id: *buff_id, - dynamic_values: HashMap::from([(String::from("SkillIndex"), 2.0)]), - ..Default::default() - }); - } - - (battle_avatar, battle_buff) - } - - pub fn to_lineup_avatar_proto(&self, slot: u32) -> LineupAvatar { - LineupAvatar { - id: self.avatar_id, - hp: 10_000, - satiety: 100, - avatar_type: AvatarType::AvatarFormalType.into(), - sp_bar: Some(SpBarInfo { - cur_sp: self.sp_value.unwrap_or(10_000), - max_sp: self.sp_max.unwrap_or(10_000), - }), - slot, - } - } - - pub fn to_lineup_avatars(player: &FreesrData) -> Vec { - let avatar_ids = player - .avatars - .values() - .map(|v| &v.avatar_id) - .collect::>(); - - player - .lineups - .iter() - .filter(|(slot, v)| **slot < 4 && v > &&0 && avatar_ids.contains(v)) - .map(|(slot, avatar_id)| { - player - .avatars - .get(avatar_id) - .unwrap() - .to_lineup_avatar_proto(*slot) - }) - .collect::>() - } - - pub fn to_lineup_info(lineups: &BTreeMap) -> LineupInfo { - let mut lineup_info = LineupInfo { - extra_lineup_type: ExtraLineupType::LineupNone.into(), - name: "Squad 1".to_string(), - mp: 5, - max_mp: 5, - ..Default::default() - }; - - for id in lineups.values() { - if *id == 0 { - continue; - } - lineup_info.avatar_list.push(LineupAvatar { - id: *id, - hp: 10_000, - satiety: 100, - avatar_type: AvatarType::AvatarFormalType.into(), - sp_bar: Some(SpBarInfo { - cur_sp: 10_000, - max_sp: 10_000, - }), - slot: lineup_info.avatar_list.len() as u32, - }); - } - - lineup_info - } -} - -// LIGHTCONE -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Lightcone { - pub level: u32, - #[serde(alias = "itemId")] - pub item_id: u32, - #[serde(alias = "equipAvatar")] - pub equip_avatar: u32, - pub rank: u32, - pub promotion: u32, - #[serde(alias = "internalUid")] - pub internal_uid: u32, -} - -impl Lightcone { - pub fn to_equipment_proto(&self) -> Equipment { - Equipment { - equip_avatar_id: self.equip_avatar, - exp: 0, - is_protected: false, - level: self.level, - promotion: self.promotion, - rank: self.rank, - tid: self.item_id, - // ? - unique_id: 2000 + self.internal_uid, - } - } - - pub fn to_battle_equipment_proto(&self) -> BattleEquipment { - BattleEquipment { - id: self.item_id, - level: self.level, - promotion: self.promotion, - rank: self.rank, - } - } -} - -// RELIC -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Relic { - pub level: u32, - #[serde(alias = "relicId")] - pub relic_id: u32, - // #[serde(alias = "relicSetId")] - // pub relic_set_id: u32, - #[serde(alias = "mainAffixId")] - pub main_affix_id: u32, - #[serde(alias = "subAffixes")] - pub sub_affixes: Vec, - #[serde(alias = "internalUid")] - pub internal_uid: u32, - #[serde(alias = "equipAvatar")] - pub equip_avatar: u32, -} - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] -pub struct SubAffix { - #[serde(alias = "subAffixId")] - pub sub_affix_id: u32, - pub count: u32, - pub step: u32, -} - -impl Relic { - pub fn to_relic_proto(&self) -> proto::Relic { - proto::Relic { - equip_avatar_id: self.equip_avatar, - exp: 0, - is_protected: false, - level: self.level, - main_affix_id: self.main_affix_id, - tid: self.relic_id, - // ? - unique_id: 1 + self.internal_uid, - sub_affix_list: self - .sub_affixes - .iter() - .map(|v| RelicAffix { - affix_id: v.sub_affix_id, - cnt: v.count, - step: v.step, - }) - .collect::>(), - ..Default::default() - } - } - - pub fn to_battle_relic_proto(&self) -> BattleRelic { - BattleRelic { - id: self.relic_id, - level: self.level, - main_affix_id: self.main_affix_id, - unique_id: self.internal_uid, - sub_affix_list: self - .sub_affixes - .iter() - .map(|v| RelicAffix { - affix_id: v.sub_affix_id, - cnt: v.count, - step: v.step, - }) - .collect::>(), - ..Default::default() - } - } - - pub fn to_equipment_relic_proto(&self) -> EquipRelic { - EquipRelic { - slot: self.relic_id % 10, - // ? - relic_unique_id: 1 + self.internal_uid, - } - } -} - -// MONSTER -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -pub struct Monster { - pub level: u32, - #[serde(alias = "monsterId")] - pub monster_id: u32, - #[serde(default)] - pub max_hp: u32, -} - -impl Monster { - fn to_scene_monster_info(&self) -> SceneMonster { - SceneMonster { - monster_id: self.monster_id, - max_hp: self.max_hp, - cur_hp: self.max_hp, - } - } - - pub fn to_scene_monster_wave(wave_index: u32, monsters: &[Self]) -> SceneMonsterWave { - let mut wave_index = wave_index; - if wave_index < 1 { - wave_index += 1; - } - - SceneMonsterWave { - wave_id: wave_index, // wave indexx?? - - wave_param: Some(SceneMonsterWaveParam { - // monster param - level: monsters.iter().map(|v| v.level).max().unwrap_or(95), - ..Default::default() - }), - - monster_list: monsters - .iter() - .map(|v| v.to_scene_monster_info()) - .collect::>(), - ..Default::default() - } - } - - pub fn to_scene_monster_waves(monsters: &[Vec]) -> Vec { - monsters - .iter() - .enumerate() - .map(|(i, v)| Self::to_scene_monster_wave(i as u32, v)) - .collect::<_>() - } -} - -// BATTLE CONFIG -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct BattleConfig { - pub battle_type: BattleType, - pub monsters: Vec>, - pub blessings: Vec, - pub stage_id: u32, - pub cycle_count: u32, - pub path_resonance_id: u32, - pub custom_stats: Vec, - #[serde(default)] - pub scepters: Vec, -} - -impl Default for BattleConfig { - fn default() -> Self { - Self { - battle_type: Default::default(), - monsters: vec![vec![Monster { - level: 60, - monster_id: 3014022, - max_hp: 0, - }]], - stage_id: 201012311, - blessings: Default::default(), - cycle_count: Default::default(), - path_resonance_id: Default::default(), - custom_stats: Default::default(), - scepters: Default::default(), - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub enum BattleType { - #[serde(alias = "DEFAULT")] - Default = 0, - #[serde(alias = "MOC")] - Moc = 1, - PF = 2, - SU = 3, - AS = 4, -} - -impl Default for BattleType { - fn default() -> Self { - Self::Default - } -} - -// BATTLE BUFFS -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -pub struct BattleBuffJson { - pub level: u32, - pub id: u32, - pub dynamic_key: Option, - #[serde(default)] - pub dynamic_values: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -pub struct DynamicKey { - pub key: String, - pub value: u32, -} - -#[allow(dead_code)] -impl BattleBuffJson { - pub fn to_battle_buff_proto(&self) -> proto::BattleBuff { - proto::BattleBuff { - id: self.id, - level: self.level, - wave_flag: 0xffffffff, - owner_id: 0xffffffff, - dynamic_values: if let Some(dyn_key) = &self.dynamic_key { - HashMap::from([(dyn_key.key.clone(), dyn_key.value as f32)]) - } else { - Default::default() - }, - ..Default::default() - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)] -#[repr(u32)] -pub enum RogueMagicComponentType { - Passive = 3, - #[default] - Active = 4, - Attach = 5, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -pub struct RogueMagicScepter { - pub level: u32, - pub id: u32, - pub components: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -pub struct RogueMagicComponent { - pub id: u32, - pub level: u32, - pub component_type: RogueMagicComponentType, -} - -// SCENE -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Scene { - pub plane_id: u32, - pub floor_id: u32, - pub entry_id: u32, -} - -impl Default for Scene { - fn default() -> Self { - Self { - plane_id: 20411, - floor_id: 20411001, - entry_id: 2041101, - } - } -} - -// Position -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Position { - pub x: i32, - pub y: i32, - pub z: i32, - pub rot_y: i32, -} - -impl Default for Position { - fn default() -> Self { - Position { - x: -26968, - y: 78953, - z: 14457, - rot_y: 11858, - } - } -} - -impl Position { - #[allow(unused)] - pub fn is_empty(&self) -> bool { - self.x == 0 && self.y == 0 && self.z == 0 - } - - pub fn to_motion(&self) -> MotionInfo { - MotionInfo { - rot: Some(Vector { - x: 0, - y: self.rot_y, - z: 0, - }), - pos: Some(Vector { - x: self.x, - y: self.y, - z: self.z, - }), - } - } -} - -// FREESR-DATA.json #[derive(Debug, Serialize, Deserialize)] pub struct FreesrData { pub lightcones: Vec, @@ -531,147 +20,138 @@ pub struct FreesrData { #[serde(default)] pub battle_config: BattleConfig, - #[serde(default, skip_serializing)] + // Non freesr-data.json fields + #[serde(skip_serializing, skip_deserializing)] pub lineups: BTreeMap, - #[serde(default, skip_serializing)] + #[serde(skip_serializing, skip_deserializing)] pub position: Position, - #[serde(default, skip_serializing)] + #[serde(skip_serializing, skip_deserializing)] pub scene: Scene, - #[serde(skip_serializing, skip_deserializing)] pub main_character: MultiPathAvatar, #[serde(skip_serializing, skip_deserializing)] pub march_type: MultiPathAvatar, + // #[serde(skip_serializing, skip_deserializing)] + // pub game_language: AllowedLanguages, + // #[serde(skip_serializing, skip_deserializing)] + // pub voice_langauge: AllowedLanguages, } -#[derive(Debug, Serialize, Deserialize)] -pub struct Persistent { - #[serde(default)] - pub lineups: BTreeMap, - #[serde(default)] - pub position: Position, - #[serde(default)] - pub scene: Scene, - pub main_character: MultiPathAvatar, - pub march_type: MultiPathAvatar, -} +impl FreesrData { + pub fn get_avatar_proto(&self, avatar_id: u32) -> Option { + let avatar = self.avatars.get(&avatar_id)?; + let lightcone = self.lightcones.iter().find(|l| l.equip_avatar == avatar_id); + let relics = self.relics.iter().filter(|r| r.equip_avatar == avatar_id); -impl Default for Persistent { - fn default() -> Self { - Self { - lineups: BTreeMap::from([(0, 1313), (1, 1006), (2, 8001), (3, 1405)]), - position: Default::default(), - main_character: MultiPathAvatar::FemaleRememberance, - scene: Default::default(), - march_type: MultiPathAvatar::MarchHunt, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq, Default)] -#[repr(u32)] -pub enum MultiPathAvatar { - MalePyhsical = 8001, - FemalePhysical = 8002, - MalePreservation = 8003, - FemalePreservation = 8004, - MaleHarmony = 8005, - FemaleHarmony = 8006, - MaleRememberance = 8007, - FemaleRememberance = 8008, - MarchHunt = 1224, - MarchPreservation = 1001, - #[default] - Unk = 0, -} - -impl From for MultiPathAvatar { - fn from(value: u32) -> Self { - match value { - 8001 => Self::MalePyhsical, - 8002 => Self::FemalePhysical, - 8003 => Self::MalePreservation, - 8004 => Self::FemalePreservation, - 8005 => Self::MaleHarmony, - 8006 => Self::FemaleHarmony, - 8007 => Self::MaleRememberance, - 8008 => Self::FemaleRememberance, - 1224 => Self::MarchHunt, - 1001 => Self::MarchPreservation, - _ => Self::Unk, - } - } -} - -impl From for u32 { - fn from(value: MultiPathAvatar) -> Self { - match value { - MultiPathAvatar::MalePyhsical => 8001, - MultiPathAvatar::FemalePhysical => 8002, - MultiPathAvatar::MalePreservation => 8003, - MultiPathAvatar::FemalePreservation => 8004, - MultiPathAvatar::MaleHarmony => 8005, - MultiPathAvatar::FemaleHarmony => 8006, - MultiPathAvatar::MaleRememberance => 8007, - MultiPathAvatar::FemaleRememberance => 8008, - MultiPathAvatar::MarchHunt => 1224, - MultiPathAvatar::MarchPreservation => 1001, - _ => 8006, - } - } -} - -impl MultiPathAvatar { - #[allow(unused)] - pub fn get_gender(&self) -> Gender { - if (*self as u32) < 8000 { - Gender::None - } else if *self as u32 % 2 == 1 { - Gender::Man + // TODO: HARDCODED + let base_avatar_id = if avatar.avatar_id > 8000 { + 8001 + } else if avatar.avatar_id == 1001 || avatar.avatar_id == 1224 { + 1001 } else { - Gender::Woman + avatar.avatar_id + }; + + Some(Avatar { + base_avatar_id, + level: avatar.level, + promotion: avatar.promotion, + rank: avatar.data.rank, + skilltree_list: avatar + .data + .skills + .iter() + .map(|v| AvatarSkillTree { + point_id: *v.0, + level: *v.1, + }) + .collect::>(), + equipment_unique_id: lightcone.map(|v| v.get_unique_id()).unwrap_or_default(), + first_met_timestamp: 1712924677, + equip_relic_list: relics.map(|v| v.into()).collect::>(), + ..Default::default() + }) + } + + pub fn get_avatar_multipath_proto(&self, avatar_id: u32) -> Option { + let avatar = self.avatars.get(&avatar_id)?; + let mp_type = MultiPathAvatar::from(avatar_id); + + if mp_type == MultiPathAvatar::Unk { + return None; } + + Some(MultiPathAvatarTypeInfo { + avatar_id: mp_type as i32, + rank: avatar.data.rank, + equip_relic_list: self + .relics + .iter() + .filter(|relic| relic.equip_avatar == mp_type as u32) + .map(|relic| relic.into()) + .collect(), + skilltree_list: avatar + .data + .skills + .iter() + .map(|(point_id, level)| AvatarSkillTree { + point_id: *point_id, + level: *level, + }) + .collect(), + path_equipment_id: self + .lightcones + .iter() + .find(|v| v.equip_avatar == mp_type as u32) + .map(|v| v.get_unique_id()) + .unwrap_or_default(), + dressed_skin_id: 0, + }) } - pub fn get_type(&self) -> MultiPathAvatarType { - match *self { - MultiPathAvatar::MalePyhsical => MultiPathAvatarType::BoyWarriorType, - MultiPathAvatar::FemalePhysical => MultiPathAvatarType::GirlWarriorType, - MultiPathAvatar::MalePreservation => MultiPathAvatarType::BoyKnightType, - MultiPathAvatar::FemalePreservation => MultiPathAvatarType::GirlKnightType, - MultiPathAvatar::MaleHarmony => MultiPathAvatarType::BoyShamanType, - MultiPathAvatar::FemaleHarmony => MultiPathAvatarType::GirlShamanType, - MultiPathAvatar::MarchHunt => MultiPathAvatarType::Mar7thRogueType, - MultiPathAvatar::MarchPreservation => MultiPathAvatarType::Mar7thKnightType, - MultiPathAvatar::Unk => MultiPathAvatarType::None, - MultiPathAvatar::MaleRememberance => MultiPathAvatarType::BoyMemoryType, - MultiPathAvatar::FemaleRememberance => MultiPathAvatarType::GirlMemoryType, - } - } + pub fn get_multi_path_info(&self) -> Vec { + MultiPathAvatar::to_vec() + .into_iter() + .filter_map(|mp_type| { + if mp_type.is_mc() && mp_type.get_gender() != self.main_character.get_gender() { + return Option::None; + } - pub fn is_mc(&self) -> bool { - (*self as u32) > 8000 - } - - pub fn to_vec() -> Vec { - vec![ - Self::MalePyhsical, - Self::FemalePhysical, - Self::MalePreservation, - Self::FemalePreservation, - Self::MaleHarmony, - Self::FemaleHarmony, - Self::MaleRememberance, - Self::FemaleRememberance, - Self::MarchHunt, - Self::MarchPreservation, - ] + let avatar_info = self.avatars.get(&((mp_type) as u32))?; + Some(MultiPathAvatarTypeInfo { + avatar_id: mp_type as i32, + rank: avatar_info.data.rank, + equip_relic_list: self + .relics + .iter() + .filter(|relic| relic.equip_avatar == mp_type as u32) + .map(|relic| relic.into()) + .collect(), + skilltree_list: avatar_info + .data + .skills + .iter() + .map(|(point_id, level)| AvatarSkillTree { + point_id: *point_id, + level: *level, + }) + .collect(), + path_equipment_id: self + .lightcones + .iter() + .find(|v| v.equip_avatar == mp_type as u32) + .map(|v| v.get_unique_id()) + .unwrap_or_default(), + dressed_skin_id: 0, + }) + }) + .collect() } } impl FreesrData { pub async fn load() -> Self { - let mut json: FreesrData = tokio::fs::read_to_string("freesr-data.json") + let mut freesr_data: FreesrData = tokio::fs::read_to_string("freesr-data.json") .await .map(|v| { serde_json::from_str::(&v) @@ -679,26 +159,38 @@ impl FreesrData { }) .expect("failed to read freesr-data.json"); - let json2: Persistent = serde_json::from_str( + let persistent: Persistent = serde_json::from_str( &tokio::fs::read_to_string("persistent") .await .unwrap_or_default(), ) .unwrap_or_default(); - json.lineups = json2.lineups; - json.position = json2.position; - json.scene = json2.scene; - json.main_character = json2.main_character; - json.march_type = json2.march_type; + freesr_data.lineups = persistent.lineups; + freesr_data.position = persistent.position; + freesr_data.scene = persistent.scene; + freesr_data.main_character = persistent.main_character; + freesr_data.march_type = persistent.march_type; + // freesr_data.game_language = persistent.game_language; + // freesr_data.voice_langauge = persistent.voice_language; - json.verify_lineup().await; - - if json.march_type as u32 > 8000 { - json.march_type = MultiPathAvatar::MarchHunt; + // remove unequipped lightcones + if freesr_data.lightcones.len() > 1500 { + freesr_data.lightcones.retain(|v| v.equip_avatar != 0); } - json + // remove unequipped relics + if freesr_data.relics.len() > 2000 { + freesr_data.relics.retain(|v| v.equip_avatar != 0); + } + + freesr_data.verify_lineup().await; + + if freesr_data.march_type as u32 > 8000 { + freesr_data.march_type = MultiPathAvatar::MarchHunt; + } + + freesr_data } pub async fn update(&mut self) { @@ -725,52 +217,12 @@ impl FreesrData { position: self.position.clone(), scene: self.scene.clone(), march_type: self.march_type, + // game_language: self.game_language, + // voice_language: self.voice_langauge, }) .unwrap(), ) .await; tracing::info!("persistent saved"); } - - pub fn get_multi_path_info(&self) -> Vec { - MultiPathAvatar::to_vec() - .iter() - .filter_map(|mp_type| { - if mp_type.is_mc() && mp_type.get_gender() != self.main_character.get_gender() { - return Option::None; - } - - let avatar_info = self.avatars.get(&((*mp_type) as u32))?; - Some(MultiPathAvatarTypeInfo { - avatar_id: *mp_type as i32, - rank: avatar_info.data.rank, - equip_relic_list: self - .relics - .iter() - .filter(|relic| relic.equip_avatar == *mp_type as u32) - .map(|relic| relic.to_equipment_relic_proto()) - .collect(), - skilltree_list: avatar_info - .data - .skills - .iter() - .map(|(point_id, level)| AvatarSkillTree { - point_id: *point_id, - level: *level, - }) - .collect(), - path_equipment_id: self - .lightcones - .iter() - .find(|v| v.equip_avatar == *mp_type as u32) - .map(|v| { - // TODO: HARDCODED LIGHTCONE ID - 2000 + v.internal_uid - }) - .unwrap_or_default(), - dressed_skin_id: 0, - }) - }) - .collect() - } } diff --git a/common/src/structs/avatar.rs b/common/src/structs/avatar.rs new file mode 100644 index 0000000..9e7c173 --- /dev/null +++ b/common/src/structs/avatar.rs @@ -0,0 +1,286 @@ +use std::collections::{BTreeMap, HashMap}; + +use proto::{ + Avatar, AvatarSkillTree, AvatarType, BattleAvatar, BattleBuff, ExtraLineupType, Gender, + LineupAvatar, LineupInfo, MultiPathAvatarType, SpBarInfo, +}; +use serde::{Deserialize, Serialize}; + +use crate::sr_tools::FreesrData; + +use super::{lightcone::Lightcone, relic::Relic}; + +#[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq, Default)] +#[repr(u32)] +pub enum MultiPathAvatar { + MalePyhsical = 8001, + FemalePhysical = 8002, + MalePreservation = 8003, + FemalePreservation = 8004, + MaleHarmony = 8005, + FemaleHarmony = 8006, + MaleRememberance = 8007, + FemaleRememberance = 8008, + MarchHunt = 1224, + MarchPreservation = 1001, + #[default] + Unk = 0, +} + +impl From for MultiPathAvatar { + fn from(value: u32) -> Self { + match value { + 8001 => Self::MalePyhsical, + 8002 => Self::FemalePhysical, + 8003 => Self::MalePreservation, + 8004 => Self::FemalePreservation, + 8005 => Self::MaleHarmony, + 8006 => Self::FemaleHarmony, + 8007 => Self::MaleRememberance, + 8008 => Self::FemaleRememberance, + 1224 => Self::MarchHunt, + 1001 => Self::MarchPreservation, + _ => Self::Unk, + } + } +} + +impl From for u32 { + fn from(value: MultiPathAvatar) -> Self { + match value { + MultiPathAvatar::MalePyhsical => 8001, + MultiPathAvatar::FemalePhysical => 8002, + MultiPathAvatar::MalePreservation => 8003, + MultiPathAvatar::FemalePreservation => 8004, + MultiPathAvatar::MaleHarmony => 8005, + MultiPathAvatar::FemaleHarmony => 8006, + MultiPathAvatar::MaleRememberance => 8007, + MultiPathAvatar::FemaleRememberance => 8008, + MultiPathAvatar::MarchHunt => 1224, + MultiPathAvatar::MarchPreservation => 1001, + _ => 8006, + } + } +} + +impl MultiPathAvatar { + #[allow(unused)] + pub fn get_gender(&self) -> Gender { + if (*self as u32) < 8000 { + Gender::None + } else if *self as u32 % 2 == 1 { + Gender::Man + } else { + Gender::Woman + } + } + + pub fn get_type(&self) -> MultiPathAvatarType { + match *self { + MultiPathAvatar::MalePyhsical => MultiPathAvatarType::BoyWarriorType, + MultiPathAvatar::FemalePhysical => MultiPathAvatarType::GirlWarriorType, + MultiPathAvatar::MalePreservation => MultiPathAvatarType::BoyKnightType, + MultiPathAvatar::FemalePreservation => MultiPathAvatarType::GirlKnightType, + MultiPathAvatar::MaleHarmony => MultiPathAvatarType::BoyShamanType, + MultiPathAvatar::FemaleHarmony => MultiPathAvatarType::GirlShamanType, + MultiPathAvatar::MarchHunt => MultiPathAvatarType::Mar7thRogueType, + MultiPathAvatar::MarchPreservation => MultiPathAvatarType::Mar7thKnightType, + MultiPathAvatar::Unk => MultiPathAvatarType::None, + MultiPathAvatar::MaleRememberance => MultiPathAvatarType::BoyMemoryType, + MultiPathAvatar::FemaleRememberance => MultiPathAvatarType::GirlMemoryType, + } + } + + pub fn is_mc(&self) -> bool { + (*self as u32) > 8000 + } + + pub fn to_vec() -> Vec { + vec![ + Self::MalePyhsical, + Self::FemalePhysical, + Self::MalePreservation, + Self::FemalePreservation, + Self::MaleHarmony, + Self::FemaleHarmony, + Self::MaleRememberance, + Self::FemaleRememberance, + Self::MarchHunt, + Self::MarchPreservation, + ] + } +} + +// AVATAR +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AvatarJson { + // #[serde(alias = "ownerUid")] + // pub owner_uid: u32, + #[serde(alias = "avatarId")] + pub avatar_id: u32, + pub data: AvatarData, + pub level: u32, + pub promotion: u32, + #[serde(alias = "use_technique")] + #[serde(alias = "useTechnique")] + pub techniques: Vec, + #[serde(alias = "spValue")] + pub sp_value: Option, + #[serde(alias = "spMax")] + pub sp_max: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AvatarData { + pub rank: u32, + pub skills: HashMap, +} + +impl AvatarJson { + pub fn to_avatar_proto(&self, lightcone: Option<&Lightcone>, relics: Vec<&Relic>) -> Avatar { + // TODO: HARDCODED + let base_avatar_id = if self.avatar_id > 8000 { + 8001 + } else if self.avatar_id == 1001 || self.avatar_id == 1224 { + 1001 + } else { + self.avatar_id + }; + + Avatar { + base_avatar_id, + level: self.level, + promotion: self.promotion, + rank: self.data.rank, + skilltree_list: self + .data + .skills + .iter() + .map(|v| AvatarSkillTree { + point_id: *v.0, + level: *v.1, + }) + .collect::>(), + equipment_unique_id: lightcone.map(|v| v.get_unique_id()).unwrap_or_default(), + first_met_timestamp: 1712924677, + equip_relic_list: relics.iter().map(|v| (*v).into()).collect::>(), + ..Default::default() + } + } + + pub fn to_battle_avatar_proto( + &self, + index: u32, + lightcone: Option<&Lightcone>, + relics: Vec<&Relic>, + ) -> (BattleAvatar, Vec) { + let battle_avatar = BattleAvatar { + index, + avatar_type: AvatarType::AvatarUpgradeAvailableType.into(), + id: self.avatar_id, + level: self.level, + rank: self.data.rank, + skilltree_list: self + .data + .skills + .iter() + .map(|v| AvatarSkillTree { + point_id: *v.0, + level: *v.1, + }) + .collect::>(), + equipment_list: if let Some(lc) = lightcone { + vec![lc.into()] + } else { + vec![] + }, + hp: 10_000, + promotion: self.promotion, + relic_list: relics.iter().map(|v| (*v).into()).collect::>(), + world_level: 6, + sp_bar: Some(SpBarInfo { + cur_sp: self.sp_value.unwrap_or(10_000), + max_sp: self.sp_max.unwrap_or(10_000), + }), + ..Default::default() + }; + + let mut battle_buff = Vec::::new(); + for buff_id in &self.techniques { + battle_buff.push(BattleBuff { + wave_flag: 0xffffffff, + owner_id: index, + level: 1, + id: *buff_id, + dynamic_values: HashMap::from([(String::from("SkillIndex"), 2.0)]), + ..Default::default() + }); + } + + (battle_avatar, battle_buff) + } + + pub fn to_lineup_avatar_proto(&self, slot: u32) -> LineupAvatar { + LineupAvatar { + id: self.avatar_id, + hp: 10_000, + satiety: 100, + avatar_type: AvatarType::AvatarFormalType.into(), + sp_bar: Some(SpBarInfo { + cur_sp: self.sp_value.unwrap_or(10_000), + max_sp: self.sp_max.unwrap_or(10_000), + }), + slot, + } + } + + pub fn to_lineup_avatars(player: &FreesrData) -> Vec { + let avatar_ids = player + .avatars + .values() + .map(|v| &v.avatar_id) + .collect::>(); + + player + .lineups + .iter() + .filter(|(slot, v)| **slot < 4 && v > &&0 && avatar_ids.contains(v)) + .map(|(slot, avatar_id)| { + player + .avatars + .get(avatar_id) + .unwrap() + .to_lineup_avatar_proto(*slot) + }) + .collect::>() + } + + pub fn to_lineup_info(lineups: &BTreeMap) -> LineupInfo { + let mut lineup_info = LineupInfo { + extra_lineup_type: ExtraLineupType::LineupNone.into(), + name: "Squad 1".to_string(), + mp: 5, + max_mp: 5, + ..Default::default() + }; + + for id in lineups.values() { + if *id == 0 { + continue; + } + lineup_info.avatar_list.push(LineupAvatar { + id: *id, + hp: 10_000, + satiety: 100, + avatar_type: AvatarType::AvatarFormalType.into(), + sp_bar: Some(SpBarInfo { + cur_sp: 10_000, + max_sp: 10_000, + }), + slot: lineup_info.avatar_list.len() as u32, + }); + } + + lineup_info + } +} diff --git a/common/src/structs/battle.rs b/common/src/structs/battle.rs new file mode 100644 index 0000000..f014e66 --- /dev/null +++ b/common/src/structs/battle.rs @@ -0,0 +1,112 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use super::{monster::Monster, relic::SubAffix}; + +// BATTLE CONFIG +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BattleConfig { + pub battle_type: BattleType, + pub monsters: Vec>, + pub blessings: Vec, + pub stage_id: u32, + pub cycle_count: u32, + pub path_resonance_id: u32, + pub custom_stats: Vec, + #[serde(default)] + pub scepters: Vec, +} + +impl Default for BattleConfig { + fn default() -> Self { + Self { + battle_type: Default::default(), + monsters: vec![vec![Monster { + level: 60, + monster_id: 3014022, + max_hp: 0, + }]], + stage_id: 201012311, + blessings: Default::default(), + cycle_count: Default::default(), + path_resonance_id: Default::default(), + custom_stats: Default::default(), + scepters: Default::default(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum BattleType { + #[serde(alias = "DEFAULT")] + Default = 0, + #[serde(alias = "MOC")] + Moc = 1, + PF = 2, + SU = 3, + AS = 4, +} + +impl Default for BattleType { + fn default() -> Self { + Self::Default + } +} + +// BATTLE BUFFS +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct BattleBuffJson { + pub level: u32, + pub id: u32, + pub dynamic_key: Option, + #[serde(default)] + pub dynamic_values: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct DynamicKey { + pub key: String, + pub value: u32, +} + +#[allow(dead_code)] +impl BattleBuffJson { + pub fn to_battle_buff_proto(&self) -> proto::BattleBuff { + proto::BattleBuff { + id: self.id, + level: self.level, + wave_flag: 0xffffffff, + owner_id: 0xffffffff, + dynamic_values: if let Some(dyn_key) = &self.dynamic_key { + HashMap::from([(dyn_key.key.clone(), dyn_key.value as f32)]) + } else { + Default::default() + }, + ..Default::default() + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)] +#[repr(u32)] +pub enum RogueMagicComponentType { + Passive = 3, + #[default] + Active = 4, + Attach = 5, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct RogueMagicScepter { + pub level: u32, + pub id: u32, + pub components: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct RogueMagicComponent { + pub id: u32, + pub level: u32, + pub component_type: RogueMagicComponentType, +} diff --git a/common/src/structs/lightcone.rs b/common/src/structs/lightcone.rs new file mode 100644 index 0000000..8f914ea --- /dev/null +++ b/common/src/structs/lightcone.rs @@ -0,0 +1,45 @@ +use proto::{BattleEquipment, Equipment, ItemType}; +use serde::{Deserialize, Serialize}; + +use crate::impl_from; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Lightcone { + pub level: u32, + #[serde(alias = "itemId")] + pub item_id: u32, + #[serde(alias = "equipAvatar")] + pub equip_avatar: u32, + pub rank: u32, + pub promotion: u32, + #[serde(alias = "internalUid")] + pub internal_uid: u32, +} + +impl_from!(Lightcone, Equipment, |value| { + Equipment { + equip_avatar_id: value.equip_avatar, + exp: 0, + is_protected: false, + level: value.level, + promotion: value.promotion, + rank: value.rank, + tid: value.item_id, + unique_id: value.get_unique_id(), + } +}); + +impl_from!(Lightcone, BattleEquipment, |value| { + BattleEquipment { + id: value.item_id, + level: value.level, + promotion: value.promotion, + rank: value.rank, + } +}); + +impl Lightcone { + pub fn get_unique_id(&self) -> u32 { + super::get_item_unique_id(self.internal_uid, ItemType::ItemEquipment) + } +} diff --git a/common/src/structs/mod.rs b/common/src/structs/mod.rs new file mode 100644 index 0000000..d0b0c50 --- /dev/null +++ b/common/src/structs/mod.rs @@ -0,0 +1,47 @@ +use proto::ItemType; + +pub mod avatar; +pub mod battle; +pub mod lightcone; +pub mod monster; +pub mod persistent; +pub mod relic; +pub mod scene; + +pub use avatar::*; +pub use battle::*; +pub use lightcone::*; +pub use monster::*; +pub use persistent::*; +pub use scene::*; + +pub fn get_item_unique_id(internal_uid: u32, item_type: ItemType) -> u32 { + if item_type == ItemType::ItemEquipment { + 2000 + internal_uid // 2000.3500 + } else { + 1 + internal_uid // 1..2000 + } +} + +#[macro_export] +macro_rules! impl_from { + ($from:ty, $to:ty, |$param:ident| $body:block) => { + impl From<$from> for $to { + fn from($param: $from) -> Self { + $body + } + } + + impl From<&$from> for $to { + fn from($param: &$from) -> Self { + $body + } + } + + impl From<&mut $from> for $to { + fn from($param: &mut $from) -> Self { + $body + } + } + }; +} diff --git a/common/src/structs/monster.rs b/common/src/structs/monster.rs new file mode 100644 index 0000000..7bd8377 --- /dev/null +++ b/common/src/structs/monster.rs @@ -0,0 +1,48 @@ +use proto::{SceneMonster, SceneMonsterWave, SceneMonsterWaveParam}; +use serde::{Deserialize, Serialize}; + +use crate::impl_from; + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct Monster { + pub level: u32, + #[serde(alias = "monsterId")] + pub monster_id: u32, + #[serde(default)] + pub max_hp: u32, +} + +impl_from!(Monster, SceneMonster, |value| { + SceneMonster { + monster_id: value.monster_id, + max_hp: value.max_hp, + cur_hp: value.max_hp, + } +}); + +impl Monster { + pub fn to_scene_monster_wave(mut wave_id: u32, monsters: &[Self]) -> SceneMonsterWave { + if wave_id < 1 { + wave_id += 1; + } + + SceneMonsterWave { + wave_id, + wave_param: Some(SceneMonsterWaveParam { + level: monsters.iter().map(|v| v.level).max().unwrap_or(95), + ..Default::default() + }), + + monster_list: monsters.iter().map(|v| v.into()).collect(), + ..Default::default() + } + } + + pub fn to_scene_monster_waves(monsters: &[Vec]) -> Vec { + monsters + .iter() + .enumerate() + .map(|(i, v)| Self::to_scene_monster_wave(i as u32, v)) + .collect::<_>() + } +} diff --git a/common/src/structs/persistent.rs b/common/src/structs/persistent.rs new file mode 100644 index 0000000..fde8c97 --- /dev/null +++ b/common/src/structs/persistent.rs @@ -0,0 +1,60 @@ +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +use super::{ + avatar::MultiPathAvatar, + scene::{Position, Scene}, +}; + +// #[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq, Default)] +// #[repr(u32)] +// pub enum AllowedLanguages { +// #[default] +// En, +// Jp, +// Kr, +// Cn, +// } + +// impl FromStr for AllowedLanguages { +// type Err = (); + +// fn from_str(input: &str) -> Result { +// match input { +// "en" => Ok(AllowedLanguages::En), +// "kr" => Ok(AllowedLanguages::Kr), +// "jp" => Ok(AllowedLanguages::Jp), +// "cn" => Ok(AllowedLanguages::Cn), +// _ => Ok(AllowedLanguages::En), +// } +// } +// } + +#[derive(Debug, Serialize, Deserialize)] +pub struct Persistent { + #[serde(default)] + pub lineups: BTreeMap, + #[serde(default)] + pub position: Position, + #[serde(default)] + pub scene: Scene, + pub main_character: MultiPathAvatar, + pub march_type: MultiPathAvatar, + // pub game_language: AllowedLanguages, + // pub voice_language: AllowedLanguages, +} + +impl Default for Persistent { + fn default() -> Self { + Self { + lineups: BTreeMap::from([(0, 1313), (1, 1006), (2, 8001), (3, 1405)]), + position: Default::default(), + main_character: MultiPathAvatar::FemaleRememberance, + scene: Default::default(), + march_type: MultiPathAvatar::MarchHunt, + // game_language: AllowedLanguages::En, + // voice_language: AllowedLanguages::Jp, + } + } +} diff --git a/common/src/structs/relic.rs b/common/src/structs/relic.rs new file mode 100644 index 0000000..ea9710f --- /dev/null +++ b/common/src/structs/relic.rs @@ -0,0 +1,95 @@ +use proto::{BattleRelic, EquipRelic, ItemType, RelicAffix}; +use serde::{Deserialize, Serialize}; + +use crate::impl_from; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Relic { + pub level: u32, + #[serde(alias = "relicId")] + pub relic_id: u32, + // #[serde(alias = "relicSetId")] + // pub relic_set_id: u32, + #[serde(alias = "mainAffixId")] + pub main_affix_id: u32, + #[serde(alias = "subAffixes")] + pub sub_affixes: Vec, + #[serde(alias = "internalUid")] + pub internal_uid: u32, + #[serde(alias = "equipAvatar")] + pub equip_avatar: u32, +} + +#[derive(Debug, Serialize, Deserialize, Default, Clone)] +pub struct SubAffix { + #[serde(alias = "subAffixId")] + pub sub_affix_id: u32, + pub count: u32, + pub step: u32, +} + +impl Relic { + pub fn is_matching_slot(&self, slot: u32) -> bool { + self.get_slot() == slot + } + + pub fn get_slot(&self) -> u32 { + self.relic_id % 10 + } + + pub fn get_unique_id(&self) -> u32 { + super::get_item_unique_id(self.internal_uid, ItemType::ItemRelic) + } +} + +impl_from!(SubAffix, RelicAffix, |v| { + RelicAffix { + affix_id: v.sub_affix_id, + cnt: v.count, + step: v.step, + } +}); + +impl_from!(Relic, BattleRelic, |value| { + BattleRelic { + id: value.relic_id, + level: value.level, + main_affix_id: value.main_affix_id, + unique_id: value.get_unique_id(), + sub_affix_list: value + .sub_affixes + .iter() + .map(|v| v.into()) + .collect::>(), + ..Default::default() + } +}); + +impl_from!(Relic, EquipRelic, |value| { + EquipRelic { + slot: value.get_slot(), + relic_unique_id: value.get_unique_id(), + } +}); + +impl_from!(Relic, proto::Relic, |value| { + proto::Relic { + equip_avatar_id: value.equip_avatar, + exp: 0, + is_protected: false, + level: value.level, + main_affix_id: value.main_affix_id, + tid: value.relic_id, + unique_id: value.get_unique_id(), + sub_affix_list: value + .sub_affixes + .iter() + .map(|v| RelicAffix { + affix_id: v.sub_affix_id, + cnt: v.count, + step: v.step, + }) + .collect::>(), + ..Default::default() + } +}); diff --git a/common/src/structs/scene.rs b/common/src/structs/scene.rs new file mode 100644 index 0000000..06b66cc --- /dev/null +++ b/common/src/structs/scene.rs @@ -0,0 +1,56 @@ +use proto::{MotionInfo, Vector}; +use serde::{Deserialize, Serialize}; + +use crate::impl_from; + +// SCENE +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Scene { + pub plane_id: u32, + pub floor_id: u32, + pub entry_id: u32, +} + +impl Default for Scene { + fn default() -> Self { + Self { + plane_id: 20411, + floor_id: 20411001, + entry_id: 2041101, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Position { + pub x: i32, + pub y: i32, + pub z: i32, + pub rot_y: i32, +} + +impl Default for Position { + fn default() -> Self { + Position { + x: -26968, + y: 78953, + z: 14457, + rot_y: 11858, + } + } +} + +impl_from!(Position, MotionInfo, |value| { + MotionInfo { + rot: Some(Vector { + x: 0, + y: value.rot_y, + z: 0, + }), + pos: Some(Vector { + x: value.x, + y: value.y, + z: value.z, + }), + } +}); diff --git a/gameserver/src/net/handlers/battle.rs b/gameserver/src/net/handlers/battle.rs index 38180f5..4cfc657 100644 --- a/gameserver/src/net/handlers/battle.rs +++ b/gameserver/src/net/handlers/battle.rs @@ -5,7 +5,7 @@ use rogue_magic_battle_unit_info::Item; use common::{ resources::GAME_RES, - sr_tools::{BattleType, Monster, RogueMagicComponentType}, + structs::{BattleType, Monster, RogueMagicComponentType}, }; use super::*; diff --git a/gameserver/src/net/handlers/chat.rs b/gameserver/src/net/handlers/chat.rs index 095748a..42e77dc 100644 --- a/gameserver/src/net/handlers/chat.rs +++ b/gameserver/src/net/handlers/chat.rs @@ -1,4 +1,4 @@ -use common::sr_tools::MultiPathAvatar; +use common::structs::MultiPathAvatar; use crate::{net::PlayerSession, util::cur_timestamp_ms}; diff --git a/gameserver/src/net/handlers/inventory.rs b/gameserver/src/net/handlers/inventory.rs index d5f12d6..565e0a1 100644 --- a/gameserver/src/net/handlers/inventory.rs +++ b/gameserver/src/net/handlers/inventory.rs @@ -1,3 +1,4 @@ +use common::sr_tools::FreesrData; use proto::{get_big_data_all_recommend_sc_rsp::RecommendType, *}; use crate::net::PlayerSession; @@ -14,12 +15,8 @@ pub async fn on_get_bag_cs_req( return; }; - res.equipment_list = player - .lightcones - .iter() - .map(|v| v.to_equipment_proto()) - .collect(); - res.relic_list = player.relics.iter().map(|v| v.to_relic_proto()).collect(); + res.equipment_list = player.lightcones.iter().map(|v| v.into()).collect(); + res.relic_list = player.relics.iter().map(|v| v.into()).collect(); res.material_list = vec![ Material { tid: 101, // Normal Pass @@ -43,31 +40,63 @@ pub async fn on_get_archive_data_cs_req( } pub async fn on_dress_relic_avatar_cs_req( - _session: &mut PlayerSession, - _req: &DressRelicAvatarCsReq, - _res: &mut DressRelicAvatarScRsp, + session: &mut PlayerSession, + req: &DressRelicAvatarCsReq, + _: &mut DressRelicAvatarScRsp, ) { + let Some(player) = session.json_data.get_mut() else { + tracing::error!("data is not set!"); + return; + }; + + if let Some(pkt) = equip_relic(player, req) { + let _ = session.send(pkt).await; + }; } pub async fn on_take_off_relic_cs_req( - _session: &mut PlayerSession, - _req: &TakeOffRelicCsReq, - _res: &mut TakeOffRelicScRsp, + session: &mut PlayerSession, + req: &TakeOffRelicCsReq, + _: &mut TakeOffRelicScRsp, ) { + let Some(player) = session.json_data.get_mut() else { + tracing::error!("data is not set!"); + return; + }; + + if let Some(pkt) = unequip_relic(player, req) { + let _ = session.send(pkt).await; + }; } pub async fn on_dress_avatar_cs_req( - _session: &mut PlayerSession, - _req: &DressAvatarCsReq, - _res: &mut DressAvatarScRsp, + session: &mut PlayerSession, + req: &DressAvatarCsReq, + _: &mut DressAvatarScRsp, ) { + let Some(player) = session.json_data.get_mut() else { + tracing::error!("data is not set!"); + return; + }; + + if let Some(pkt) = set_lightcone_equipper(player, req.avatar_id, req.equipment_unique_id) { + let _ = session.send(pkt).await; + }; } pub async fn on_take_off_equipment_cs_req( - _session: &mut PlayerSession, - _req: &TakeOffEquipmentCsReq, - _res: &mut TakeOffEquipmentScRsp, + session: &mut PlayerSession, + req: &TakeOffEquipmentCsReq, + _: &mut TakeOffEquipmentScRsp, ) { + let Some(player) = session.json_data.get_mut() else { + tracing::error!("data is not set!"); + return; + }; + + if let Some(pkt) = set_lightcone_equipper(player, req.avatar_id, 0) { + let _ = session.send(pkt).await; + }; } pub async fn on_get_big_data_all_recommend_cs_req( @@ -104,3 +133,210 @@ pub async fn on_get_big_data_all_recommend_cs_req( _ => {} } } + +pub async fn on_rank_up_avatar_cs_req( + session: &mut PlayerSession, + req: &RankUpAvatarCsReq, + _: &mut RankUpAvatarScRsp, +) -> Option<()> { + let player = session.json_data.get_mut()?; + let avatar = player.avatars.get_mut(&req.avatar_id)?; + + avatar.data.rank = req.rank; + + let avatar_id = avatar.avatar_id; + + let mut ret = PlayerSyncScNotify::default(); + + build_sync( + player, + &mut ret, + vec![avatar_id], + Vec::with_capacity(0), + Vec::with_capacity(0), + ); + + let _ = session.send(ret).await; + + Some(()) +} + +// TODO: move these somewhere else? + +fn set_lightcone_equipper( + player: &mut FreesrData, + target_avatar: u32, + target_lightcone_uid: u32, +) -> Option { + let mut ret = PlayerSyncScNotify::default(); + + let target_avatar = player.avatars.get(&target_avatar)?; + + let cur_avatar_lc_idx = player + .lightcones + .iter() + .position(|l| l.equip_avatar == target_avatar.avatar_id); + + // undress + if target_lightcone_uid == 0 + && let Some(cur_avatar_lc_idx) = cur_avatar_lc_idx + { + player.lightcones[cur_avatar_lc_idx].equip_avatar = 0; + + build_sync( + player, + &mut ret, + vec![target_avatar.avatar_id], + vec![cur_avatar_lc_idx], + Vec::with_capacity(0), + ); + + return Some(ret); + } + + let target_lightcone_idx = player + .lightcones + .iter() + .position(|l| l.get_unique_id() == target_lightcone_uid)?; + + // jika avatar sekarang sedang pakai LC, kita tukar pemiliknya dengan pemilik dari LC target + if let Some(cur_avatar_lc_idx) = cur_avatar_lc_idx { + player.lightcones[cur_avatar_lc_idx].equip_avatar = + player.lightcones[target_lightcone_idx].equip_avatar; + } + + let avatars_sync = vec![ + player.lightcones[target_lightcone_idx].equip_avatar, // old + target_avatar.avatar_id, // cur + ]; + + // set kepemilikan lightcone barunya ke avatar sekarang + player.lightcones[target_lightcone_idx].equip_avatar = target_avatar.avatar_id; + + build_sync( + player, + &mut ret, + avatars_sync, + [Some(target_lightcone_idx), cur_avatar_lc_idx] + .into_iter() + .flatten() + .collect(), + Vec::with_capacity(0), + ); + + Some(ret) +} + +fn equip_relic(player: &mut FreesrData, req: &DressRelicAvatarCsReq) -> Option { + let mut ret = PlayerSyncScNotify::default(); + + let target_avatar = player.avatars.get(&req.avatar_id)?; + + let mut avatar_ids_to_sy = vec![]; + let mut relic_index_to_sync = vec![]; + + for param in &req.param_list { + let Some(target_relic_idx) = player + .relics + .iter() + .position(|v| v.get_unique_id() == param.relic_unique_id) + else { + continue; + }; + + let cur_avatar_relic_idx = player + .relics + .iter() + .position(|r| r.equip_avatar == target_avatar.avatar_id && r.get_slot() == param.slot); + + // jika avatar sekarang sedang pakai LC, kita tukar pemiliknya dengan pemilik dari LC target + if let Some(cur_avatar_relic_idx) = cur_avatar_relic_idx { + avatar_ids_to_sy.push(player.relics[cur_avatar_relic_idx].equip_avatar); + player.relics[cur_avatar_relic_idx].equip_avatar = + player.relics[target_relic_idx].equip_avatar; + + relic_index_to_sync.push(cur_avatar_relic_idx); + } + + // old owner + avatar_ids_to_sy.push(player.relics[target_relic_idx].equip_avatar); + + // set kepemilikan relic barunya ke avatar sekarang + player.relics[target_relic_idx].equip_avatar = target_avatar.avatar_id; + + // new owner + avatar_ids_to_sy.push(player.relics[target_relic_idx].equip_avatar); + + relic_index_to_sync.push(target_relic_idx); + } + + build_sync( + player, + &mut ret, + avatar_ids_to_sy, + Vec::with_capacity(0), + relic_index_to_sync, + ); + + Some(ret) +} + +fn unequip_relic(player: &mut FreesrData, req: &TakeOffRelicCsReq) -> Option { + let mut ret = PlayerSyncScNotify::default(); + + let target_avatar = player.avatars.get(&req.avatar_id)?; + + let mut relic_index_to_sync = vec![]; + + for slot in &req.slot_list { + let relics = player + .relics + .iter() + .enumerate() + .filter(|(_, r)| r.equip_avatar == target_avatar.avatar_id && r.get_slot() == *slot) + .map(|(i, _)| i) + .collect::>(); + + for relic_idx in &relics { + player.relics[*relic_idx].equip_avatar = 0; + } + + relic_index_to_sync.extend(relics); + } + + build_sync( + player, + &mut ret, + vec![req.avatar_id], + Vec::with_capacity(0), + relic_index_to_sync, + ); + + Some(ret) +} + +fn build_sync( + player: &mut FreesrData, + ret: &mut PlayerSyncScNotify, + avatar_ids: Vec, + lightcone_indexes: Vec, + relic_indexes: Vec, +) { + let avatar_list = avatar_ids + .iter() + .filter_map(|id| player.get_avatar_proto(*id)) + .collect::>(); + ret.avatar_sync = Some(AvatarSync { avatar_list }); + ret.multi_path_avatar_type_info_list = avatar_ids + .into_iter() + .filter_map(|id| player.get_avatar_multipath_proto(id)) + .collect(); + ret.relic_list = relic_indexes + .into_iter() + .map(|id| (&player.relics[id]).into()) + .collect(); + ret.equipment_list = lightcone_indexes + .into_iter() + .map(|id| (&player.lightcones[id]).into()) + .collect(); +} diff --git a/gameserver/src/net/handlers/lineup.rs b/gameserver/src/net/handlers/lineup.rs index 5945bf4..a8b632d 100644 --- a/gameserver/src/net/handlers/lineup.rs +++ b/gameserver/src/net/handlers/lineup.rs @@ -1,4 +1,4 @@ -use common::sr_tools::AvatarJson; +use common::structs::AvatarJson; use scene_entity_info::Entity; use scene_entity_refresh_info::RefreshType; diff --git a/gameserver/src/net/handlers/player.rs b/gameserver/src/net/handlers/player.rs index c93712a..17d3a83 100644 --- a/gameserver/src/net/handlers/player.rs +++ b/gameserver/src/net/handlers/player.rs @@ -22,7 +22,7 @@ pub async fn on_player_heart_beat_cs_req( res.download_data = Some(ClientDownloadData { version: 51, time: res.server_time_ms as i64, - data: rbase64::decode("bG9jYWwgZnVuY3Rpb24gYmV0YV90ZXh0KG9iaikKICAgIGxvY2FsIGdhbWVPYmplY3QgPSBDUy5Vbml0eUVuZ2luZS5HYW1lT2JqZWN0LkZpbmQoIlVJUm9vdC9BYm92ZURpYWxvZy9CZXRhSGludERpYWxvZyhDbG9uZSkiKQogICAgaWYgZ2FtZU9iamVjdCB0aGVuCiAgICAgICAgbG9jYWwgdGV4dENvbXBvbmVudCA9IGdhbWVPYmplY3Q6R2V0Q29tcG9uZW50SW5DaGlsZHJlbih0eXBlb2YoQ1MuUlBHLkNsaWVudC5Mb2NhbGl6ZWRUZXh0KSkKICAgICAgICBpZiB0ZXh0Q29tcG9uZW50IHRoZW4KICAgICAgICAgICAgdGV4dENvbXBvbmVudC50ZXh0ID0gIiIKICAgICAgICBlbmQKICAgIGVuZAplbmQKCmxvY2FsIGZ1bmN0aW9uIHZlcnNpb25fdGV4dChvYmopCiAgICBsb2NhbCBnYW1lT2JqZWN0ID0gQ1MuVW5pdHlFbmdpbmUuR2FtZU9iamVjdC5GaW5kKCJWZXJzaW9uVGV4dCIpCiAgICBpZiBnYW1lT2JqZWN0IHRoZW4KICAgICAgICBsb2NhbCB0ZXh0Q29tcG9uZW50ID0gZ2FtZU9iamVjdDpHZXRDb21wb25lbnRJbkNoaWxkcmVuKHR5cGVvZihDUy5SUEcuQ2xpZW50LkxvY2FsaXplZFRleHQpKQogICAgICAgIGlmIHRleHRDb21wb25lbnQgdGhlbgogICAgICAgICAgICB0ZXh0Q29tcG9uZW50LnRleHQgPSAiPGNvbG9yPSMwMGUxZmY+Um9iaW5TUiE8L2NvbG9yPiIKICAgICAgICBlbmQKICAgIGVuZAplbmQKCnZlcnNpb25fdGV4dCgpCmJldGFfdGV4dCgpCg==").unwrap(), + data: rbase64::decode("BAEwB0dldFR5cGUIR2V0RmllbGQIR2V0VmFsdWUCQ1MGU3lzdGVtC0NvbGxlY3Rpb25zB0dlbmVyaWMETGlzdAZTdHJpbmcCY24DQWRkAmVuAmtyAmpwB1RvQXJyYXkDUlBHBkNsaWVudApHbG9iYWxWYXJzFXNfTG9jYWxpemF0aW9uTWFuYWdlchRfYWxsb3dlZExhbmd1YWdlS2V5cxFfYWxsb3dlZEF1ZGlvS2V5cwhHYW1lQ29yZRlBbGxvd2VkTGFuZ3VhZ2VFeGNlbFRhYmxlCGRhdGFEaWN0BXBhaXJzDExhbmd1YWdlTGlzdAtVbml0eUVuZ2luZQtBcHBsaWNhdGlvbgdNQVhfRlBTD3RhcmdldEZyYW1lUmF0ZQ9RdWFsaXR5U2V0dGluZ3MKdlN5bmNDb3VudApHYW1lT2JqZWN0BEZpbmQoVUlSb290L0Fib3ZlRGlhbG9nL0JldGFIaW50RGlhbG9nKENsb25lKQ1Mb2NhbGl6ZWRUZXh0BnR5cGVvZhZHZXRDb21wb25lbnRJbkNoaWxkcmVuB0JFVEFfV00EdGV4dAtWZXJzaW9uVGV4dCI8Y29sb3I9IzAwZTFmZj5Sb2JpblNSISB8IDwvY29sb3I+Dzxjb2xvcj0jMDBlMWZmPg1zX1ZlcnNpb25EYXRhF0dldFNlcnZlclBha1R5cGVWZXJzaW9uCDwvY29sb3I+BnhwY2FsbApJU19QQVRDSEVECQEBAAAAAAEWAAEAAAACAAAABgIAAAAADRQCABsAAAAAFQICAgYEAQAEBT4AFAICkQEAAAAVAgQCBgQAABQCAvMCAAAAFQIDABYCAAADAwEDAgMDAAQAAAAFAAAAAAAfDAIDAAIEAMAPAQL5BAAAAA8AAesFAAAADAEHAAYEAMAVAAICBgEAABUBAQIFBAgAFAIBXwkAAAAVAgMBBQQKABQCAV8JAAAAFQIDAQUECwAUAgFfCQAAABUCAwEFBAwAFAIBXwkAAAAVAgMBFAIBOQ0AAAAVAgIAFgIAAA4DBAMFAwYEAgQAwAMHAwgDCQQGBADAAwoDCwMMAw0DDgMPAAsAAAAGAAAAAAA/DAIDAAIEAMAPAQKsBAAAAA8AAaAFAAAAFAEAGwYAAAAVAQICBQMHAAQEPgAUAQGRCAAAABUBBAIGAwAAFAEB8wkAAAAVAQMCBQQKABQCAV8LAAAAFQIDAQUEDAAUAgFfCwAAABUCAwEFBA0AFAIBXwsAAAAVAgMBBQQOABQCAV8LAAAAFQIDARQCABsGAAAAFQICAgUEDwAEBT4AFAICkQgAAAAVAgQCBgQAABQCAvMJAAAAFQIDAgUFCgAUAwJfCwAAABUDAwEFBQwAFAMCXwsAAAAVAwMBBQUNABQDAl8LAAAAFQMDAQUFDgAUAwJfCwAAABUDAwEWAAEAEAMEAxADEQQCBADAAxIDEwMBAxQDAgMDAwoDCwMMAw0DDgMVABQAAAAHAAEAAAASDAIDAAIEAMAPAQLwBAAAAA8AAbkFAAAACQEAABUBAQIMAgcAAABgQAYDAAAVAgIEQQICABABBjIIAAAAPgL9/wIAAAAWAAEACQMEAxADFgQCBADAAxcDGAMZBAAAYEADGgAiAAAAAgAAAAAADAwAAwACBADABwEAygQAAAAQAQADBQAAAAwABwAGBADABAEAABABANAIAAAAFgABAAkDBAMbAxwEAgQAwAMdAx4DHwQGBADAAyAALAAAAAkAAAAAADcMAQMAAgQAwA8AAXYEAAAABQEFABUAAgIMBAgABxgAwA8DBBQJAAAATSwDAgwCCwAAAKBAFQICAhQAALEMAAAAFQADAgwBDgAAANBAEAEA1Q8AAAAMAQMAAgQAwA8AAXYEAAAABQEQABUAAgIMBAgABxgAwA8DBBQJAAAATSwDAgwCCwAAAKBAFQICAhQAALEMAAAAFQADAgUCEQAFAxIADAgIAAcYAMAPBwisEwAAAA8GByMUAAAAFAYGkRUAAAAVBgICBgQGAAUFFgA1AQIFEAEA1Q8AAAAWAAEAFwMEAxsDIQQCBADAAyIDIwMQAxEEBxgAwAMkAyUEAACgQAMmAycEAADQQAMoAykDKgMrAxIDLAMtAy4AMQAAAAMABQAAACQHAADKAAAAAFEABAAAAACABABoAQgAAMoAAAAADAACAAAAEEAJAQAACQIBABUAAwEMAAIAAAAQQAkBAgAJAgEAFQADAQcAACsDAAAAUgACAAEAAIAWAAEAAwABAAgAACsDAAAADAACAAAAEEAJAQMACQIBABUAAwEMAAIAAAAQQAkBBAAJAgEAFQADARYAAQAEAx0DLwQAABBAAzAAPwAAAAsAAAEAABVFAAAARAAAAEQBAQBEAgIARAMDAEQEBABKAAIARAUFAEQGBgBEBwcASgAFAEoAAABKAAYASgAEAEoAAwAMCAkAAACAQAYJBwAGCgAAFQgDARYAAQAKBgAGAQYCBgMGBAYFBgYGBwMvBAAAgEAIAAECAwQFBgcBAAAACA==").unwrap(), haehhcpoapp: 0 }); } diff --git a/gameserver/src/net/handlers/scene.rs b/gameserver/src/net/handlers/scene.rs index bd137ec..4ce849c 100644 --- a/gameserver/src/net/handlers/scene.rs +++ b/gameserver/src/net/handlers/scene.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use common::{ resources::GAME_RES, - sr_tools::{AvatarJson, Position}, + structs::{AvatarJson, Position}, }; use scene_entity_info::Entity; @@ -277,7 +277,7 @@ async fn load_scene( let entity_info = SceneEntityInfo { inst_id: prop.inst_id, group_id: prop.group_id, - motion: Some(prop_position.to_motion()), + motion: Some(prop_position.into()), entity: Some(Entity::Prop(ScenePropInfo { prop_state: prop.prop_state, prop_id: prop.prop_id, @@ -308,7 +308,7 @@ async fn load_scene( inst_id: npc.inst_id, group_id: npc.group_id, entity_id: npc_entity_id, - motion: Some(npc_position.to_motion()), + motion: Some(npc_position.into()), entity: Some(Entity::Npc(SceneNpcInfo { npc_id: npc.npc_id, ..Default::default() @@ -339,7 +339,7 @@ async fn load_scene( inst_id: monster.inst_id, group_id: monster.group_id, entity_id: monster_entity_id, - motion: Some(monster_position.to_motion()), + motion: Some(monster_position.into()), entity: Some(Entity::NpcMonster(npc_monster)), }; diff --git a/gameserver/src/net/packet.rs b/gameserver/src/net/packet.rs index ce74931..e5413d5 100644 --- a/gameserver/src/net/packet.rs +++ b/gameserver/src/net/packet.rs @@ -190,6 +190,7 @@ trait_handler! { TakeOffEquipment; DressRelicAvatar; TakeOffRelic; + RankUpAvatar; // Chat (dummy!) SendMsg; diff --git a/gameserver/src/net/session.rs b/gameserver/src/net/session.rs index c956102..9b42df8 100644 --- a/gameserver/src/net/session.rs +++ b/gameserver/src/net/session.rs @@ -14,12 +14,12 @@ use proto::{AvatarSync, CmdID, CmdPlayerType, PlayerSyncScNotify}; use tokio::{ io::AsyncWrite, net::UdpSocket, - sync::{watch, Mutex}, + sync::{Mutex, watch}, }; use crate::util; -use super::{packet::CommandHandler, NetPacket}; +use super::{NetPacket, packet::CommandHandler}; struct RemoteEndPoint { socket: Arc, @@ -41,12 +41,10 @@ impl PlayerSession { let (shutdown_tx, shutdown_rx) = watch::channel(()); Self { token, - kcp: Arc::new(Mutex::new(Kcp::new( - conv, - token, - false, - RemoteEndPoint { socket, addr }, - ))), + kcp: Arc::new(Mutex::new(Kcp::new(conv, token, false, RemoteEndPoint { + socket, + addr, + }))), start_time: util::cur_timestamp_secs(), json_data: OnceLock::new(), shutdown_rx, @@ -124,8 +122,8 @@ impl PlayerSession { // clear relics & lightcones self.send(PlayerSyncScNotify { - del_equipment_list: (2000..3500).collect(), - del_relic_list: (1..2000).collect(), + del_equipment_list: (2000..=3500).collect(), + del_relic_list: (1..=2000).collect(), ..Default::default() }) .await @@ -148,12 +146,8 @@ impl PlayerSession { // Sync new relics self.send(PlayerSyncScNotify { - relic_list: json.relics.iter().map(|v| v.to_relic_proto()).collect(), - equipment_list: json - .lightcones - .iter() - .map(|v| v.to_equipment_proto()) - .collect(), + relic_list: json.relics.iter().map(|v| v.into()).collect(), + equipment_list: json.lightcones.iter().map(|v| v.into()).collect(), ..Default::default() }) .await