mirror of
https://git.neonteam.dev/amizing/robinsr.git
synced 2025-03-12 03:28:30 -04:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c5ba8b6e4 | ||
|
|
608775b428 | ||
|
|
d3af43fb06 | ||
|
|
fff8fc803e | ||
|
|
ea640e7992 | ||
|
|
505c446a53 | ||
|
|
c034f8a2f9 | ||
|
|
6d9bf58a85 | ||
|
|
7d233dc614 | ||
|
|
6d2489de47 | ||
|
|
6435d4e30b |
48
Cargo.toml
48
Cargo.toml
@ -6,19 +6,23 @@ resolver = "3"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1.0.81"
|
# Framework
|
||||||
ansi_term = "0.12.1"
|
tokio = { version = "1.36.0", features = ["full"] }
|
||||||
atomic_refcell = "0.1.13"
|
|
||||||
lazy_static = "1.4.0"
|
|
||||||
|
|
||||||
axum = "0.8.1"
|
axum = "0.8.1"
|
||||||
axum-server = "0.7.1"
|
axum-server = "0.7.1"
|
||||||
tower-http = "0.6.2"
|
tower-http = "0.6.2"
|
||||||
|
reqwest = "0.12.12"
|
||||||
|
|
||||||
|
# JSON
|
||||||
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
serde_json = "1.0.114"
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
tracing = "0.1.40"
|
||||||
|
ansi_term = "0.12.1"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
|
|
||||||
rbase64 = "2.0.3"
|
# Cryptography
|
||||||
rand = "0.9.0"
|
|
||||||
rsa = { version = "0.9.6", features = [
|
rsa = { version = "0.9.6", features = [
|
||||||
"sha1",
|
"sha1",
|
||||||
"nightly",
|
"nightly",
|
||||||
@ -26,41 +30,27 @@ rsa = { version = "0.9.6", features = [
|
|||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
] }
|
] }
|
||||||
|
rand = "0.9.0"
|
||||||
|
|
||||||
|
# Serialization
|
||||||
prost = "0.13.5"
|
prost = "0.13.5"
|
||||||
prost-types = "0.13.5"
|
prost-types = "0.13.5"
|
||||||
prost-build = "0.13.5"
|
prost-build = "0.13.5"
|
||||||
|
rbase64 = "2.0.3"
|
||||||
|
|
||||||
|
# Utilities
|
||||||
paste = "1.0.14"
|
paste = "1.0.14"
|
||||||
sysinfo = "0.33.1"
|
|
||||||
notify = "8.0.0"
|
notify = "8.0.0"
|
||||||
notify-debouncer-mini = "0.6.0"
|
notify-debouncer-mini = "0.6.0"
|
||||||
|
anyhow = "1.0.81"
|
||||||
|
|
||||||
hex = "0.4.3"
|
# Local
|
||||||
|
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
|
||||||
serde_json = "1.0.114"
|
|
||||||
|
|
||||||
tokio = { version = "1.36.0", features = ["full"] }
|
|
||||||
tokio-util = { version = "0.7.10", features = ["io"] }
|
|
||||||
|
|
||||||
tracing = "0.1.40"
|
|
||||||
tracing-futures = "0.2.5"
|
|
||||||
tracing-log = { version = "0.2.0", features = ["std", "log-tracer"] }
|
|
||||||
tracing-subscriber = { version = "0.3.18", features = [
|
|
||||||
"env-filter",
|
|
||||||
"registry",
|
|
||||||
"std",
|
|
||||||
"tracing",
|
|
||||||
"tracing-log",
|
|
||||||
] }
|
|
||||||
tracing-bunyan-formatter = "0.3.9"
|
|
||||||
|
|
||||||
proto = { path = "proto/" }
|
proto = { path = "proto/" }
|
||||||
proto-derive = { path = "proto/proto-derive" }
|
proto-derive = { path = "proto/proto-derive" }
|
||||||
mhy-kcp = { path = "kcp/", features = ["tokio"] }
|
mhy-kcp = { path = "kcp/", features = ["tokio"] }
|
||||||
common = { path = "common/" }
|
common = { path = "common/" }
|
||||||
|
sdkserver = { path = "sdkserver/"}
|
||||||
|
gameserver = { path = "gameserver/"}
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true # Automatically strip symbols from the binary.
|
strip = true # Automatically strip symbols from the binary.
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
pub mod resources;
|
pub mod resources;
|
||||||
pub mod sr_tools;
|
pub mod sr_tools;
|
||||||
|
pub mod structs;
|
||||||
|
|||||||
@ -1,517 +1,17 @@
|
|||||||
use proto::*;
|
use proto::{Avatar, AvatarSkillTree, MultiPathAvatarTypeInfo};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
// AVATAR
|
use crate::structs::{
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
// AllowedLanguages,
|
||||||
pub struct AvatarJson {
|
avatar::{AvatarJson, MultiPathAvatar},
|
||||||
#[serde(alias = "ownerUid")]
|
battle::BattleConfig,
|
||||||
pub owner_uid: u32,
|
lightcone::Lightcone,
|
||||||
#[serde(alias = "avatarId")]
|
persistent::Persistent,
|
||||||
pub avatar_id: u32,
|
relic::Relic,
|
||||||
pub data: AvatarData,
|
scene::{Position, Scene},
|
||||||
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: 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 {
|
|
||||||
entry_id: 2032101,
|
|
||||||
plane_id: 20321,
|
|
||||||
floor_id: 20321001,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
||||||
pub struct Position {
|
|
||||||
pub x: i32,
|
|
||||||
pub y: i32,
|
|
||||||
pub z: i32,
|
|
||||||
pub rot_y: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct FreesrData {
|
pub struct FreesrData {
|
||||||
pub lightcones: Vec<Lightcone>,
|
pub lightcones: Vec<Lightcone>,
|
||||||
@ -520,153 +20,138 @@ pub struct FreesrData {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub battle_config: BattleConfig,
|
pub battle_config: BattleConfig,
|
||||||
|
|
||||||
#[serde(default, skip_serializing)]
|
// Non freesr-data.json fields
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub lineups: BTreeMap<u32, u32>,
|
pub lineups: BTreeMap<u32, u32>,
|
||||||
#[serde(default, skip_serializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
#[serde(default, skip_serializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub scene: Scene,
|
pub scene: Scene,
|
||||||
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub main_character: MultiPathAvatar,
|
pub main_character: MultiPathAvatar,
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub march_type: MultiPathAvatar,
|
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)]
|
impl FreesrData {
|
||||||
pub struct Persistent {
|
pub fn get_avatar_proto(&self, avatar_id: u32) -> Option<Avatar> {
|
||||||
#[serde(default)]
|
let avatar = self.avatars.get(&avatar_id)?;
|
||||||
pub lineups: BTreeMap<u32, u32>,
|
let lightcone = self.lightcones.iter().find(|l| l.equip_avatar == avatar_id);
|
||||||
#[serde(default)]
|
let relics = self.relics.iter().filter(|r| r.equip_avatar == avatar_id);
|
||||||
pub position: Position,
|
|
||||||
#[serde(default)]
|
|
||||||
pub scene: Scene,
|
|
||||||
pub main_character: MultiPathAvatar,
|
|
||||||
pub march_type: MultiPathAvatar,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Persistent {
|
// TODO: HARDCODED
|
||||||
fn default() -> Self {
|
let base_avatar_id = if avatar.avatar_id > 8000 {
|
||||||
let mut lineups = BTreeMap::<u32, u32>::new();
|
8001
|
||||||
lineups.insert(0, 8001);
|
} else if avatar.avatar_id == 1001 || avatar.avatar_id == 1224 {
|
||||||
lineups.insert(1, 0);
|
1001
|
||||||
lineups.insert(2, 0);
|
|
||||||
lineups.insert(3, 0);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
lineups,
|
|
||||||
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
|
|
||||||
} else {
|
} 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 {
|
pub fn get_avatar_multipath_proto(&self, avatar_id: u32) -> Option<MultiPathAvatarTypeInfo> {
|
||||||
match *self {
|
let avatar = self.avatars.get(&avatar_id)?;
|
||||||
MultiPathAvatar::MalePyhsical => MultiPathAvatarType::BoyWarriorType,
|
let mp_type = MultiPathAvatar::from(avatar_id);
|
||||||
MultiPathAvatar::FemalePhysical => MultiPathAvatarType::GirlWarriorType,
|
|
||||||
MultiPathAvatar::MalePreservation => MultiPathAvatarType::BoyKnightType,
|
if mp_type == MultiPathAvatar::Unk {
|
||||||
MultiPathAvatar::FemalePreservation => MultiPathAvatarType::GirlKnightType,
|
return None;
|
||||||
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 {
|
Some(MultiPathAvatarTypeInfo {
|
||||||
(*self as u32) > 8000
|
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> {
|
pub fn get_multi_path_info(&self) -> Vec<MultiPathAvatarTypeInfo> {
|
||||||
vec![
|
MultiPathAvatar::to_vec()
|
||||||
Self::MalePyhsical,
|
.into_iter()
|
||||||
Self::FemalePhysical,
|
.filter_map(|mp_type| {
|
||||||
Self::MalePreservation,
|
if mp_type.is_mc() && mp_type.get_gender() != self.main_character.get_gender() {
|
||||||
Self::FemalePreservation,
|
return Option::None;
|
||||||
Self::MaleHarmony,
|
}
|
||||||
Self::FemaleHarmony,
|
|
||||||
Self::MaleRememberance,
|
let avatar_info = self.avatars.get(&((mp_type) as u32))?;
|
||||||
Self::FemaleRememberance,
|
Some(MultiPathAvatarTypeInfo {
|
||||||
Self::MarchHunt,
|
avatar_id: mp_type as i32,
|
||||||
Self::MarchPreservation,
|
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 {
|
impl FreesrData {
|
||||||
pub async fn load() -> Self {
|
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
|
.await
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
serde_json::from_str::<FreesrData>(&v)
|
serde_json::from_str::<FreesrData>(&v)
|
||||||
@ -674,26 +159,38 @@ impl FreesrData {
|
|||||||
})
|
})
|
||||||
.expect("failed to read freesr-data.json");
|
.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")
|
&tokio::fs::read_to_string("persistent")
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
)
|
)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
json.lineups = json2.lineups;
|
freesr_data.lineups = persistent.lineups;
|
||||||
json.position = json2.position;
|
freesr_data.position = persistent.position;
|
||||||
json.scene = json2.scene;
|
freesr_data.scene = persistent.scene;
|
||||||
json.main_character = json2.main_character;
|
freesr_data.main_character = persistent.main_character;
|
||||||
json.march_type = json2.march_type;
|
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;
|
// remove unequipped lightcones
|
||||||
|
if freesr_data.lightcones.len() > 1500 {
|
||||||
if json.march_type as u32 > 8000 {
|
freesr_data.lightcones.retain(|v| v.equip_avatar != 0);
|
||||||
json.march_type = MultiPathAvatar::MarchHunt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
pub async fn update(&mut self) {
|
||||||
@ -720,52 +217,12 @@ impl FreesrData {
|
|||||||
position: self.position.clone(),
|
position: self.position.clone(),
|
||||||
scene: self.scene.clone(),
|
scene: self.scene.clone(),
|
||||||
march_type: self.march_type,
|
march_type: self.march_type,
|
||||||
|
// game_language: self.game_language,
|
||||||
|
// voice_language: self.voice_langauge,
|
||||||
})
|
})
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
tracing::info!("persistent saved");
|
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
286
common/src/structs/avatar.rs
Normal file
286
common/src/structs/avatar.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
112
common/src/structs/battle.rs
Normal file
112
common/src/structs/battle.rs
Normal 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,
|
||||||
|
}
|
||||||
45
common/src/structs/lightcone.rs
Normal file
45
common/src/structs/lightcone.rs
Normal 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
47
common/src/structs/mod.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
48
common/src/structs/monster.rs
Normal file
48
common/src/structs/monster.rs
Normal 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::<_>()
|
||||||
|
}
|
||||||
|
}
|
||||||
60
common/src/structs/persistent.rs
Normal file
60
common/src/structs/persistent.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
common/src/structs/relic.rs
Normal file
95
common/src/structs/relic.rs
Normal 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()
|
||||||
|
}
|
||||||
|
});
|
||||||
56
common/src/structs/scene.rs
Normal file
56
common/src/structs/scene.rs
Normal 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,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -4,33 +4,35 @@ edition = "2024"
|
|||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ansi_term.workspace = true
|
# Framework
|
||||||
anyhow.workspace = true
|
tokio.workspace = true
|
||||||
atomic_refcell.workspace = true
|
|
||||||
env_logger.workspace = true
|
|
||||||
hex.workspace = true
|
|
||||||
lazy_static.workspace = true
|
|
||||||
paste.workspace = true
|
|
||||||
rbase64.workspace = true
|
|
||||||
notify.workspace = true
|
|
||||||
notify-debouncer-mini.workspace = true
|
|
||||||
|
|
||||||
|
# JSON
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|
||||||
tokio.workspace = true
|
# Logging
|
||||||
tokio-util.workspace = true
|
|
||||||
|
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracing-futures.workspace = true
|
ansi_term.workspace = true
|
||||||
tracing-log.workspace = true
|
env_logger.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
|
||||||
tracing-bunyan-formatter.workspace = true
|
|
||||||
|
|
||||||
|
# Encoding / Serialization
|
||||||
prost.workspace = true
|
prost.workspace = true
|
||||||
proto.workspace = true
|
rbase64.workspace = true
|
||||||
proto-derive.workspace = true
|
|
||||||
|
|
||||||
|
# Cryptography
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
paste.workspace = true
|
||||||
|
notify.workspace = true
|
||||||
|
notify-debouncer-mini.workspace = true
|
||||||
|
|
||||||
|
# Error handling
|
||||||
|
anyhow.workspace = true
|
||||||
|
|
||||||
|
# Local
|
||||||
mhy-kcp.workspace = true
|
mhy-kcp.workspace = true
|
||||||
common.workspace = true
|
common.workspace = true
|
||||||
|
proto.workspace = true
|
||||||
|
proto-derive.workspace = true
|
||||||
|
|||||||
12
gameserver/src/lib.rs
Normal file
12
gameserver/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#![feature(let_chains)]
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
mod net;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use net::gateway::Gateway;
|
||||||
|
|
||||||
|
pub async fn start_gameserver() -> Result<()> {
|
||||||
|
let mut gateway = Gateway::new("0.0.0.0", 23301).await?;
|
||||||
|
Box::pin(gateway.listen()).await
|
||||||
|
}
|
||||||
@ -1,29 +0,0 @@
|
|||||||
#[macro_export]
|
|
||||||
macro_rules! log_error {
|
|
||||||
($e:expr) => {
|
|
||||||
if let Err(e) = $e {
|
|
||||||
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($context:expr, $e:expr $(,)?) => {
|
|
||||||
if let Err(e) = $e {
|
|
||||||
let e = format!("{:?}", ::anyhow::anyhow!(e).context($context));
|
|
||||||
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($ok_context:expr, $err_context:expr, $e:expr $(,)?) => {
|
|
||||||
if let Err(e) = $e {
|
|
||||||
let e = format!("{:?}", ::anyhow::anyhow!(e).context($err_context));
|
|
||||||
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
|
|
||||||
} else {
|
|
||||||
tracing::info!($ok_context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_tracing() {
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
ansi_term::enable_ansi_support().unwrap();
|
|
||||||
|
|
||||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
|
||||||
}
|
|
||||||
@ -1,16 +1,14 @@
|
|||||||
#![feature(let_chains)]
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
mod logging;
|
pub fn init_tracing() {
|
||||||
mod net;
|
#[cfg(target_os = "windows")]
|
||||||
mod util;
|
ansi_term::enable_ansi_support().unwrap();
|
||||||
|
|
||||||
use logging::init_tracing;
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
use net::gateway::Gateway;
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
init_tracing();
|
init_tracing();
|
||||||
let mut gateway = Gateway::new("0.0.0.0", 23301).await?;
|
gameserver::start_gameserver().await
|
||||||
Box::pin(gateway.listen()).await
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use std::{
|
|||||||
Arc,
|
Arc,
|
||||||
atomic::{AtomicU32, Ordering},
|
atomic::{AtomicU32, Ordering},
|
||||||
},
|
},
|
||||||
time::Duration,
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@ -113,6 +113,11 @@ impl Gateway {
|
|||||||
tracing::info!("watching freesr-data.json changes");
|
tracing::info!("watching freesr-data.json changes");
|
||||||
|
|
||||||
let mut shutdown_rx = session.read().await.shutdown_rx.clone();
|
let mut shutdown_rx = session.read().await.shutdown_rx.clone();
|
||||||
|
|
||||||
|
let mut last_modified = std::fs::metadata(path)
|
||||||
|
.and_then(|meta| meta.modified())
|
||||||
|
.unwrap_or(SystemTime::UNIX_EPOCH);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = rx.recv() => {
|
res = rx.recv() => {
|
||||||
@ -125,11 +130,19 @@ impl Gateway {
|
|||||||
.iter()
|
.iter()
|
||||||
.any(|p| p.path.file_name() == path.file_name())
|
.any(|p| p.path.file_name() == path.file_name())
|
||||||
{
|
{
|
||||||
|
if let Ok(metadata) = std::fs::metadata(path) {
|
||||||
|
if let Ok(modified) = metadata.modified() {
|
||||||
|
if modified > last_modified {
|
||||||
|
last_modified = modified;
|
||||||
|
|
||||||
let mut session = session.write().await;
|
let mut session = session.write().await;
|
||||||
if let Some(json) = session.json_data.get_mut() {
|
if let Some(json) = session.json_data.get_mut() {
|
||||||
let _ = json.update().await;
|
let _ = json.update().await;
|
||||||
session.sync_player().await;
|
session.sync_player().await;
|
||||||
tracing::info!("json updated")
|
tracing::info!("json updated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use rogue_magic_battle_unit_info::Item;
|
|||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
resources::GAME_RES,
|
resources::GAME_RES,
|
||||||
sr_tools::{BattleType, Monster, RogueMagicComponentType},
|
structs::{BattleType, Monster, RogueMagicComponentType},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use common::sr_tools::MultiPathAvatar;
|
use common::structs::MultiPathAvatar;
|
||||||
|
|
||||||
use crate::{net::PlayerSession, util::cur_timestamp_ms};
|
use crate::{net::PlayerSession, util::cur_timestamp_ms};
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use common::sr_tools::FreesrData;
|
||||||
use proto::{get_big_data_all_recommend_sc_rsp::RecommendType, *};
|
use proto::{get_big_data_all_recommend_sc_rsp::RecommendType, *};
|
||||||
|
|
||||||
use crate::net::PlayerSession;
|
use crate::net::PlayerSession;
|
||||||
@ -14,12 +15,8 @@ pub async fn on_get_bag_cs_req(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
res.equipment_list = player
|
res.equipment_list = player.lightcones.iter().map(|v| v.into()).collect();
|
||||||
.lightcones
|
res.relic_list = player.relics.iter().map(|v| v.into()).collect();
|
||||||
.iter()
|
|
||||||
.map(|v| v.to_equipment_proto())
|
|
||||||
.collect();
|
|
||||||
res.relic_list = player.relics.iter().map(|v| v.to_relic_proto()).collect();
|
|
||||||
res.material_list = vec![
|
res.material_list = vec![
|
||||||
Material {
|
Material {
|
||||||
tid: 101, // Normal Pass
|
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(
|
pub async fn on_dress_relic_avatar_cs_req(
|
||||||
_session: &mut PlayerSession,
|
session: &mut PlayerSession,
|
||||||
_req: &DressRelicAvatarCsReq,
|
req: &DressRelicAvatarCsReq,
|
||||||
_res: &mut DressRelicAvatarScRsp,
|
_: &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(
|
pub async fn on_take_off_relic_cs_req(
|
||||||
_session: &mut PlayerSession,
|
session: &mut PlayerSession,
|
||||||
_req: &TakeOffRelicCsReq,
|
req: &TakeOffRelicCsReq,
|
||||||
_res: &mut TakeOffRelicScRsp,
|
_: &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(
|
pub async fn on_dress_avatar_cs_req(
|
||||||
_session: &mut PlayerSession,
|
session: &mut PlayerSession,
|
||||||
_req: &DressAvatarCsReq,
|
req: &DressAvatarCsReq,
|
||||||
_res: &mut DressAvatarScRsp,
|
_: &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(
|
pub async fn on_take_off_equipment_cs_req(
|
||||||
_session: &mut PlayerSession,
|
session: &mut PlayerSession,
|
||||||
_req: &TakeOffEquipmentCsReq,
|
req: &TakeOffEquipmentCsReq,
|
||||||
_res: &mut TakeOffEquipmentScRsp,
|
_: &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(
|
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();
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use common::sr_tools::AvatarJson;
|
use common::structs::AvatarJson;
|
||||||
use scene_entity_info::Entity;
|
use scene_entity_info::Entity;
|
||||||
use scene_entity_refresh_info::RefreshType;
|
use scene_entity_refresh_info::RefreshType;
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@ pub async fn on_player_heart_beat_cs_req(
|
|||||||
res.download_data = Some(ClientDownloadData {
|
res.download_data = Some(ClientDownloadData {
|
||||||
version: 51,
|
version: 51,
|
||||||
time: res.server_time_ms as i64,
|
time: res.server_time_ms as i64,
|
||||||
data: rbase64::decode("bG9jYWwgZnVuY3Rpb24gYmV0YV90ZXh0KG9iaikKICAgIGxvY2FsIGdhbWVPYmplY3QgPSBDUy5Vbml0eUVuZ2luZS5HYW1lT2JqZWN0LkZpbmQoIlVJUm9vdC9BYm92ZURpYWxvZy9CZXRhSGludERpYWxvZyhDbG9uZSkiKQogICAgaWYgZ2FtZU9iamVjdCB0aGVuCiAgICAgICAgbG9jYWwgdGV4dENvbXBvbmVudCA9IGdhbWVPYmplY3Q6R2V0Q29tcG9uZW50SW5DaGlsZHJlbih0eXBlb2YoQ1MuUlBHLkNsaWVudC5Mb2NhbGl6ZWRUZXh0KSkKICAgICAgICBpZiB0ZXh0Q29tcG9uZW50IHRoZW4KICAgICAgICAgICAgdGV4dENvbXBvbmVudC50ZXh0ID0gIiIKICAgICAgICBlbmQKICAgIGVuZAplbmQKCmxvY2FsIGZ1bmN0aW9uIHZlcnNpb25fdGV4dChvYmopCiAgICBsb2NhbCBnYW1lT2JqZWN0ID0gQ1MuVW5pdHlFbmdpbmUuR2FtZU9iamVjdC5GaW5kKCJWZXJzaW9uVGV4dCIpCiAgICBpZiBnYW1lT2JqZWN0IHRoZW4KICAgICAgICBsb2NhbCB0ZXh0Q29tcG9uZW50ID0gZ2FtZU9iamVjdDpHZXRDb21wb25lbnRJbkNoaWxkcmVuKHR5cGVvZihDUy5SUEcuQ2xpZW50LkxvY2FsaXplZFRleHQpKQogICAgICAgIGlmIHRleHRDb21wb25lbnQgdGhlbgogICAgICAgICAgICB0ZXh0Q29tcG9uZW50LnRleHQgPSAiPGNvbG9yPSMwMGUxZmY+Um9iaW5TUiE8L2NvbG9yPiIKICAgICAgICBlbmQKICAgIGVuZAplbmQKCnZlcnNpb25fdGV4dCgpCmJldGFfdGV4dCgpCg==").unwrap(),
|
data: rbase64::decode("BAEwB0dldFR5cGUIR2V0RmllbGQIR2V0VmFsdWUCQ1MGU3lzdGVtC0NvbGxlY3Rpb25zB0dlbmVyaWMETGlzdAZTdHJpbmcCY24DQWRkAmVuAmtyAmpwB1RvQXJyYXkDUlBHBkNsaWVudApHbG9iYWxWYXJzFXNfTG9jYWxpemF0aW9uTWFuYWdlchRfYWxsb3dlZExhbmd1YWdlS2V5cxFfYWxsb3dlZEF1ZGlvS2V5cwhHYW1lQ29yZRlBbGxvd2VkTGFuZ3VhZ2VFeGNlbFRhYmxlCGRhdGFEaWN0BXBhaXJzDExhbmd1YWdlTGlzdAtVbml0eUVuZ2luZQtBcHBsaWNhdGlvbgdNQVhfRlBTD3RhcmdldEZyYW1lUmF0ZQ9RdWFsaXR5U2V0dGluZ3MKdlN5bmNDb3VudApHYW1lT2JqZWN0BEZpbmQoVUlSb290L0Fib3ZlRGlhbG9nL0JldGFIaW50RGlhbG9nKENsb25lKQ1Mb2NhbGl6ZWRUZXh0BnR5cGVvZhZHZXRDb21wb25lbnRJbkNoaWxkcmVuB0JFVEFfV00EdGV4dAtWZXJzaW9uVGV4dCI8Y29sb3I9IzAwZTFmZj5Sb2JpblNSISB8IDwvY29sb3I+Dzxjb2xvcj0jMDBlMWZmPg1zX1ZlcnNpb25EYXRhF0dldFNlcnZlclBha1R5cGVWZXJzaW9uCDwvY29sb3I+BnhwY2FsbApJU19QQVRDSEVECQEBAAAAAAEWAAEAAAACAAAABgIAAAAADRQCABsAAAAAFQICAgYEAQAEBT4AFAICkQEAAAAVAgQCBgQAABQCAvMCAAAAFQIDABYCAAADAwEDAgMDAAQAAAAFAAAAAAAfDAIDAAIEAMAPAQL5BAAAAA8AAesFAAAADAEHAAYEAMAVAAICBgEAABUBAQIFBAgAFAIBXwkAAAAVAgMBBQQKABQCAV8JAAAAFQIDAQUECwAUAgFfCQAAABUCAwEFBAwAFAIBXwkAAAAVAgMBFAIBOQ0AAAAVAgIAFgIAAA4DBAMFAwYEAgQAwAMHAwgDCQQGBADAAwoDCwMMAw0DDgMPAAsAAAAGAAAAAAA/DAIDAAIEAMAPAQKsBAAAAA8AAaAFAAAAFAEAGwYAAAAVAQICBQMHAAQEPgAUAQGRCAAAABUBBAIGAwAAFAEB8wkAAAAVAQMCBQQKABQCAV8LAAAAFQIDAQUEDAAUAgFfCwAAABUCAwEFBA0AFAIBXwsAAAAVAgMBBQQOABQCAV8LAAAAFQIDARQCABsGAAAAFQICAgUEDwAEBT4AFAICkQgAAAAVAgQCBgQAABQCAvMJAAAAFQIDAgUFCgAUAwJfCwAAABUDAwEFBQwAFAMCXwsAAAAVAwMBBQUNABQDAl8LAAAAFQMDAQUFDgAUAwJfCwAAABUDAwEWAAEAEAMEAxADEQQCBADAAxIDEwMBAxQDAgMDAwoDCwMMAw0DDgMVABQAAAAHAAEAAAASDAIDAAIEAMAPAQLwBAAAAA8AAbkFAAAACQEAABUBAQIMAgcAAABgQAYDAAAVAgIEQQICABABBjIIAAAAPgL9/wIAAAAWAAEACQMEAxADFgQCBADAAxcDGAMZBAAAYEADGgAiAAAAAgAAAAAADAwAAwACBADABwEAygQAAAAQAQADBQAAAAwABwAGBADABAEAABABANAIAAAAFgABAAkDBAMbAxwEAgQAwAMdAx4DHwQGBADAAyAALAAAAAkAAAAAADcMAQMAAgQAwA8AAXYEAAAABQEFABUAAgIMBAgABxgAwA8DBBQJAAAATSwDAgwCCwAAAKBAFQICAhQAALEMAAAAFQADAgwBDgAAANBAEAEA1Q8AAAAMAQMAAgQAwA8AAXYEAAAABQEQABUAAgIMBAgABxgAwA8DBBQJAAAATSwDAgwCCwAAAKBAFQICAhQAALEMAAAAFQADAgUCEQAFAxIADAgIAAcYAMAPBwisEwAAAA8GByMUAAAAFAYGkRUAAAAVBgICBgQGAAUFFgA1AQIFEAEA1Q8AAAAWAAEAFwMEAxsDIQQCBADAAyIDIwMQAxEEBxgAwAMkAyUEAACgQAMmAycEAADQQAMoAykDKgMrAxIDLAMtAy4AMQAAAAMABQAAACQHAADKAAAAAFEABAAAAACABABoAQgAAMoAAAAADAACAAAAEEAJAQAACQIBABUAAwEMAAIAAAAQQAkBAgAJAgEAFQADAQcAACsDAAAAUgACAAEAAIAWAAEAAwABAAgAACsDAAAADAACAAAAEEAJAQMACQIBABUAAwEMAAIAAAAQQAkBBAAJAgEAFQADARYAAQAEAx0DLwQAABBAAzAAPwAAAAsAAAEAABVFAAAARAAAAEQBAQBEAgIARAMDAEQEBABKAAIARAUFAEQGBgBEBwcASgAFAEoAAABKAAYASgAEAEoAAwAMCAkAAACAQAYJBwAGCgAAFQgDARYAAQAKBgAGAQYCBgMGBAYFBgYGBwMvBAAAgEAIAAECAwQFBgcBAAAACA==").unwrap(),
|
||||||
haehhcpoapp: 0
|
haehhcpoapp: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,9 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
resources::GAME_RES,
|
resources::GAME_RES,
|
||||||
sr_tools::{AvatarJson, Position},
|
structs::{AvatarJson, Position},
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use scene_entity_info::Entity;
|
use scene_entity_info::Entity;
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
use crate::util::{self};
|
use crate::util::{self};
|
||||||
|
|
||||||
@ -117,10 +115,6 @@ pub async fn on_get_scene_map_info_cs_req(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref NEXT_SCENE_SAVE: Mutex<u64> = Mutex::new(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn on_scene_entity_move_cs_req(
|
pub async fn on_scene_entity_move_cs_req(
|
||||||
session: &mut PlayerSession,
|
session: &mut PlayerSession,
|
||||||
req: &SceneEntityMoveCsReq,
|
req: &SceneEntityMoveCsReq,
|
||||||
@ -131,14 +125,12 @@ pub async fn on_scene_entity_move_cs_req(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut timestamp = NEXT_SCENE_SAVE.lock().await;
|
if util::cur_timestamp_ms() <= session.next_scene_save {
|
||||||
|
|
||||||
if util::cur_timestamp_ms() <= *timestamp {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// save every 5 sec
|
// save every 5 sec
|
||||||
*timestamp = util::cur_timestamp_ms() + (5 * 1000);
|
session.next_scene_save = util::cur_timestamp_ms() + (5 * 1000);
|
||||||
|
|
||||||
for entity in &req.entity_motion_list {
|
for entity in &req.entity_motion_list {
|
||||||
if entity.entity_id != 0 {
|
if entity.entity_id != 0 {
|
||||||
@ -285,7 +277,7 @@ async fn load_scene(
|
|||||||
let entity_info = SceneEntityInfo {
|
let entity_info = SceneEntityInfo {
|
||||||
inst_id: prop.inst_id,
|
inst_id: prop.inst_id,
|
||||||
group_id: prop.group_id,
|
group_id: prop.group_id,
|
||||||
motion: Some(prop_position.to_motion()),
|
motion: Some(prop_position.into()),
|
||||||
entity: Some(Entity::Prop(ScenePropInfo {
|
entity: Some(Entity::Prop(ScenePropInfo {
|
||||||
prop_state: prop.prop_state,
|
prop_state: prop.prop_state,
|
||||||
prop_id: prop.prop_id,
|
prop_id: prop.prop_id,
|
||||||
@ -316,7 +308,7 @@ async fn load_scene(
|
|||||||
inst_id: npc.inst_id,
|
inst_id: npc.inst_id,
|
||||||
group_id: npc.group_id,
|
group_id: npc.group_id,
|
||||||
entity_id: npc_entity_id,
|
entity_id: npc_entity_id,
|
||||||
motion: Some(npc_position.to_motion()),
|
motion: Some(npc_position.into()),
|
||||||
entity: Some(Entity::Npc(SceneNpcInfo {
|
entity: Some(Entity::Npc(SceneNpcInfo {
|
||||||
npc_id: npc.npc_id,
|
npc_id: npc.npc_id,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -347,7 +339,7 @@ async fn load_scene(
|
|||||||
inst_id: monster.inst_id,
|
inst_id: monster.inst_id,
|
||||||
group_id: monster.group_id,
|
group_id: monster.group_id,
|
||||||
entity_id: monster_entity_id,
|
entity_id: monster_entity_id,
|
||||||
motion: Some(monster_position.to_motion()),
|
motion: Some(monster_position.into()),
|
||||||
entity: Some(Entity::NpcMonster(npc_monster)),
|
entity: Some(Entity::NpcMonster(npc_monster)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -190,6 +190,7 @@ trait_handler! {
|
|||||||
TakeOffEquipment;
|
TakeOffEquipment;
|
||||||
DressRelicAvatar;
|
DressRelicAvatar;
|
||||||
TakeOffRelic;
|
TakeOffRelic;
|
||||||
|
RankUpAvatar;
|
||||||
|
|
||||||
// Chat (dummy!)
|
// Chat (dummy!)
|
||||||
SendMsg;
|
SendMsg;
|
||||||
|
|||||||
@ -33,6 +33,7 @@ pub struct PlayerSession {
|
|||||||
pub shutdown_tx: watch::Sender<()>,
|
pub shutdown_tx: watch::Sender<()>,
|
||||||
pub shutdown_rx: watch::Receiver<()>,
|
pub shutdown_rx: watch::Receiver<()>,
|
||||||
pub json_data: OnceLock<FreesrData>,
|
pub json_data: OnceLock<FreesrData>,
|
||||||
|
pub next_scene_save: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerSession {
|
impl PlayerSession {
|
||||||
@ -48,6 +49,7 @@ impl PlayerSession {
|
|||||||
json_data: OnceLock::new(),
|
json_data: OnceLock::new(),
|
||||||
shutdown_rx,
|
shutdown_rx,
|
||||||
shutdown_tx,
|
shutdown_tx,
|
||||||
|
next_scene_save: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,8 +122,8 @@ impl PlayerSession {
|
|||||||
|
|
||||||
// clear relics & lightcones
|
// clear relics & lightcones
|
||||||
self.send(PlayerSyncScNotify {
|
self.send(PlayerSyncScNotify {
|
||||||
del_equipment_list: (2000..3500).collect(),
|
del_equipment_list: (2000..=3500).collect(),
|
||||||
del_relic_list: (1..2000).collect(),
|
del_relic_list: (1..=2000).collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -144,12 +146,8 @@ impl PlayerSession {
|
|||||||
|
|
||||||
// Sync new relics
|
// Sync new relics
|
||||||
self.send(PlayerSyncScNotify {
|
self.send(PlayerSyncScNotify {
|
||||||
relic_list: json.relics.iter().map(|v| v.to_relic_proto()).collect(),
|
relic_list: json.relics.iter().map(|v| v.into()).collect(),
|
||||||
equipment_list: json
|
equipment_list: json.lightcones.iter().map(|v| v.into()).collect(),
|
||||||
.lightcones
|
|
||||||
.iter()
|
|
||||||
.map(|v| v.to_equipment_proto())
|
|
||||||
.collect(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|||||||
@ -4,29 +4,29 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
# Framework
|
||||||
env_logger.workspace = true
|
tokio.workspace = true
|
||||||
|
|
||||||
tower-http = { workspace = true, features = ["cors"] }
|
tower-http = { workspace = true, features = ["cors"] }
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
axum-server.workspace = true
|
axum-server.workspace = true
|
||||||
|
reqwest.workspace = true
|
||||||
|
|
||||||
lazy_static.workspace = true
|
# JSON
|
||||||
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|
||||||
tokio.workspace = true
|
# Logging
|
||||||
tokio-util.workspace = true
|
|
||||||
|
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracing-futures.workspace = true
|
|
||||||
tracing-log.workspace = true
|
|
||||||
tracing-subscriber.workspace = true
|
|
||||||
tracing-bunyan-formatter.workspace = true
|
|
||||||
ansi_term.workspace = true
|
ansi_term.workspace = true
|
||||||
|
env_logger.workspace = true
|
||||||
|
|
||||||
|
# Encoding / Serialization
|
||||||
prost.workspace = true
|
prost.workspace = true
|
||||||
rbase64.workspace = true
|
rbase64.workspace = true
|
||||||
proto.workspace = true
|
|
||||||
|
# Error handling
|
||||||
|
anyhow.workspace = true
|
||||||
|
|
||||||
|
# Local
|
||||||
common.workspace = true
|
common.workspace = true
|
||||||
|
proto.workspace = true
|
||||||
|
|||||||
@ -1,3 +1 @@
|
|||||||
mod version_config;
|
pub mod version_config;
|
||||||
|
|
||||||
pub use version_config::INSTANCE as versions;
|
|
||||||
|
|||||||
@ -1,29 +1,127 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use anyhow::{Result, anyhow};
|
||||||
use serde::Deserialize;
|
use prost::Message as _;
|
||||||
use serde_json::from_str;
|
use proto::Gateserver;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
const DEFAULT_VERSIONS: &str = include_str!("../../versions.json");
|
const DEFAULT_VERSIONS: &str = include_str!("../../../versions.json");
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct VersionConfig {
|
pub struct VersionConfig {
|
||||||
pub asset_bundle_url: String,
|
pub asset_bundle_url: String,
|
||||||
pub ex_resource_url: String,
|
pub ex_resource_url: String,
|
||||||
pub lua_url: String,
|
pub lua_url: String,
|
||||||
// pub lua_version: String,
|
pub ifix_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
impl VersionConfig {
|
||||||
pub static ref INSTANCE: HashMap<String, VersionConfig> = {
|
const CNPROD_HOST: &str = "prod-gf-cn-dp01.bhsr.com";
|
||||||
let local_config = std::path::Path::new("versions.json");
|
const CNBETA_HOST: &str = "beta-release01-cn.bhsr.com";
|
||||||
let data = if local_config.exists() {
|
const OSPROD_HOST: &str = "prod-official-asia-dp01.starrails.com";
|
||||||
std::fs::read_to_string("versions.json").unwrap()
|
const OSBETA_HOST: &str = "beta-release01-asia.starrails.com";
|
||||||
} else {
|
|
||||||
std::fs::write("versions.json", DEFAULT_VERSIONS).unwrap();
|
const PROXY_HOST: &str = "proxy1.neonteam.dev"; // we used this because PS users usually have a proxy enabled
|
||||||
|
|
||||||
|
pub async fn load_hotfix() -> HashMap<String, VersionConfig> {
|
||||||
|
const CONFIG_PATH: &str = "versions.json";
|
||||||
|
|
||||||
|
let data = match fs::read_to_string(CONFIG_PATH).await {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(_) => {
|
||||||
|
fs::write(CONFIG_PATH, DEFAULT_VERSIONS)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create versions file");
|
||||||
DEFAULT_VERSIONS.to_string()
|
DEFAULT_VERSIONS.to_string()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
from_str(&data).unwrap()
|
match serde_json::from_str(&data) {
|
||||||
};
|
Ok(data) => data,
|
||||||
|
Err(_) => {
|
||||||
|
tracing::error!("malformed versions.json. replacing it with default one");
|
||||||
|
let _ = fs::write(CONFIG_PATH, DEFAULT_VERSIONS).await;
|
||||||
|
serde_json::from_str(DEFAULT_VERSIONS).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_hotfix(version: &str, dispatch_seed: &str) -> Result<VersionConfig> {
|
||||||
|
let (region, branch, _, _) = Self::parse_version_string(version)?;
|
||||||
|
|
||||||
|
let host = match (region.as_str(), branch.as_str()) {
|
||||||
|
("OS", "BETA") => Self::OSBETA_HOST,
|
||||||
|
("OS", "PROD") => Self::OSPROD_HOST,
|
||||||
|
("CN", "BETA") => Self::CNBETA_HOST,
|
||||||
|
("CN", "PROD") => Self::CNPROD_HOST,
|
||||||
|
// TODO: Support more host, or use query_dispatch result to determine the urls
|
||||||
|
_ => Self::OSBETA_HOST,
|
||||||
|
};
|
||||||
|
|
||||||
|
let url = format!(
|
||||||
|
"https://{}/{host}/query_gateway?version={}&platform_type=1&language_type=3&dispatch_seed={}&channel_id=1&sub_channel_id=1&is_need_url=1",
|
||||||
|
Self::PROXY_HOST,
|
||||||
|
version,
|
||||||
|
dispatch_seed
|
||||||
|
);
|
||||||
|
|
||||||
|
tracing::info!("fetching hotfix: {url}");
|
||||||
|
|
||||||
|
let res = reqwest::get(url).await?.text().await?;
|
||||||
|
|
||||||
|
tracing::info!("raw gateway response: {}", res);
|
||||||
|
|
||||||
|
let bytes = rbase64::decode(&res)?;
|
||||||
|
let decoded = Gateserver::decode(bytes.as_slice())?;
|
||||||
|
|
||||||
|
if decoded.retcode != 0 || res.is_empty() {
|
||||||
|
return Err(anyhow::format_err!(
|
||||||
|
"gateway result code: {} message: {}",
|
||||||
|
decoded.retcode,
|
||||||
|
decoded.msg
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("{:#?}", decoded);
|
||||||
|
|
||||||
|
Ok(VersionConfig {
|
||||||
|
asset_bundle_url: decoded.asset_bundle_url,
|
||||||
|
ex_resource_url: decoded.ex_resource_url,
|
||||||
|
lua_url: decoded.lua_url,
|
||||||
|
ifix_url: decoded.ifix_url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_version_string(s: &str) -> Result<(String, String, String, String)> {
|
||||||
|
const BRANCHES: [&str; 7] = ["PREbeta", "BETA", "PROD", "DEV", "PRE", "GM", "CECREATION"];
|
||||||
|
const OS: [&str; 3] = ["Android", "Win", "iOS"];
|
||||||
|
|
||||||
|
let region = s
|
||||||
|
.get(0..2)
|
||||||
|
.ok_or_else(|| anyhow!("version parse failed. reason: invalid region url: {s}"))?;
|
||||||
|
let after_region = s.get(2..).unwrap_or(s);
|
||||||
|
|
||||||
|
let branch = BRANCHES
|
||||||
|
.iter()
|
||||||
|
.find(|&&b| after_region.starts_with(b))
|
||||||
|
.map(|&b| b.to_string())
|
||||||
|
.ok_or_else(|| anyhow!("version parse failed. reason: invalid branch url: {s}"))?;
|
||||||
|
|
||||||
|
let branch_len = branch.len();
|
||||||
|
let after_branch = after_region.get(branch_len..).unwrap_or(after_region);
|
||||||
|
|
||||||
|
let os = OS
|
||||||
|
.iter()
|
||||||
|
.find(|&&o| after_branch.starts_with(o))
|
||||||
|
.map(|&o| o.to_string())
|
||||||
|
.ok_or_else(|| anyhow!("version parse failed. reason: invalid os url: {s}"))?;
|
||||||
|
|
||||||
|
let os_len = os.len();
|
||||||
|
let version = after_branch
|
||||||
|
.get(os_len..)
|
||||||
|
.ok_or_else(|| anyhow!("version parse failed. reason: invalid version url: {s}"))?;
|
||||||
|
|
||||||
|
Ok((region.to_string(), branch, os, version.to_string()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
sdkserver/src/lib.rs
Normal file
109
sdkserver/src/lib.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::Router;
|
||||||
|
use axum::http::Method;
|
||||||
|
use axum::http::header::CONTENT_TYPE;
|
||||||
|
use axum::routing::{get, post};
|
||||||
|
use config::version_config::VersionConfig;
|
||||||
|
use services::{auth, dispatch, errors, sr_tools};
|
||||||
|
use tokio::fs;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
|
use tracing::Level;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod services;
|
||||||
|
|
||||||
|
const PORT: u16 = 21000;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {
|
||||||
|
hotfix_map: HashMap<String, VersionConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
async fn get_or_insert_hotfix(&mut self, version: &str, dispatch_seed: &str) -> &VersionConfig {
|
||||||
|
if self.hotfix_map.contains_key(version) {
|
||||||
|
return &self.hotfix_map[version];
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"trying to fetch hotfix for version {version} with dispatch seed {dispatch_seed}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let hotfix = match VersionConfig::fetch_hotfix(version, dispatch_seed).await {
|
||||||
|
Ok(hotfix) => hotfix,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("failed to fetch hotfix. reason: {err}");
|
||||||
|
VersionConfig::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.hotfix_map.insert(version.to_string(), hotfix);
|
||||||
|
|
||||||
|
if let Ok(serialized) = serde_json::to_string_pretty(&self.hotfix_map) {
|
||||||
|
let _ = fs::write("versions.json", serialized).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
&self.hotfix_map[version]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_sdkserver() -> Result<()> {
|
||||||
|
let span = tracing::span!(Level::DEBUG, "main");
|
||||||
|
let _ = span.enter();
|
||||||
|
let hotfix_map = VersionConfig::load_hotfix().await;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"loaded {} hotfix versions. supported versions: {:?}",
|
||||||
|
hotfix_map.len(),
|
||||||
|
hotfix_map.keys()
|
||||||
|
);
|
||||||
|
|
||||||
|
let state = Arc::new(RwLock::new(AppState { hotfix_map }));
|
||||||
|
|
||||||
|
let router = Router::new()
|
||||||
|
.route(
|
||||||
|
dispatch::QUERY_DISPATCH_ENDPOINT,
|
||||||
|
get(dispatch::query_dispatch),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
dispatch::QUERY_GATEWAY_ENDPOINT,
|
||||||
|
get(dispatch::query_gateway),
|
||||||
|
)
|
||||||
|
.route(auth::RISKY_API_CHECK_ENDPOINT, post(auth::risky_api_check))
|
||||||
|
.route(
|
||||||
|
auth::LOGIN_WITH_PASSWORD_ENDPOINT,
|
||||||
|
post(auth::login_with_password),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
auth::LOGIN_WITH_SESSION_TOKEN_ENDPOINT,
|
||||||
|
post(auth::login_with_session_token),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
auth::GRANTER_LOGIN_VERIFICATION_ENDPOINT,
|
||||||
|
post(auth::granter_login_verification),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
sr_tools::SRTOOLS_UPLOAD_ENDPOINT,
|
||||||
|
post(sr_tools::sr_tool_save),
|
||||||
|
)
|
||||||
|
.layer(
|
||||||
|
CorsLayer::new()
|
||||||
|
.allow_origin(Any)
|
||||||
|
.allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE])
|
||||||
|
.allow_headers([CONTENT_TYPE]),
|
||||||
|
)
|
||||||
|
.with_state(state)
|
||||||
|
.fallback(errors::not_found);
|
||||||
|
|
||||||
|
let addr = format!("0.0.0.0:{PORT}");
|
||||||
|
let server = axum_server::bind(addr.parse()?);
|
||||||
|
|
||||||
|
tracing::info!("sdkserver is listening at {addr}");
|
||||||
|
server.serve(router.into_make_service()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@ -1,29 +0,0 @@
|
|||||||
#[macro_export]
|
|
||||||
macro_rules! log_error {
|
|
||||||
($e:expr) => {
|
|
||||||
if let Err(e) = $e {
|
|
||||||
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($context:expr, $e:expr $(,)?) => {
|
|
||||||
if let Err(e) = $e {
|
|
||||||
let e = format!("{:?}", ::anyhow::anyhow!(e).context($context));
|
|
||||||
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($ok_context:expr, $err_context:expr, $e:expr $(,)?) => {
|
|
||||||
if let Err(e) = $e {
|
|
||||||
let e = format!("{:?}", ::anyhow::anyhow!(e).context($err_context));
|
|
||||||
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
|
|
||||||
} else {
|
|
||||||
tracing::info!($ok_context);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_tracing() {
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
ansi_term::enable_ansi_support().unwrap();
|
|
||||||
|
|
||||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
|
||||||
}
|
|
||||||
@ -1,65 +1,14 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use axum::Router;
|
|
||||||
use axum::http::Method;
|
|
||||||
use axum::http::header::CONTENT_TYPE;
|
|
||||||
use axum::routing::{get, post};
|
|
||||||
use logging::init_tracing;
|
|
||||||
use services::{auth, dispatch, errors, sr_tools};
|
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
|
||||||
use tracing::Level;
|
|
||||||
|
|
||||||
mod config;
|
pub fn init_tracing() {
|
||||||
mod logging;
|
#[cfg(target_os = "windows")]
|
||||||
mod services;
|
ansi_term::enable_ansi_support().unwrap();
|
||||||
|
|
||||||
const PORT: u16 = 21000;
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
init_tracing();
|
init_tracing();
|
||||||
|
sdkserver::start_sdkserver().await
|
||||||
let span = tracing::span!(Level::DEBUG, "main");
|
|
||||||
let _ = span.enter();
|
|
||||||
|
|
||||||
let router = Router::new()
|
|
||||||
.route(
|
|
||||||
dispatch::QUERY_DISPATCH_ENDPOINT,
|
|
||||||
get(dispatch::query_dispatch),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
dispatch::QUERY_GATEWAY_ENDPOINT,
|
|
||||||
get(dispatch::query_gateway),
|
|
||||||
)
|
|
||||||
.route(auth::RISKY_API_CHECK_ENDPOINT, post(auth::risky_api_check))
|
|
||||||
.route(
|
|
||||||
auth::LOGIN_WITH_PASSWORD_ENDPOINT,
|
|
||||||
post(auth::login_with_password),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
auth::LOGIN_WITH_SESSION_TOKEN_ENDPOINT,
|
|
||||||
post(auth::login_with_session_token),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
auth::GRANTER_LOGIN_VERIFICATION_ENDPOINT,
|
|
||||||
post(auth::granter_login_verification),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
sr_tools::SRTOOLS_UPLOAD_ENDPOINT,
|
|
||||||
post(sr_tools::sr_tool_save),
|
|
||||||
)
|
|
||||||
.layer(
|
|
||||||
CorsLayer::new()
|
|
||||||
.allow_origin(Any)
|
|
||||||
.allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE])
|
|
||||||
.allow_headers([CONTENT_TYPE]),
|
|
||||||
)
|
|
||||||
.fallback(errors::not_found);
|
|
||||||
|
|
||||||
let addr = format!("0.0.0.0:{PORT}");
|
|
||||||
let server = axum_server::bind(addr.parse()?);
|
|
||||||
|
|
||||||
tracing::info!("sdkserver is listening at {addr}");
|
|
||||||
server.serve(router.into_make_service()).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
use crate::config::versions;
|
use std::sync::Arc;
|
||||||
use axum::extract::Query;
|
|
||||||
|
use crate::AppState;
|
||||||
|
use axum::extract::{Query, State};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use proto::{DispatchRegionData, Gateserver, RegionEntry};
|
use proto::{DispatchRegionData, Gateserver, RegionEntry};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
pub const QUERY_DISPATCH_ENDPOINT: &str = "/query_dispatch";
|
pub const QUERY_DISPATCH_ENDPOINT: &str = "/query_dispatch";
|
||||||
pub const QUERY_GATEWAY_ENDPOINT: &str = "/query_gateway";
|
pub const QUERY_GATEWAY_ENDPOINT: &str = "/query_gateway";
|
||||||
@ -14,7 +18,7 @@ pub async fn query_dispatch() -> String {
|
|||||||
region_list: vec![RegionEntry {
|
region_list: vec![RegionEntry {
|
||||||
name: String::from("RobinSR"),
|
name: String::from("RobinSR"),
|
||||||
title: String::from("RobinSR"),
|
title: String::from("RobinSR"),
|
||||||
env_type: String::from("21"),
|
env_type: String::from("22"),
|
||||||
dispatch_url: String::from("http://127.0.0.1:21000/query_gateway"),
|
dispatch_url: String::from("http://127.0.0.1:21000/query_gateway"),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
@ -30,12 +34,20 @@ pub async fn query_dispatch() -> String {
|
|||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct QueryGatewayParameters {
|
pub struct QueryGatewayParameters {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
pub dispatch_seed: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[instrument(skip(state))]
|
||||||
pub async fn query_gateway(parameters: Query<QueryGatewayParameters>) -> String {
|
pub async fn query_gateway(
|
||||||
let rsp = if let Some(config) = versions.get(¶meters.version) {
|
State(state): State<Arc<RwLock<AppState>>>,
|
||||||
Gateserver {
|
parameters: Query<QueryGatewayParameters>,
|
||||||
|
) -> String {
|
||||||
|
let mut lock = state.write().await;
|
||||||
|
let config = lock
|
||||||
|
.get_or_insert_hotfix(¶meters.version, ¶meters.dispatch_seed)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let rsp = Gateserver {
|
||||||
retcode: 0,
|
retcode: 0,
|
||||||
ip: String::from("127.0.0.1"),
|
ip: String::from("127.0.0.1"),
|
||||||
port: 23301,
|
port: 23301,
|
||||||
@ -54,13 +66,6 @@ pub async fn query_gateway(parameters: Query<QueryGatewayParameters>) -> String
|
|||||||
enable_cdn_ipv6: 1,
|
enable_cdn_ipv6: 1,
|
||||||
enable_save_replay_file: true,
|
enable_save_replay_file: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Gateserver {
|
|
||||||
retcode: 9,
|
|
||||||
login_white_msg: format!("forbidden version: {} or invalid bind", parameters.version),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut buff = Vec::new();
|
let mut buff = Vec::new();
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use common::sr_tools::FreesrData;
|
use common::sr_tools::FreesrData;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -30,15 +32,21 @@ pub async fn sr_tool_save(Json(json): Json<SrToolDataReq>) -> Json<SrToolDataRsp
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Json(SrToolDataRsp {
|
return Json(SrToolDataRsp {
|
||||||
message: format!("malformed json: {}", err),
|
message: format!("malformed json: {}", err),
|
||||||
status: 200,
|
status: 500,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = fs::write("freesr-data.json", json).await {
|
let path = Path::new("freesr-data.json");
|
||||||
|
let env = std::env::current_dir();
|
||||||
|
|
||||||
|
if let Err(err) = fs::write(&path, json).await {
|
||||||
return Json(SrToolDataRsp {
|
return Json(SrToolDataRsp {
|
||||||
message: format!("failed to write freesr-data.json: {}", err),
|
message: format!(
|
||||||
status: 200,
|
"failed to write freesr-data.json: {} at path: {:#?} env: {:#?}",
|
||||||
|
err, path, env
|
||||||
|
),
|
||||||
|
status: 500,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"OSBETAWin3.1.51": {
|
|
||||||
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966",
|
|
||||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
|
|
||||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
|
|
||||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,5 +4,55 @@
|
|||||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
|
||||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
|
||||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
||||||
|
},
|
||||||
|
"CNBETAWin3.1.51": {
|
||||||
|
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966",
|
||||||
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
|
||||||
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
|
||||||
|
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
||||||
|
},
|
||||||
|
"CNBETAAndroid3.1.51": {
|
||||||
|
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966",
|
||||||
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
|
||||||
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
|
||||||
|
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
||||||
|
},
|
||||||
|
|
||||||
|
"OSBETAWin3.1.52": {
|
||||||
|
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9614772_b674c1e08556",
|
||||||
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9614804_4a927001828b",
|
||||||
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9615588_bd4aff54bc1a",
|
||||||
|
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
||||||
|
},
|
||||||
|
"CNBETAWin3.1.52": {
|
||||||
|
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9614772_b674c1e08556",
|
||||||
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9614804_4a927001828b",
|
||||||
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9615588_bd4aff54bc1a",
|
||||||
|
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
||||||
|
},
|
||||||
|
"CNBETAAndroid3.1.52": {
|
||||||
|
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9614772_b674c1e08556",
|
||||||
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9614804_4a927001828b",
|
||||||
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9615588_bd4aff54bc1a",
|
||||||
|
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
||||||
|
},
|
||||||
|
|
||||||
|
"CNBETAWin3.1.53": {
|
||||||
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_9697258_e86620fd557e",
|
||||||
|
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_9706936_f0d9c0308622",
|
||||||
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_9685341_7378b1f100ee",
|
||||||
|
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253"
|
||||||
|
},
|
||||||
|
"OSBETAWin3.1.53": {
|
||||||
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_9697258_e86620fd557e",
|
||||||
|
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_9706936_f0d9c0308622",
|
||||||
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_9685341_7378b1f100ee",
|
||||||
|
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253"
|
||||||
|
},
|
||||||
|
"CNBETAAndroid3.1.53": {
|
||||||
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_9697258_e86620fd557e",
|
||||||
|
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_9706936_f0d9c0308622",
|
||||||
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_9685341_7378b1f100ee",
|
||||||
|
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user