refactor: some restructuring

- move structs inside `sr_tools.rs` into individual file
- implement in-game equip handler (relic & lightcone)
This commit is contained in:
amizing25 2025-03-11 17:27:05 +07:00
parent fff8fc803e
commit d3af43fb06
18 changed files with 1170 additions and 737 deletions

View File

@ -1,2 +1,3 @@
pub mod resources;
pub mod sr_tools;
pub mod structs;

View File

@ -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<u32>,
#[serde(alias = "spValue")]
pub sp_value: Option<u32>,
#[serde(alias = "spMax")]
pub sp_max: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AvatarData {
pub rank: u32,
pub skills: HashMap<u32, u32>,
}
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
use crate::structs::{
// AllowedLanguages,
avatar::{AvatarJson, MultiPathAvatar},
battle::BattleConfig,
lightcone::Lightcone,
persistent::Persistent,
relic::Relic,
scene::{Position, Scene},
};
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::<Vec<_>>(),
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::<Vec<_>>(),
..Default::default()
}
}
pub fn to_battle_avatar_proto(
&self,
index: u32,
lightcone: Option<&Lightcone>,
relics: Vec<&Relic>,
) -> (BattleAvatar, Vec<BattleBuff>) {
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::<Vec<_>>(),
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::<Vec<_>>(),
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::<BattleBuff>::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<LineupAvatar> {
let avatar_ids = player
.avatars
.values()
.map(|v| &v.avatar_id)
.collect::<Vec<_>>();
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::<Vec<LineupAvatar>>()
}
pub fn to_lineup_info(lineups: &BTreeMap<u32, u32>) -> 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<SubAffix>,
#[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::<Vec<_>>(),
..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::<Vec<_>>(),
..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::<Vec<_>>(),
..Default::default()
}
}
pub fn to_scene_monster_waves(monsters: &[Vec<Self>]) -> Vec<SceneMonsterWave> {
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<Vec<Monster>>,
pub blessings: Vec<BattleBuffJson>,
pub stage_id: u32,
pub cycle_count: u32,
pub path_resonance_id: u32,
pub custom_stats: Vec<SubAffix>,
#[serde(default)]
pub scepters: Vec<RogueMagicScepter>,
}
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<DynamicKey>,
#[serde(default)]
pub dynamic_values: Vec<DynamicKey>,
}
#[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<RogueMagicComponent>,
}
#[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<Lightcone>,
@ -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<u32, u32>,
#[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<u32, u32>,
#[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<Avatar> {
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<u32> 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<MultiPathAvatar> 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::<Vec<_>>(),
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::<Vec<_>>(),
..Default::default()
})
}
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_avatar_multipath_proto(&self, avatar_id: u32) -> Option<MultiPathAvatarTypeInfo> {
let avatar = self.avatars.get(&avatar_id)?;
let mp_type = MultiPathAvatar::from(avatar_id);
if mp_type == MultiPathAvatar::Unk {
return None;
}
pub fn is_mc(&self) -> bool {
(*self as u32) > 8000
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 to_vec() -> Vec<MultiPathAvatar> {
vec![
Self::MalePyhsical,
Self::FemalePhysical,
Self::MalePreservation,
Self::FemalePreservation,
Self::MaleHarmony,
Self::FemaleHarmony,
Self::MaleRememberance,
Self::FemaleRememberance,
Self::MarchHunt,
Self::MarchPreservation,
]
pub fn get_multi_path_info(&self) -> Vec<MultiPathAvatarTypeInfo> {
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;
}
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::<FreesrData>(&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<MultiPathAvatarTypeInfo> {
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()
}
}

View File

@ -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<u32> 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<MultiPathAvatar> 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<MultiPathAvatar> {
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<u32>,
#[serde(alias = "spValue")]
pub sp_value: Option<u32>,
#[serde(alias = "spMax")]
pub sp_max: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AvatarData {
pub rank: u32,
pub skills: HashMap<u32, u32>,
}
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::<Vec<_>>(),
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::<Vec<_>>(),
..Default::default()
}
}
pub fn to_battle_avatar_proto(
&self,
index: u32,
lightcone: Option<&Lightcone>,
relics: Vec<&Relic>,
) -> (BattleAvatar, Vec<BattleBuff>) {
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::<Vec<_>>(),
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::<Vec<_>>(),
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::<BattleBuff>::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<LineupAvatar> {
let avatar_ids = player
.avatars
.values()
.map(|v| &v.avatar_id)
.collect::<Vec<_>>();
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::<Vec<LineupAvatar>>()
}
pub fn to_lineup_info(lineups: &BTreeMap<u32, u32>) -> 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
}
}

View File

@ -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<Vec<Monster>>,
pub blessings: Vec<BattleBuffJson>,
pub stage_id: u32,
pub cycle_count: u32,
pub path_resonance_id: u32,
pub custom_stats: Vec<SubAffix>,
#[serde(default)]
pub scepters: Vec<RogueMagicScepter>,
}
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<DynamicKey>,
#[serde(default)]
pub dynamic_values: Vec<DynamicKey>,
}
#[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<RogueMagicComponent>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct RogueMagicComponent {
pub id: u32,
pub level: u32,
pub component_type: RogueMagicComponentType,
}

View File

@ -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)
}
}

47
common/src/structs/mod.rs Normal file
View File

@ -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
}
}
};
}

View File

@ -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<Self>]) -> Vec<SceneMonsterWave> {
monsters
.iter()
.enumerate()
.map(|(i, v)| Self::to_scene_monster_wave(i as u32, v))
.collect::<_>()
}
}

View File

@ -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<AllowedLanguages, Self::Err> {
// 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<u32, u32>,
#[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,
}
}
}

View File

@ -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<SubAffix>,
#[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::<Vec<_>>(),
..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::<Vec<_>>(),
..Default::default()
}
});

View File

@ -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,
}),
}
});

View File

@ -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::*;

View File

@ -1,4 +1,4 @@
use common::sr_tools::MultiPathAvatar;
use common::structs::MultiPathAvatar;
use crate::{net::PlayerSession, util::cur_timestamp_ms};

View File

@ -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<PlayerSyncScNotify> {
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<PlayerSyncScNotify> {
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<PlayerSyncScNotify> {
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::<Vec<_>>();
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<u32>,
lightcone_indexes: Vec<usize>,
relic_indexes: Vec<usize>,
) {
let avatar_list = avatar_ids
.iter()
.filter_map(|id| player.get_avatar_proto(*id))
.collect::<Vec<_>>();
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();
}

View File

@ -1,4 +1,4 @@
use common::sr_tools::AvatarJson;
use common::structs::AvatarJson;
use scene_entity_info::Entity;
use scene_entity_refresh_info::RefreshType;

View File

@ -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
});
}

View File

@ -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)),
};

View File

@ -190,6 +190,7 @@ trait_handler! {
TakeOffEquipment;
DressRelicAvatar;
TakeOffRelic;
RankUpAvatar;
// Chat (dummy!)
SendMsg;

View File

@ -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<UdpSocket>,
@ -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