Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
243df2b03e
|
|||
|
592ad690d9
|
|||
| 99e1e09707 | |||
| a2e81c40fc | |||
| 701a78e9dc | |||
| 6aa7670ca8 | |||
| 80754bbfea | |||
| 137db60000 | |||
| 676bd63dac | |||
|
5e0f2a9673
|
|||
| 26956e3193 | |||
|
cc389bd368
|
|||
| c697c799d6 | |||
|
b34e7af824
|
|||
|
9d856e76c8
|
|||
|
5e5a90cb50
|
|||
|
0208899832
|
|||
|
5ad0ac3556
|
|||
|
7807973f93
|
|||
|
c2b3bb4dd9
|
|||
|
779efd2771
|
|||
|
14692dc78e
|
|||
|
56bdb1abc1
|
|||
|
f1ef8d8da2
|
|||
| 77f4bdf686 | |||
| aed52a523e | |||
| 14dd31c11f | |||
| 8c0d4fdc34 | |||
| f6717b3157 | |||
| bd40dc058f | |||
| 8d8686692e | |||
| 04f517daed | |||
| 6e72b9b509 | |||
| 2d995ef7d0 | |||
| 42545bbc53 | |||
| 81a783a078 | |||
| 0cb4138562 | |||
| d1c8eeaa52 | |||
| ac5ef9706f | |||
| 284e5cd48e | |||
| d14bc79fcc | |||
| 90c2f332a5 | |||
| e69e3c4e61 | |||
| 38ee98cea2 | |||
| 74455e23f8 | |||
| 6c5ba8b6e4 |
@@ -0,0 +1,109 @@
|
|||||||
|
name: Prebuilt release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
releases: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prebuilt:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install nightly Rust
|
||||||
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
|
with:
|
||||||
|
targets: x86_64-pc-windows-gnu
|
||||||
|
|
||||||
|
- name: Install native build tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y mingw-w64 protobuf-compiler zip unzip jq
|
||||||
|
|
||||||
|
- name: Build release binaries
|
||||||
|
env:
|
||||||
|
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc
|
||||||
|
run: cargo build --release --target x86_64-pc-windows-gnu --bin gameserver --bin sdkserver
|
||||||
|
|
||||||
|
- name: Package prebuilt release
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
rm -rf dist
|
||||||
|
mkdir -p dist
|
||||||
|
|
||||||
|
cp target/x86_64-pc-windows-gnu/release/gameserver.exe dist/gameserver.exe
|
||||||
|
cp target/x86_64-pc-windows-gnu/release/sdkserver.exe dist/sdkserver.exe
|
||||||
|
cp persistent res.json freesr-data.json versions.json dist/
|
||||||
|
|
||||||
|
for file in gameserver.exe sdkserver.exe persistent res.json freesr-data.json versions.json; do
|
||||||
|
test -f "dist/${file}"
|
||||||
|
done
|
||||||
|
|
||||||
|
(
|
||||||
|
cd dist
|
||||||
|
zip -9 -q ../prebuilt-win64.zip \
|
||||||
|
gameserver.exe \
|
||||||
|
sdkserver.exe \
|
||||||
|
persistent \
|
||||||
|
res.json \
|
||||||
|
freesr-data.json \
|
||||||
|
versions.json
|
||||||
|
)
|
||||||
|
|
||||||
|
unzip -l prebuilt-win64.zip
|
||||||
|
|
||||||
|
- name: Publish prebuilt release
|
||||||
|
env:
|
||||||
|
GITEA_API_URL: ${{ gitea.server_url }}/api/v1
|
||||||
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
|
GITEA_SHA: ${{ gitea.sha }}
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
api="${GITEA_API_URL}"
|
||||||
|
repo="${GITEA_REPOSITORY}"
|
||||||
|
tag="prebuilt"
|
||||||
|
auth=(-H "Authorization: token ${GITEA_TOKEN}" -H "Accept: application/json")
|
||||||
|
|
||||||
|
release_status="$(curl -sS "${auth[@]}" -o release.json -w '%{http_code}' \
|
||||||
|
"${api}/repos/${repo}/releases/tags/${tag}" || true)"
|
||||||
|
|
||||||
|
case "${release_status}" in
|
||||||
|
200)
|
||||||
|
;;
|
||||||
|
404)
|
||||||
|
curl -fsS -X POST "${auth[@]}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\":\"${tag}\",\"target_commitish\":\"${GITEA_SHA}\",\"name\":\"${tag}\",\"body\":\"Automated prebuilt binaries for ${GITEA_SHA}\",\"draft\":false,\"prerelease\":false}" \
|
||||||
|
"${api}/repos/${repo}/releases" > release.json
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
cat release.json
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
release_id="$(jq -er '.id' release.json)"
|
||||||
|
assets="$(curl -fsS "${auth[@]}" \
|
||||||
|
"${api}/repos/${repo}/releases/${release_id}/assets")"
|
||||||
|
|
||||||
|
while read -r asset_id; do
|
||||||
|
test -z "${asset_id}" || curl -fsS -X DELETE "${auth[@]}" \
|
||||||
|
"${api}/repos/${repo}/releases/${release_id}/assets/${asset_id}"
|
||||||
|
done < <(printf '%s' "${assets}" | jq -r '.[] | select(.name == "prebuilt-win64.zip") | .id')
|
||||||
|
|
||||||
|
curl -fsS -X POST "${auth[@]}" \
|
||||||
|
-F "attachment=@prebuilt-win64.zip;type=application/zip" \
|
||||||
|
"${api}/repos/${repo}/releases/${release_id}/assets?name=prebuilt-win64.zip"
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
target/
|
target/
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
proto/StarRail.proto
|
|
||||||
/prebuild
|
/prebuild
|
||||||
/assets
|
/assets
|
||||||
.vscode
|
.vscode
|
||||||
|
.prebuilt
|
||||||
+32
-20
@@ -7,52 +7,64 @@ version = "0.1.0"
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# Framework
|
# Framework
|
||||||
tokio = { version = "1.36.0", features = ["full"] }
|
tokio = { version = "1.52.1", features = ["full"] }
|
||||||
axum = "0.8.1"
|
axum = "0.8.9"
|
||||||
axum-server = "0.7.1"
|
axum-server = "0.8.0"
|
||||||
tower-http = "0.6.2"
|
tower-http = "0.6.8"
|
||||||
|
reqwest = { version = "0.13.3", default-features = false, features = [
|
||||||
|
"json",
|
||||||
|
"multipart",
|
||||||
|
"cookies",
|
||||||
|
"gzip",
|
||||||
|
"brotli",
|
||||||
|
"deflate",
|
||||||
|
"rustls",
|
||||||
|
"charset",
|
||||||
|
"http2",
|
||||||
|
"system-proxy",
|
||||||
|
] }
|
||||||
|
|
||||||
# JSON
|
# JSON
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.114"
|
serde_json = "1.0.149"
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.44"
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.10"
|
||||||
|
|
||||||
# Cryptography
|
# Cryptography
|
||||||
rsa = { version = "0.9.6", features = [
|
rsa = { version = "0.9.10", features = [
|
||||||
"sha1",
|
"sha1",
|
||||||
"nightly",
|
"nightly",
|
||||||
"pkcs5",
|
"pkcs5",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
] }
|
] }
|
||||||
rand = "0.9.0"
|
rand = "0.10.1"
|
||||||
|
|
||||||
# Serialization
|
# Serialization
|
||||||
prost = "0.13.5"
|
prost = "0.14.3"
|
||||||
prost-types = "0.13.5"
|
prost-types = "0.14.3"
|
||||||
prost-build = "0.13.5"
|
prost-build = "0.14.3"
|
||||||
rbase64 = "2.0.3"
|
rbase64 = "2.0.3"
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
paste = "1.0.14"
|
paste = "1.0.15"
|
||||||
notify = "8.0.0"
|
notify = "8.2.0"
|
||||||
notify-debouncer-mini = "0.6.0"
|
notify-debouncer-mini = "0.7.0"
|
||||||
anyhow = "1.0.81"
|
anyhow = "1.0.102"
|
||||||
|
|
||||||
# Local
|
# Local
|
||||||
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/"}
|
sdkserver = { path = "sdkserver/" }
|
||||||
gameserver = { path = "gameserver/"}
|
gameserver = { path = "gameserver/" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true # Automatically strip symbols from the binary.
|
|
||||||
opt-level = "z" # Optimize for size.
|
opt-level = "z" # Optimize for size.
|
||||||
|
strip = true # Automatically strip symbols from the binary.
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
# Supported Version: 3.1.5x
|
# Supported Version: 4.4.5x
|
||||||
|
|
||||||
Run the game by clicking run.bat file.
|
Run the game by clicking run.bat file.
|
||||||
|
|
||||||
Tool website: [https://srtools.pages.dev](https://srtools.pages.dev)
|
Tool website: [https://srtools.neonteam.dev](https://srtools.neonteam.dev)
|
||||||
|
|
||||||
Start battle by entering any calyx in the map, DON'T ATTACK THE ENEMIES, IT WON'T WORK (maybe)
|
Start battle by entering any calyx in the map, DON'T ATTACK THE ENEMIES, IT WON'T WORK (maybe)
|
||||||
|
|
||||||
Some scenes might not loaded properly. If you stuck at loading screen, remove `persistent` file.
|
Some scenes might not loaded properly. If you stuck at loading screen, remove `persistent` file.
|
||||||
|
|
||||||
# RobinSR
|
# RobinSR
|
||||||
|
|
||||||
Original:
|
Original:
|
||||||
|
|
||||||
[https://git.xeondev.com/reversedrooms/RobinSR](https://git.xeondev.com/reversedrooms/RobinSR)
|
[https://git.xeondev.com/reversedrooms/RobinSR](https://git.xeondev.com/reversedrooms/RobinSR)
|
||||||
@@ -32,4 +34,4 @@ rustup default nightly
|
|||||||
|
|
||||||
To begin using the server, you need to run both the SDK server and the game server. Just run the `run.bat` file.
|
To begin using the server, you need to run both the SDK server and the game server. Just run the `run.bat` file.
|
||||||
|
|
||||||
# Special thanks for keiracoder for updating the repo 🔥
|
# Special thanks to keiracoder for updating the repo! 🔥
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ serde.workspace = true
|
|||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
|||||||
@@ -114,6 +114,9 @@ pub struct SceneData {
|
|||||||
pub props: Vec<ScenePropInfo>,
|
pub props: Vec<ScenePropInfo>,
|
||||||
pub monsters: Vec<SceneMonsterInfo>,
|
pub monsters: Vec<SceneMonsterInfo>,
|
||||||
pub teleports: HashMap<u32, TeleportInfo>,
|
pub teleports: HashMap<u32, TeleportInfo>,
|
||||||
|
pub finished_sub_missions: Vec<u32>,
|
||||||
|
pub finished_main_missions: Vec<u32>,
|
||||||
|
pub chests: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -123,6 +126,8 @@ pub struct LevelOutputConfig {
|
|||||||
pub scenes: HashMap<u32, SceneData>,
|
pub scenes: HashMap<u32, SceneData>,
|
||||||
pub plane_type: u32,
|
pub plane_type: u32,
|
||||||
pub world_id: u32,
|
pub world_id: u32,
|
||||||
|
pub sections: Vec<u32>,
|
||||||
|
pub saved_values: HashMap<String, i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@@ -138,7 +143,8 @@ pub struct JsonConfig {
|
|||||||
/// `entryid` -> `P[planeId]_F[floorId]` -> `groupId`
|
/// `entryid` -> `P[planeId]_F[floorId]` -> `groupId`
|
||||||
pub level_output_configs: HashMap<u32, HashMap<String, LevelOutputConfig>>,
|
pub level_output_configs: HashMap<u32, HashMap<String, LevelOutputConfig>>,
|
||||||
pub avatar_configs: HashMap<u32, AvatarConfig>,
|
pub avatar_configs: HashMap<u32, AvatarConfig>,
|
||||||
pub map_default_entrance_map: HashMap<u32, u32>
|
pub map_default_entrance_map: HashMap<u32, u32>,
|
||||||
|
pub relic_avatar_recommend: HashMap<u32, Vec<u32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static GAME_RES: LazyLock<JsonConfig> = LazyLock::new(|| {
|
pub static GAME_RES: LazyLock<JsonConfig> = LazyLock::new(|| {
|
||||||
|
|||||||
+52
-121
@@ -1,4 +1,4 @@
|
|||||||
use proto::{Avatar, AvatarSkillTree, MultiPathAvatarTypeInfo};
|
use proto::{Avatar, AvatarPathData};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
@@ -31,133 +31,43 @@ pub struct FreesrData {
|
|||||||
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)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
// pub game_language: AllowedLanguages,
|
pub enable_sw_global: Option<bool>,
|
||||||
// #[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
// pub voice_langauge: AllowedLanguages,
|
pub enable_castorice_global: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FreesrData {
|
impl FreesrData {
|
||||||
pub fn get_avatar_proto(&self, avatar_id: u32) -> Option<Avatar> {
|
pub fn get_avatar_proto(&self, avatar_id: u32, mc_id: u32, march_id: u32) -> Option<Avatar> {
|
||||||
let avatar = self.avatars.get(&avatar_id)?;
|
let avatar = self.avatars.get(&avatar_id)?;
|
||||||
let lightcone = self.lightcones.iter().find(|l| l.equip_avatar == avatar_id);
|
let lightcone = self.lightcones.iter().find(|l| l.equip_avatar == avatar_id);
|
||||||
let relics = self.relics.iter().filter(|r| r.equip_avatar == avatar_id);
|
Some(avatar.to_avatar_proto(lightcone, mc_id, march_id))
|
||||||
|
|
||||||
// TODO: HARDCODED
|
|
||||||
let base_avatar_id = if avatar.avatar_id > 8000 {
|
|
||||||
8001
|
|
||||||
} else if avatar.avatar_id == 1001 || avatar.avatar_id == 1224 {
|
|
||||||
1001
|
|
||||||
} else {
|
|
||||||
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_avatar_multipath_proto(&self, avatar_id: u32) -> Option<MultiPathAvatarTypeInfo> {
|
pub fn get_avatar_path_data_proto(&self, avatar_id: u32) -> Option<AvatarPathData> {
|
||||||
let avatar = self.avatars.get(&avatar_id)?;
|
let avatar = self.avatars.get(&avatar_id)?;
|
||||||
let mp_type = MultiPathAvatar::from(avatar_id);
|
|
||||||
|
|
||||||
if mp_type == MultiPathAvatar::Unk {
|
Some(
|
||||||
return None;
|
avatar.to_avatar_path_data_proto(
|
||||||
}
|
self.lightcones.iter().find(|v| v.equip_avatar == avatar_id),
|
||||||
|
self.relics
|
||||||
Some(MultiPathAvatarTypeInfo {
|
|
||||||
avatar_id: mp_type as i32,
|
|
||||||
rank: avatar.data.rank,
|
|
||||||
equip_relic_list: self
|
|
||||||
.relics
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|relic| relic.equip_avatar == mp_type as u32)
|
.filter(|relic| relic.equip_avatar == avatar_id)
|
||||||
.map(|relic| relic.into())
|
|
||||||
.collect(),
|
.collect(),
|
||||||
skilltree_list: avatar
|
),
|
||||||
.data
|
)
|
||||||
.skills
|
|
||||||
.iter()
|
|
||||||
.map(|(point_id, level)| AvatarSkillTree {
|
|
||||||
point_id: *point_id,
|
|
||||||
level: *level,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
path_equipment_id: self
|
|
||||||
.lightcones
|
|
||||||
.iter()
|
|
||||||
.find(|v| v.equip_avatar == mp_type as u32)
|
|
||||||
.map(|v| v.get_unique_id())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
dressed_skin_id: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_multi_path_info(&self) -> Vec<MultiPathAvatarTypeInfo> {
|
|
||||||
MultiPathAvatar::to_vec()
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|mp_type| {
|
|
||||||
if mp_type.is_mc() && mp_type.get_gender() != self.main_character.get_gender() {
|
|
||||||
return Option::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let avatar_info = self.avatars.get(&((mp_type) as u32))?;
|
|
||||||
Some(MultiPathAvatarTypeInfo {
|
|
||||||
avatar_id: mp_type as i32,
|
|
||||||
rank: avatar_info.data.rank,
|
|
||||||
equip_relic_list: self
|
|
||||||
.relics
|
|
||||||
.iter()
|
|
||||||
.filter(|relic| relic.equip_avatar == mp_type as u32)
|
|
||||||
.map(|relic| relic.into())
|
|
||||||
.collect(),
|
|
||||||
skilltree_list: avatar_info
|
|
||||||
.data
|
|
||||||
.skills
|
|
||||||
.iter()
|
|
||||||
.map(|(point_id, level)| AvatarSkillTree {
|
|
||||||
point_id: *point_id,
|
|
||||||
level: *level,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
path_equipment_id: self
|
|
||||||
.lightcones
|
|
||||||
.iter()
|
|
||||||
.find(|v| v.equip_avatar == mp_type as u32)
|
|
||||||
.map(|v| v.get_unique_id())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
dressed_skin_id: 0,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FreesrData {
|
impl FreesrData {
|
||||||
pub async fn load() -> Self {
|
pub async fn load() -> anyhow::Result<Self> {
|
||||||
let mut freesr_data: 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_err(|e| anyhow::anyhow!("failed to read freesr-data.json: {e}"))
|
||||||
|
.and_then(|v| {
|
||||||
serde_json::from_str::<FreesrData>(&v)
|
serde_json::from_str::<FreesrData>(&v)
|
||||||
.expect("freesr-data.json is broken, pls redownload")
|
.map_err(|e| anyhow::anyhow!("freesr-data.json is broken: {e}, pls redownload"))
|
||||||
})
|
})?;
|
||||||
.expect("failed to read freesr-data.json");
|
|
||||||
|
|
||||||
let persistent: Persistent = serde_json::from_str(
|
let persistent: Persistent = serde_json::from_str(
|
||||||
&tokio::fs::read_to_string("persistent")
|
&tokio::fs::read_to_string("persistent")
|
||||||
@@ -171,6 +81,8 @@ impl FreesrData {
|
|||||||
freesr_data.scene = persistent.scene;
|
freesr_data.scene = persistent.scene;
|
||||||
freesr_data.main_character = persistent.main_character;
|
freesr_data.main_character = persistent.main_character;
|
||||||
freesr_data.march_type = persistent.march_type;
|
freesr_data.march_type = persistent.march_type;
|
||||||
|
freesr_data.enable_sw_global = persistent.enable_sw_global;
|
||||||
|
freesr_data.enable_castorice_global = persistent.enable_castorice_global;
|
||||||
// freesr_data.game_language = persistent.game_language;
|
// freesr_data.game_language = persistent.game_language;
|
||||||
// freesr_data.voice_langauge = persistent.voice_language;
|
// freesr_data.voice_langauge = persistent.voice_language;
|
||||||
|
|
||||||
@@ -180,7 +92,7 @@ impl FreesrData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove unequipped relics
|
// remove unequipped relics
|
||||||
if freesr_data.relics.len() > 2000 {
|
if freesr_data.relics.len() > 3000 {
|
||||||
freesr_data.relics.retain(|v| v.equip_avatar != 0);
|
freesr_data.relics.retain(|v| v.equip_avatar != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,13 +102,33 @@ impl FreesrData {
|
|||||||
freesr_data.march_type = MultiPathAvatar::MarchHunt;
|
freesr_data.march_type = MultiPathAvatar::MarchHunt;
|
||||||
}
|
}
|
||||||
|
|
||||||
freesr_data
|
Ok(freesr_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(&mut self) {
|
pub async fn update(&mut self) -> anyhow::Result<()> {
|
||||||
*self = Self::load().await
|
*self = Self::load().await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub async fn refresh_persistent(&mut self) -> anyhow::Result<()> {
|
||||||
|
// let persistent: Persistent = serde_json::from_str(
|
||||||
|
// &tokio::fs::read_to_string("persistent")
|
||||||
|
// .await
|
||||||
|
// .unwrap_or_default(),
|
||||||
|
// )
|
||||||
|
// .unwrap_or_default();
|
||||||
|
|
||||||
|
// self.lineups = persistent.lineups;
|
||||||
|
// self.position = persistent.position;
|
||||||
|
// self.scene = persistent.scene;
|
||||||
|
// self.main_character = persistent.main_character;
|
||||||
|
// self.march_type = persistent.march_type;
|
||||||
|
// self.enable_sw_global = persistent.enable_sw_global;
|
||||||
|
// self.enable_castorice_global = persistent.enable_castorice_global;
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
async fn verify_lineup(&mut self) {
|
async fn verify_lineup(&mut self) {
|
||||||
if self.lineups.is_empty() {
|
if self.lineups.is_empty() {
|
||||||
self.lineups = BTreeMap::<u32, u32>::from([(0, 8001), (1, 0), (2, 0), (3, 0)])
|
self.lineups = BTreeMap::<u32, u32>::from([(0, 8001), (1, 0), (2, 0), (3, 0)])
|
||||||
@@ -209,20 +141,19 @@ impl FreesrData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn save_persistent(&self) {
|
pub async fn save_persistent(&self) {
|
||||||
let _ = tokio::fs::write(
|
if let Ok(data) = serde_json::to_string_pretty(&Persistent {
|
||||||
"persistent",
|
|
||||||
serde_json::to_string_pretty(&Persistent {
|
|
||||||
lineups: self.lineups.clone(),
|
lineups: self.lineups.clone(),
|
||||||
main_character: self.main_character,
|
main_character: self.main_character,
|
||||||
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,
|
||||||
|
enable_sw_global: self.enable_sw_global,
|
||||||
|
enable_castorice_global: self.enable_castorice_global,
|
||||||
// game_language: self.game_language,
|
// game_language: self.game_language,
|
||||||
// voice_language: self.voice_langauge,
|
// voice_language: self.voice_langauge,
|
||||||
})
|
}) {
|
||||||
.unwrap(),
|
let _ = tokio::fs::write("persistent", data).await;
|
||||||
)
|
|
||||||
.await;
|
|
||||||
tracing::info!("persistent saved");
|
tracing::info!("persistent saved");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
use proto::{
|
use proto::{
|
||||||
Avatar, AvatarSkillTree, AvatarType, BattleAvatar, BattleBuff, ExtraLineupType, Gender,
|
Avatar, AvatarPathData, AvatarPathSkillTree, AvatarSkillTree, AvatarType, BattleAvatar,
|
||||||
LineupAvatar, LineupInfo, MultiPathAvatarType, SpBarInfo,
|
BattleBuff, ExtraLineupType, Gender, LineupAvatar, LineupInfo, MultiPathAvatarType, SpBarInfo,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -19,8 +19,10 @@ pub enum MultiPathAvatar {
|
|||||||
FemalePreservation = 8004,
|
FemalePreservation = 8004,
|
||||||
MaleHarmony = 8005,
|
MaleHarmony = 8005,
|
||||||
FemaleHarmony = 8006,
|
FemaleHarmony = 8006,
|
||||||
MaleRememberance = 8007,
|
MaleRemembrance = 8007,
|
||||||
FemaleRememberance = 8008,
|
FemaleRemembrance = 8008,
|
||||||
|
MaleElation = 8009,
|
||||||
|
FemaleElation = 8010,
|
||||||
MarchHunt = 1224,
|
MarchHunt = 1224,
|
||||||
MarchPreservation = 1001,
|
MarchPreservation = 1001,
|
||||||
#[default]
|
#[default]
|
||||||
@@ -36,8 +38,10 @@ impl From<u32> for MultiPathAvatar {
|
|||||||
8004 => Self::FemalePreservation,
|
8004 => Self::FemalePreservation,
|
||||||
8005 => Self::MaleHarmony,
|
8005 => Self::MaleHarmony,
|
||||||
8006 => Self::FemaleHarmony,
|
8006 => Self::FemaleHarmony,
|
||||||
8007 => Self::MaleRememberance,
|
8007 => Self::MaleRemembrance,
|
||||||
8008 => Self::FemaleRememberance,
|
8008 => Self::FemaleRemembrance,
|
||||||
|
8009 => Self::MaleElation,
|
||||||
|
8010 => Self::FemaleElation,
|
||||||
1224 => Self::MarchHunt,
|
1224 => Self::MarchHunt,
|
||||||
1001 => Self::MarchPreservation,
|
1001 => Self::MarchPreservation,
|
||||||
_ => Self::Unk,
|
_ => Self::Unk,
|
||||||
@@ -54,11 +58,13 @@ impl From<MultiPathAvatar> for u32 {
|
|||||||
MultiPathAvatar::FemalePreservation => 8004,
|
MultiPathAvatar::FemalePreservation => 8004,
|
||||||
MultiPathAvatar::MaleHarmony => 8005,
|
MultiPathAvatar::MaleHarmony => 8005,
|
||||||
MultiPathAvatar::FemaleHarmony => 8006,
|
MultiPathAvatar::FemaleHarmony => 8006,
|
||||||
MultiPathAvatar::MaleRememberance => 8007,
|
MultiPathAvatar::MaleRemembrance => 8007,
|
||||||
MultiPathAvatar::FemaleRememberance => 8008,
|
MultiPathAvatar::FemaleRemembrance => 8008,
|
||||||
|
MultiPathAvatar::MaleElation => 8009,
|
||||||
|
MultiPathAvatar::FemaleElation => 8010,
|
||||||
MultiPathAvatar::MarchHunt => 1224,
|
MultiPathAvatar::MarchHunt => 1224,
|
||||||
MultiPathAvatar::MarchPreservation => 1001,
|
MultiPathAvatar::MarchPreservation => 1001,
|
||||||
_ => 8006,
|
MultiPathAvatar::Unk => 8006,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,8 +92,10 @@ impl MultiPathAvatar {
|
|||||||
MultiPathAvatar::MarchHunt => MultiPathAvatarType::Mar7thRogueType,
|
MultiPathAvatar::MarchHunt => MultiPathAvatarType::Mar7thRogueType,
|
||||||
MultiPathAvatar::MarchPreservation => MultiPathAvatarType::Mar7thKnightType,
|
MultiPathAvatar::MarchPreservation => MultiPathAvatarType::Mar7thKnightType,
|
||||||
MultiPathAvatar::Unk => MultiPathAvatarType::None,
|
MultiPathAvatar::Unk => MultiPathAvatarType::None,
|
||||||
MultiPathAvatar::MaleRememberance => MultiPathAvatarType::BoyMemoryType,
|
MultiPathAvatar::MaleRemembrance => MultiPathAvatarType::BoyMemoryType,
|
||||||
MultiPathAvatar::FemaleRememberance => MultiPathAvatarType::GirlMemoryType,
|
MultiPathAvatar::FemaleRemembrance => MultiPathAvatarType::GirlMemoryType,
|
||||||
|
MultiPathAvatar::MaleElation => MultiPathAvatarType::BoyElationType,
|
||||||
|
MultiPathAvatar::FemaleElation => MultiPathAvatarType::GirlElationType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +111,10 @@ impl MultiPathAvatar {
|
|||||||
Self::FemalePreservation,
|
Self::FemalePreservation,
|
||||||
Self::MaleHarmony,
|
Self::MaleHarmony,
|
||||||
Self::FemaleHarmony,
|
Self::FemaleHarmony,
|
||||||
Self::MaleRememberance,
|
Self::MaleRemembrance,
|
||||||
Self::FemaleRememberance,
|
Self::FemaleRemembrance,
|
||||||
|
Self::MaleElation,
|
||||||
|
Self::FemaleElation,
|
||||||
Self::MarchHunt,
|
Self::MarchHunt,
|
||||||
Self::MarchPreservation,
|
Self::MarchPreservation,
|
||||||
]
|
]
|
||||||
@@ -128,16 +138,25 @@ pub struct AvatarJson {
|
|||||||
pub sp_value: Option<u32>,
|
pub sp_value: Option<u32>,
|
||||||
#[serde(alias = "spMax")]
|
#[serde(alias = "spMax")]
|
||||||
pub sp_max: Option<u32>,
|
pub sp_max: Option<u32>,
|
||||||
|
pub enhanced_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct AvatarData {
|
pub struct AvatarData {
|
||||||
pub rank: u32,
|
pub rank: u32,
|
||||||
pub skills: HashMap<u32, u32>,
|
pub skills: HashMap<u32, u32>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub skills_by_anchor_type: HashMap<u32, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AvatarJson {
|
impl AvatarJson {
|
||||||
pub fn to_avatar_proto(&self, lightcone: Option<&Lightcone>, relics: Vec<&Relic>) -> Avatar {
|
/// This should only be used to serialize "BaseAvatar". For example, only 8001 for MC and 1001 for March
|
||||||
|
pub fn to_avatar_proto(
|
||||||
|
&self,
|
||||||
|
lightcone: Option<&Lightcone>,
|
||||||
|
mc_id: u32,
|
||||||
|
march_id: u32,
|
||||||
|
) -> Avatar {
|
||||||
// TODO: HARDCODED
|
// TODO: HARDCODED
|
||||||
let base_avatar_id = if self.avatar_id > 8000 {
|
let base_avatar_id = if self.avatar_id > 8000 {
|
||||||
8001
|
8001
|
||||||
@@ -147,24 +166,51 @@ impl AvatarJson {
|
|||||||
self.avatar_id
|
self.avatar_id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: HARDCODED
|
||||||
|
let cur_multi_path_avatar_type = if base_avatar_id == 8001 {
|
||||||
|
mc_id
|
||||||
|
} else if base_avatar_id == 1001 {
|
||||||
|
march_id
|
||||||
|
} else {
|
||||||
|
base_avatar_id
|
||||||
|
};
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
base_avatar_id,
|
base_avatar_id,
|
||||||
level: self.level,
|
level: self.level,
|
||||||
promotion: self.promotion,
|
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(),
|
equipment_unique_id: lightcone.map(|v| v.get_unique_id()).unwrap_or_default(),
|
||||||
first_met_timestamp: 1712924677,
|
first_met_time_stamp: 1712924677,
|
||||||
equip_relic_list: relics.iter().map(|v| (*v).into()).collect::<Vec<_>>(),
|
cur_multi_path_avatar_type,
|
||||||
..Default::default()
|
has_taken_promotion_reward_list: vec![1, 3, 5],
|
||||||
|
is_marked: false,
|
||||||
|
exp: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_avatar_path_data_proto(
|
||||||
|
&self,
|
||||||
|
lightcone: Option<&Lightcone>,
|
||||||
|
relics: Vec<&Relic>,
|
||||||
|
) -> AvatarPathData {
|
||||||
|
AvatarPathData {
|
||||||
|
avatar_id: self.avatar_id,
|
||||||
|
rank: self.data.rank,
|
||||||
|
equip_relic_list: relics
|
||||||
|
.iter()
|
||||||
|
.filter(|relic| relic.equip_avatar == self.avatar_id)
|
||||||
|
.map(|&relic| relic.into())
|
||||||
|
.collect(),
|
||||||
|
avatar_path_skill_tree: self
|
||||||
|
.data
|
||||||
|
.skills_by_anchor_type
|
||||||
|
.iter()
|
||||||
|
.map(|(&anchor_type, &level)| AvatarPathSkillTree { anchor_type, level })
|
||||||
|
.collect(),
|
||||||
|
path_equipment_id: lightcone.map(|v| v.get_unique_id()).unwrap_or_default(),
|
||||||
|
unk_enhanced_id: self.enhanced_id.unwrap_or_default(),
|
||||||
|
unlock_timestamp: 0,
|
||||||
|
dressed_skin_id: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +248,7 @@ impl AvatarJson {
|
|||||||
cur_sp: self.sp_value.unwrap_or(10_000),
|
cur_sp: self.sp_value.unwrap_or(10_000),
|
||||||
max_sp: self.sp_max.unwrap_or(10_000),
|
max_sp: self.sp_max.unwrap_or(10_000),
|
||||||
}),
|
}),
|
||||||
|
enhanced_id: self.enhanced_id.unwrap_or_default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -209,7 +256,7 @@ impl AvatarJson {
|
|||||||
for buff_id in &self.techniques {
|
for buff_id in &self.techniques {
|
||||||
battle_buff.push(BattleBuff {
|
battle_buff.push(BattleBuff {
|
||||||
wave_flag: 0xffffffff,
|
wave_flag: 0xffffffff,
|
||||||
owner_id: index,
|
owner_index: index,
|
||||||
level: 1,
|
level: 1,
|
||||||
id: *buff_id,
|
id: *buff_id,
|
||||||
dynamic_values: HashMap::from([(String::from("SkillIndex"), 2.0)]),
|
dynamic_values: HashMap::from([(String::from("SkillIndex"), 2.0)]),
|
||||||
@@ -256,11 +303,12 @@ impl AvatarJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_lineup_info(lineups: &BTreeMap<u32, u32>) -> LineupInfo {
|
pub fn to_lineup_info(lineups: &BTreeMap<u32, u32>) -> LineupInfo {
|
||||||
|
let max_mp = if lineups.contains_key(&1408) { 8 } else { 5 };
|
||||||
let mut lineup_info = LineupInfo {
|
let mut lineup_info = LineupInfo {
|
||||||
extra_lineup_type: ExtraLineupType::LineupNone.into(),
|
extra_lineup_type: ExtraLineupType::LineupNone.into(),
|
||||||
name: "Squad 1".to_string(),
|
name: "Squad 1".to_string(),
|
||||||
mp: 5,
|
mp: max_mp,
|
||||||
max_mp: 5,
|
max_mp,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -16,6 +16,8 @@ pub struct BattleConfig {
|
|||||||
pub custom_stats: Vec<SubAffix>,
|
pub custom_stats: Vec<SubAffix>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub scepters: Vec<RogueMagicScepter>,
|
pub scepters: Vec<RogueMagicScepter>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub custom_battle_lineup: Option<BTreeMap<u32, u32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BattleConfig {
|
impl Default for BattleConfig {
|
||||||
@@ -33,25 +35,22 @@ impl Default for BattleConfig {
|
|||||||
path_resonance_id: Default::default(),
|
path_resonance_id: Default::default(),
|
||||||
custom_stats: Default::default(),
|
custom_stats: Default::default(),
|
||||||
scepters: Default::default(),
|
scepters: Default::default(),
|
||||||
|
custom_battle_lineup: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
pub enum BattleType {
|
pub enum BattleType {
|
||||||
#[serde(alias = "DEFAULT")]
|
#[serde(alias = "DEFAULT")]
|
||||||
|
#[default]
|
||||||
Default = 0,
|
Default = 0,
|
||||||
#[serde(alias = "MOC")]
|
#[serde(alias = "MOC")]
|
||||||
Moc = 1,
|
Moc = 1,
|
||||||
PF = 2,
|
PF = 2,
|
||||||
SU = 3,
|
SU = 3,
|
||||||
AS = 4,
|
AS = 4,
|
||||||
}
|
AA = 5,
|
||||||
|
|
||||||
impl Default for BattleType {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Default
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BATTLE BUFFS
|
// BATTLE BUFFS
|
||||||
@@ -77,7 +76,7 @@ impl BattleBuffJson {
|
|||||||
id: self.id,
|
id: self.id,
|
||||||
level: self.level,
|
level: self.level,
|
||||||
wave_flag: 0xffffffff,
|
wave_flag: 0xffffffff,
|
||||||
owner_id: 0xffffffff,
|
owner_index: 0xffffffff,
|
||||||
dynamic_values: if let Some(dyn_key) = &self.dynamic_key {
|
dynamic_values: if let Some(dyn_key) = &self.dynamic_key {
|
||||||
HashMap::from([(dyn_key.key.clone(), dyn_key.value as f32)])
|
HashMap::from([(dyn_key.key.clone(), dyn_key.value as f32)])
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub struct Lightcone {
|
|||||||
|
|
||||||
impl_from!(Lightcone, Equipment, |value| {
|
impl_from!(Lightcone, Equipment, |value| {
|
||||||
Equipment {
|
Equipment {
|
||||||
equip_avatar_id: value.equip_avatar,
|
dress_avatar_id: value.equip_avatar,
|
||||||
exp: 0,
|
exp: 0,
|
||||||
is_protected: false,
|
is_protected: false,
|
||||||
level: value.level,
|
level: value.level,
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ pub use persistent::*;
|
|||||||
pub use scene::*;
|
pub use scene::*;
|
||||||
|
|
||||||
pub fn get_item_unique_id(internal_uid: u32, item_type: ItemType) -> u32 {
|
pub fn get_item_unique_id(internal_uid: u32, item_type: ItemType) -> u32 {
|
||||||
|
// srtools start internal_uid from 1
|
||||||
if item_type == ItemType::ItemEquipment {
|
if item_type == ItemType::ItemEquipment {
|
||||||
2000 + internal_uid // 2000.3500
|
3000 + internal_uid // 3001..3500
|
||||||
} else {
|
} else {
|
||||||
1 + internal_uid // 1..2000
|
internal_uid // 1..2000 ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ impl_from!(Monster, SceneMonster, |value| {
|
|||||||
monster_id: value.monster_id,
|
monster_id: value.monster_id,
|
||||||
max_hp: value.max_hp,
|
max_hp: value.max_hp,
|
||||||
cur_hp: value.max_hp,
|
cur_hp: value.max_hp,
|
||||||
|
extra_info: None,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,8 +28,8 @@ impl Monster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SceneMonsterWave {
|
SceneMonsterWave {
|
||||||
wave_id,
|
battle_wave_id: wave_id,
|
||||||
wave_param: Some(SceneMonsterWaveParam {
|
monster_param: Some(SceneMonsterWaveParam {
|
||||||
level: monsters.iter().map(|v| v.level).max().unwrap_or(95),
|
level: monsters.iter().map(|v| v.level).max().unwrap_or(95),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ pub struct Persistent {
|
|||||||
pub march_type: MultiPathAvatar,
|
pub march_type: MultiPathAvatar,
|
||||||
// pub game_language: AllowedLanguages,
|
// pub game_language: AllowedLanguages,
|
||||||
// pub voice_language: AllowedLanguages,
|
// pub voice_language: AllowedLanguages,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enable_sw_global: Option<bool>,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enable_castorice_global: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_true() -> Option<bool> {
|
||||||
|
Some(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Persistent {
|
impl Default for Persistent {
|
||||||
@@ -50,11 +58,13 @@ impl Default for Persistent {
|
|||||||
Self {
|
Self {
|
||||||
lineups: BTreeMap::from([(0, 1313), (1, 1006), (2, 8001), (3, 1405)]),
|
lineups: BTreeMap::from([(0, 1313), (1, 1006), (2, 8001), (3, 1405)]),
|
||||||
position: Default::default(),
|
position: Default::default(),
|
||||||
main_character: MultiPathAvatar::FemaleRememberance,
|
main_character: MultiPathAvatar::FemaleRemembrance,
|
||||||
scene: Default::default(),
|
scene: Default::default(),
|
||||||
march_type: MultiPathAvatar::MarchHunt,
|
march_type: MultiPathAvatar::MarchHunt,
|
||||||
// game_language: AllowedLanguages::En,
|
// game_language: AllowedLanguages::En,
|
||||||
// voice_language: AllowedLanguages::Jp,
|
// voice_language: AllowedLanguages::Jp,
|
||||||
|
enable_sw_global: Some(true),
|
||||||
|
enable_castorice_global: Some(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,14 +67,14 @@ impl_from!(Relic, BattleRelic, |value| {
|
|||||||
|
|
||||||
impl_from!(Relic, EquipRelic, |value| {
|
impl_from!(Relic, EquipRelic, |value| {
|
||||||
EquipRelic {
|
EquipRelic {
|
||||||
slot: value.get_slot(),
|
r#type: value.get_slot(),
|
||||||
relic_unique_id: value.get_unique_id(),
|
relic_unique_id: value.get_unique_id(),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
impl_from!(Relic, proto::Relic, |value| {
|
impl_from!(Relic, proto::Relic, |value| {
|
||||||
proto::Relic {
|
proto::Relic {
|
||||||
equip_avatar_id: value.equip_avatar,
|
dress_avatar_id: value.equip_avatar,
|
||||||
exp: 0,
|
exp: 0,
|
||||||
is_protected: false,
|
is_protected: false,
|
||||||
level: value.level,
|
level: value.level,
|
||||||
|
|||||||
+20730
-38657
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
|||||||
#![feature(let_chains)]
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
mod net;
|
mod net;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use std::{
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use common::sr_tools::FreesrData;
|
use common::sr_tools::FreesrData;
|
||||||
use rand::RngCore;
|
use rand::Rng;
|
||||||
|
|
||||||
use crate::net::PlayerSession;
|
use crate::net::PlayerSession;
|
||||||
|
|
||||||
@@ -86,11 +86,11 @@ impl Gateway {
|
|||||||
)));
|
)));
|
||||||
|
|
||||||
// Init the json to session
|
// Init the json to session
|
||||||
let _ = session
|
if let Ok(data) = FreesrData::load().await {
|
||||||
.write()
|
let _ = session.write().await.json_data.set(data);
|
||||||
.await
|
} else {
|
||||||
.json_data
|
tracing::error!("Failed to load initial freesr-data.json");
|
||||||
.set(FreesrData::load().await);
|
}
|
||||||
|
|
||||||
let session_ref = session.clone();
|
let session_ref = session.clone();
|
||||||
|
|
||||||
@@ -98,17 +98,25 @@ impl Gateway {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let (tx, mut rx) = mpsc::channel(100);
|
let (tx, mut rx) = mpsc::channel(100);
|
||||||
let mut debouncer =
|
let mut debouncer =
|
||||||
notify_debouncer_mini::new_debouncer(Duration::from_millis(1000), move |ev| {
|
match notify_debouncer_mini::new_debouncer(Duration::from_millis(1000), move |ev| {
|
||||||
let _ = tx.blocking_send(ev);
|
let _ = tx.blocking_send(ev);
|
||||||
})
|
}) {
|
||||||
.unwrap();
|
Ok(d) => d,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("debouncer init err: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let path = Path::new("freesr-data.json");
|
let path = Path::new("freesr-data.json");
|
||||||
|
|
||||||
debouncer
|
if let Err(e) = debouncer
|
||||||
.watcher()
|
.watcher()
|
||||||
.watch(path, notify::RecursiveMode::NonRecursive)
|
.watch(path, notify::RecursiveMode::NonRecursive)
|
||||||
.unwrap();
|
{
|
||||||
|
tracing::error!("failed to watch {path:?}: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
tracing::info!("watching freesr-data.json changes");
|
tracing::info!("watching freesr-data.json changes");
|
||||||
|
|
||||||
@@ -129,24 +137,23 @@ impl Gateway {
|
|||||||
if events
|
if events
|
||||||
.iter()
|
.iter()
|
||||||
.any(|p| p.path.file_name() == path.file_name())
|
.any(|p| p.path.file_name() == path.file_name())
|
||||||
{
|
&& let Ok(metadata) = std::fs::metadata(path)
|
||||||
if let Ok(metadata) = std::fs::metadata(path) {
|
&& let Ok(modified) = metadata.modified()
|
||||||
if let Ok(modified) = metadata.modified() {
|
&& modified > last_modified {
|
||||||
if modified > last_modified {
|
|
||||||
last_modified = 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;
|
if let Err(e) = json.update().await {
|
||||||
session.sync_player().await;
|
tracing::error!("Failed to update json: {e}");
|
||||||
|
} else {
|
||||||
|
let _ = session.sync_player().await;
|
||||||
tracing::info!("json updated");
|
tracing::info!("json updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Err(e) => tracing::error!("json watcher error: {e:?}"),
|
||||||
}
|
|
||||||
Err(e) => eprintln!("json watcher error: {:?}", e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = shutdown_rx.changed() => {
|
_ = shutdown_rx.changed() => {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ pub async fn on_player_get_token_cs_req(
|
|||||||
_body: &PlayerGetTokenCsReq,
|
_body: &PlayerGetTokenCsReq,
|
||||||
res: &mut PlayerGetTokenScRsp,
|
res: &mut PlayerGetTokenScRsp,
|
||||||
) {
|
) {
|
||||||
res.msg = String::from("OK");
|
|
||||||
res.uid = 25;
|
res.uid = 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub static UNLOCKED_AVATARS: [u32; 63] = [
|
pub static BASE_AVATAR_IDS: [u32; 87] = [
|
||||||
|
8001, 1001, //
|
||||||
|
//
|
||||||
1002, 1003, 1004, 1005, 1006, 1008, 1009, 1013, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108,
|
1002, 1003, 1004, 1005, 1006, 1008, 1009, 1013, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108,
|
||||||
1109, 1110, 1111, 1112, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212,
|
1109, 1110, 1111, 1112, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212,
|
||||||
1213, 1214, 1215, 1217, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1312, 1315, 1310,
|
1213, 1214, 1215, 1217, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1312, 1315, 1310,
|
||||||
1314, 1218, 1221, 1220, 1222, 1223, 1317, 1313, 1225, 1402, 1401, 1404, 1403, 1405, 1407,
|
1314, 1218, 1221, 1220, 1222, 1223, 1317, 1313, 1225, 1402, 1401, 1404, 1403, 1405, 1407, 1406,
|
||||||
|
1409, 1014, 1015, 1408, 1410, 1412, 1413, 1414, 1415, 1321, 1501, 1502, 1504, 1505, 1506, 1507,
|
||||||
|
1508, 1509, 1510, 1512, 1513,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub async fn on_get_avatar_data_cs_req(
|
pub async fn on_get_avatar_data_cs_req(
|
||||||
@@ -17,46 +21,75 @@ pub async fn on_get_avatar_data_cs_req(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: HARDCODED
|
|
||||||
let mc_ids = if json.main_character.get_gender() == Gender::Man {
|
|
||||||
[8001, 8003, 8005, 8007]
|
|
||||||
} else {
|
|
||||||
[8002, 8004, 8006, 8008]
|
|
||||||
};
|
|
||||||
|
|
||||||
let march_ids = [1001, 1224];
|
|
||||||
|
|
||||||
res.is_get_all = body.is_get_all;
|
res.is_get_all = body.is_get_all;
|
||||||
res.avatar_list = UNLOCKED_AVATARS
|
res.avatar_list = BASE_AVATAR_IDS
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(mc_ids.iter().copied())
|
|
||||||
.chain(march_ids.iter().copied())
|
|
||||||
.map(|id| {
|
.map(|id| {
|
||||||
json.avatars
|
json.avatars
|
||||||
.get(&id)
|
.get(&id)
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
v.to_avatar_proto(
|
v.to_avatar_proto(
|
||||||
json.lightcones.iter().find(|v| v.equip_avatar == id),
|
json.lightcones.iter().find(|v| v.equip_avatar == id),
|
||||||
json.relics
|
json.main_character as u32,
|
||||||
.iter()
|
json.march_type as u32,
|
||||||
.filter(|v| v.equip_avatar == id)
|
|
||||||
.collect(),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or(Avatar {
|
.unwrap_or(Avatar {
|
||||||
base_avatar_id: id,
|
base_avatar_id: id,
|
||||||
level: 80,
|
level: 80,
|
||||||
promotion: 6,
|
promotion: 6,
|
||||||
rank: 6,
|
first_met_time_stamp: 1712924677,
|
||||||
skilltree_list: (1..=4)
|
cur_multi_path_avatar_type: 0,
|
||||||
.map(|m| AvatarSkillTree {
|
equipment_unique_id: 0,
|
||||||
point_id: id * 1000 + m,
|
has_taken_promotion_reward_list: vec![1, 3, 5],
|
||||||
level: 1,
|
is_marked: false,
|
||||||
})
|
exp: 0,
|
||||||
.collect(),
|
|
||||||
first_met_timestamp: 1712924677,
|
|
||||||
..Default::default()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
res.avatar_path_data_info_list = json
|
||||||
|
.avatars
|
||||||
|
.values()
|
||||||
|
.map(|avatar| {
|
||||||
|
avatar.to_avatar_path_data_proto(
|
||||||
|
json.lightcones
|
||||||
|
.iter()
|
||||||
|
.find(|l| l.equip_avatar == avatar.avatar_id),
|
||||||
|
json.relics
|
||||||
|
.iter()
|
||||||
|
.filter(|r| r.equip_avatar == avatar.avatar_id)
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on_take_promotion_reward_cs_req(
|
||||||
|
_session: &mut PlayerSession,
|
||||||
|
_req: &TakePromotionRewardCsReq,
|
||||||
|
_res: &mut TakePromotionRewardScRsp,
|
||||||
|
) {
|
||||||
|
// retcode defaults to 0, reward_list defaults to empty
|
||||||
|
// All avatars already report has_taken_promotion_reward_list = [0..=6] in GetAvatarData
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on_set_avatar_enhanced_id_cs_req(
|
||||||
|
_session: &mut PlayerSession,
|
||||||
|
_req: &SetAvatarEnhancedIdCsReq,
|
||||||
|
_res: &mut SetAvatarEnhancedIdScRsp,
|
||||||
|
) {
|
||||||
|
// let Some(json) = session.json_data.get_mut() else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
// let Some(avatar) = json.avatars.get_mut(&req.avatar_id) else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
// avatar.enhanced_id = if req.enhanced_id == 0 {
|
||||||
|
// Option::<u32>::None
|
||||||
|
// } else {
|
||||||
|
// Some(req.enhanced_id)
|
||||||
|
// };
|
||||||
|
// res.growth_avatar_id = avatar.avatar_id;
|
||||||
|
// res.unk_enhanced_id = req.enhanced_id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::RngExt;
|
||||||
use rogue_magic_battle_unit_info::Item;
|
|
||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
resources::GAME_RES,
|
resources::GAME_RES,
|
||||||
structs::{BattleType, Monster, RogueMagicComponentType},
|
structs::{BattleType, Monster},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -36,6 +35,16 @@ pub async fn on_quick_start_cocoon_stage_cs_req(
|
|||||||
res.battle_info = Some(battle_info);
|
res.battle_info = Some(battle_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn on_scene_enter_stage_cs_req(
|
||||||
|
session: &mut PlayerSession,
|
||||||
|
_req: &SceneEnterStageCsReq,
|
||||||
|
res: &mut SceneEnterStageScRsp,
|
||||||
|
) {
|
||||||
|
let battle_info = create_battle_info(session, 0, 0).await;
|
||||||
|
|
||||||
|
res.battle_info = Some(battle_info);
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn on_pve_battle_result_cs_req(
|
pub async fn on_pve_battle_result_cs_req(
|
||||||
_session: &mut PlayerSession,
|
_session: &mut PlayerSession,
|
||||||
req: &PveBattleResultCsReq,
|
req: &PveBattleResultCsReq,
|
||||||
@@ -50,7 +59,7 @@ pub async fn on_scene_cast_skill_cs_req(
|
|||||||
req: &SceneCastSkillCsReq,
|
req: &SceneCastSkillCsReq,
|
||||||
res: &mut SceneCastSkillScRsp,
|
res: &mut SceneCastSkillScRsp,
|
||||||
) {
|
) {
|
||||||
res.attacked_group_id = req.attacked_group_id;
|
res.cast_entity_id = req.cast_entity_id;
|
||||||
|
|
||||||
let targets = req
|
let targets = req
|
||||||
.hit_target_entity_id_list
|
.hit_target_entity_id_list
|
||||||
@@ -64,9 +73,9 @@ pub async fn on_scene_cast_skill_cs_req(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let battle_info = create_battle_info(session, req.caster_id, req.skill_index).await;
|
let battle_info = create_battle_info(session, req.attacked_by_entity_id, req.skill_index).await;
|
||||||
|
|
||||||
res.attacked_group_id = req.attacked_group_id;
|
res.cast_entity_id = req.cast_entity_id;
|
||||||
res.battle_info = Some(battle_info);
|
res.battle_info = Some(battle_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +84,10 @@ async fn create_battle_info(
|
|||||||
caster_id: u32,
|
caster_id: u32,
|
||||||
skill_index: u32,
|
skill_index: u32,
|
||||||
) -> SceneBattleInfo {
|
) -> SceneBattleInfo {
|
||||||
|
// if let Some(player) = session.json_data.get_mut() {
|
||||||
|
// let _ = player.refresh_persistent().await;
|
||||||
|
// }
|
||||||
|
|
||||||
let Some(player) = session.json_data.get() else {
|
let Some(player) = session.json_data.get() else {
|
||||||
tracing::error!("data is not set!");
|
tracing::error!("data is not set!");
|
||||||
return SceneBattleInfo::default();
|
return SceneBattleInfo::default();
|
||||||
@@ -89,8 +102,27 @@ async fn create_battle_info(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let is_calyx = caster_id == 0 && skill_index == 0;
|
||||||
|
let mut has_dahlia = false;
|
||||||
|
let mut first_avatar_id = 0;
|
||||||
|
let mut first_avatar_idx = 9999;
|
||||||
|
|
||||||
|
let lineup_uwu = if let Some(custom) = &player.battle_config.custom_battle_lineup {
|
||||||
|
custom.iter()
|
||||||
|
} else {
|
||||||
|
player.lineups.iter()
|
||||||
|
};
|
||||||
|
|
||||||
// avatars
|
// avatars
|
||||||
for (avatar_index, avatar_id) in player.lineups.iter() {
|
for (avatar_index, avatar_id) in lineup_uwu {
|
||||||
|
if first_avatar_id == 0 {
|
||||||
|
first_avatar_id = *avatar_id;
|
||||||
|
first_avatar_idx = *avatar_index;
|
||||||
|
}
|
||||||
|
if !has_dahlia {
|
||||||
|
has_dahlia = *avatar_id == 1321;
|
||||||
|
}
|
||||||
|
|
||||||
let is_trailblazer = *avatar_id == 8001;
|
let is_trailblazer = *avatar_id == 8001;
|
||||||
let is_march = *avatar_id == 1001;
|
let is_march = *avatar_id == 1001;
|
||||||
|
|
||||||
@@ -115,9 +147,8 @@ async fn create_battle_info(
|
|||||||
.filter(|v| v.equip_avatar == avatar.avatar_id)
|
.filter(|v| v.equip_avatar == avatar.avatar_id)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
for tech in techs {
|
|
||||||
battle_info.buff_list.push(tech);
|
battle_info.buff_list.extend(techs);
|
||||||
}
|
|
||||||
|
|
||||||
if caster_id > 0
|
if caster_id > 0
|
||||||
&& *avatar_index == (caster_id - 1)
|
&& *avatar_index == (caster_id - 1)
|
||||||
@@ -127,7 +158,7 @@ async fn create_battle_info(
|
|||||||
battle_info.buff_list.push(BattleBuff {
|
battle_info.buff_list.push(BattleBuff {
|
||||||
id: avatar_config.weakness_buff_id,
|
id: avatar_config.weakness_buff_id,
|
||||||
level: 1,
|
level: 1,
|
||||||
owner_id: *avatar_index,
|
owner_index: *avatar_index,
|
||||||
wave_flag: 0xffffffff,
|
wave_flag: 0xffffffff,
|
||||||
dynamic_values: HashMap::from([(
|
dynamic_values: HashMap::from([(
|
||||||
String::from("SkillIndex"),
|
String::from("SkillIndex"),
|
||||||
@@ -141,23 +172,79 @@ async fn create_battle_info(
|
|||||||
|
|
||||||
// hardcoded march
|
// hardcoded march
|
||||||
if avatar.avatar_id == 1224 {
|
if avatar.avatar_id == 1224 {
|
||||||
let buffs = BattleBuff {
|
battle_info.buff_list.push(BattleBuff {
|
||||||
id: 122401,
|
id: 122401,
|
||||||
level: 3,
|
level: 3,
|
||||||
wave_flag: 0xffffffff,
|
wave_flag: 0xffffffff,
|
||||||
owner_id: *avatar_index,
|
owner_index: *avatar_index,
|
||||||
dynamic_values: HashMap::from([
|
dynamic_values: HashMap::from([
|
||||||
(String::from("#ADF_1"), 3f32),
|
(String::from("#ADF_1"), 3f32),
|
||||||
(String::from("#ADF_2"), 3f32),
|
(String::from("#ADF_2"), 3f32),
|
||||||
]),
|
]),
|
||||||
target_index_list: vec![0],
|
target_index_list: vec![0],
|
||||||
};
|
});
|
||||||
|
|
||||||
battle_info.buff_list.push(buffs);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hardcoded Cerydra & Danheng PT technique
|
||||||
|
// and hardcode dahlia dance partner to 1st in lineup
|
||||||
|
let first_avatar_attack_id = if let Some(c) = GAME_RES.avatar_configs.get(&first_avatar_id) {
|
||||||
|
c.weakness_buff_id
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let len = battle_info.buff_list.len();
|
||||||
|
let mut has_replaced = false;
|
||||||
|
let mut dahlia_buffs = Vec::new();
|
||||||
|
for (i, buff) in battle_info.buff_list.iter_mut().enumerate() {
|
||||||
|
let is_last = i == len - 1;
|
||||||
|
|
||||||
|
if buff.id == 141202 || buff.id == 141403 {
|
||||||
|
buff.owner_index = first_avatar_idx;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is actually useless because there is 0 attack buff id in calyx but idc
|
||||||
|
if has_dahlia
|
||||||
|
&& is_calyx
|
||||||
|
&& let 1000111..=1000117 = buff.id
|
||||||
|
&& !has_replaced
|
||||||
|
&& first_avatar_attack_id != 0
|
||||||
|
{
|
||||||
|
buff.id = first_avatar_attack_id;
|
||||||
|
buff.owner_index = first_avatar_idx;
|
||||||
|
has_replaced = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_dahlia && is_calyx && !has_replaced && first_avatar_attack_id != 0 && is_last {
|
||||||
|
dahlia_buffs.push(BattleBuff {
|
||||||
|
id: 1000121,
|
||||||
|
level: 1,
|
||||||
|
wave_flag: u32::MAX,
|
||||||
|
target_index_list: vec![first_avatar_idx],
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
dahlia_buffs.push(BattleBuff {
|
||||||
|
id: first_avatar_attack_id,
|
||||||
|
level: 1,
|
||||||
|
wave_flag: u32::MAX,
|
||||||
|
target_index_list: vec![first_avatar_idx],
|
||||||
|
dynamic_values: HashMap::from([(
|
||||||
|
String::from("SkillIndex"),
|
||||||
|
first_avatar_idx as f32,
|
||||||
|
)]),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dahlia_buffs.is_empty() {
|
||||||
|
battle_info.buff_list.extend(dahlia_buffs);
|
||||||
|
}
|
||||||
|
|
||||||
// custom stats for avatars
|
// custom stats for avatars
|
||||||
for stat in &player.battle_config.custom_stats {
|
for stat in &player.battle_config.custom_stats {
|
||||||
for avatar in &mut battle_info.battle_avatar_list {
|
for avatar in &mut battle_info.battle_avatar_list {
|
||||||
@@ -192,7 +279,7 @@ async fn create_battle_info(
|
|||||||
id: blessing.id,
|
id: blessing.id,
|
||||||
level: blessing.level,
|
level: blessing.level,
|
||||||
wave_flag: 0xffffffff,
|
wave_flag: 0xffffffff,
|
||||||
owner_id: 0xffffffff,
|
owner_index: 0xffffffff,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -217,21 +304,27 @@ async fn create_battle_info(
|
|||||||
// pf score object
|
// pf score object
|
||||||
if player.battle_config.battle_type == BattleType::PF {
|
if player.battle_config.battle_type == BattleType::PF {
|
||||||
if battle_info.stage_id >= 30309011 {
|
if battle_info.stage_id >= 30309011 {
|
||||||
battle_info.battle_target_info.insert(1, BattleTargetList {
|
battle_info.battle_target_info.insert(
|
||||||
|
1,
|
||||||
|
BattleTargetList {
|
||||||
battle_target_list: vec![BattleTarget {
|
battle_target_list: vec![BattleTarget {
|
||||||
id: 10003,
|
id: 10003,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
});
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
battle_info.battle_target_info.insert(1, BattleTargetList {
|
battle_info.battle_target_info.insert(
|
||||||
|
1,
|
||||||
|
BattleTargetList {
|
||||||
battle_target_list: vec![BattleTarget {
|
battle_target_list: vec![BattleTarget {
|
||||||
id: 10002,
|
id: 10002,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 2..=4 {
|
for i in 2..=4 {
|
||||||
@@ -240,7 +333,9 @@ async fn create_battle_info(
|
|||||||
.insert(i, BattleTargetList::default());
|
.insert(i, BattleTargetList::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
battle_info.battle_target_info.insert(5, BattleTargetList {
|
battle_info.battle_target_info.insert(
|
||||||
|
5,
|
||||||
|
BattleTargetList {
|
||||||
battle_target_list: vec![
|
battle_target_list: vec![
|
||||||
BattleTarget {
|
BattleTarget {
|
||||||
id: 2001,
|
id: 2001,
|
||||||
@@ -253,27 +348,29 @@ async fn create_battle_info(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apocalyptic Shadow
|
// Apocalyptic Shadow
|
||||||
if player.battle_config.battle_type == BattleType::AS {
|
if player.battle_config.battle_type == BattleType::AS {
|
||||||
battle_info.battle_target_info.insert(1, BattleTargetList {
|
battle_info.battle_target_info.insert(
|
||||||
|
1,
|
||||||
|
BattleTargetList {
|
||||||
battle_target_list: vec![BattleTarget {
|
battle_target_list: vec![BattleTarget {
|
||||||
id: 90005,
|
id: 90005,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SU
|
// SU
|
||||||
if player.battle_config.battle_type == BattleType::SU {
|
if player.battle_config.battle_type == BattleType::SU {
|
||||||
battle_info
|
battle_info.battle_event.push(BattleEventBattleInfo {
|
||||||
.event_battle_info_list
|
|
||||||
.push(BattleEventBattleInfo {
|
|
||||||
battle_event_id: player.battle_config.path_resonance_id,
|
battle_event_id: player.battle_config.path_resonance_id,
|
||||||
status: Some(BattleEventInitedData {
|
status: Some(BattleEventProperty {
|
||||||
sp_bar: Some(SpBarInfo {
|
sp_bar: Some(SpBarInfo {
|
||||||
cur_sp: 10_000,
|
cur_sp: 10_000,
|
||||||
max_sp: 10_000,
|
max_sp: 10_000,
|
||||||
@@ -287,62 +384,86 @@ async fn create_battle_info(
|
|||||||
battle_info.monster_wave_list = Monster::to_scene_monster_waves(&player.battle_config.monsters);
|
battle_info.monster_wave_list = Monster::to_scene_monster_waves(&player.battle_config.monsters);
|
||||||
|
|
||||||
// Rogue Magic
|
// Rogue Magic
|
||||||
|
// TODO: i dont need these shit
|
||||||
if !player.battle_config.scepters.is_empty() {
|
if !player.battle_config.scepters.is_empty() {
|
||||||
battle_info.rogue_magic_battle_info = Some(RogueMagicBattleInfo {
|
// battle_info.battle_rogue_magic_info = Some(BattleRogueMagicInfo {
|
||||||
player_detail_info: Some(RogueMagicBattleUnitInfo {
|
// detail_info: Some(BattleRogueMagicDetailInfo {
|
||||||
item: Some(Item::BattleRogueMagicData(BattleRogueMagicData {
|
// paiigoggofj: Some(Item::BattleRogueMagicData(BattleRogueMagicData {
|
||||||
round_cnt: Some(BattleRogueMagicRoundCount {
|
// round_cnt: Some(BattleRogueMagicRoundCount {
|
||||||
gpojenhaiba: 3,
|
// gpojenhaiba: 3,
|
||||||
kljklbmlefo: 0,
|
// kljklbmlefo: 0,
|
||||||
}),
|
// }),
|
||||||
battle_scepter_list: player
|
// battle_scepter_list: player
|
||||||
.battle_config
|
// .battle_config
|
||||||
.scepters
|
// .scepters
|
||||||
.iter()
|
// .iter()
|
||||||
.map(|scepter| {
|
// .map(|scepter| {
|
||||||
let mut battle_scepter = BattleRogueMagicScepter {
|
// let mut battle_scepter = BattleRogueMagicScepter {
|
||||||
level: scepter.level,
|
// level: scepter.level,
|
||||||
scepter_id: scepter.id,
|
// scepter_id: scepter.id,
|
||||||
magic_list: Vec::new(),
|
// magic_list: Vec::new(),
|
||||||
trench_count: HashMap::from([(3, 0), (4, 0), (5, 0)]),
|
// trench_count: HashMap::from([(3, 0), (4, 0), (5, 0)]),
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
let mut index = [0u32; 3];
|
// let mut index = [0u32; 3];
|
||||||
|
//
|
||||||
for component in &scepter.components {
|
// for component in &scepter.components {
|
||||||
let (slot_type, locked) = match component.component_type {
|
// let (slot_type, locked) = match component.component_type {
|
||||||
RogueMagicComponentType::Passive => (3u32, false),
|
// RogueMagicComponentType::Passive => (3u32, false),
|
||||||
RogueMagicComponentType::Active => (4, true),
|
// RogueMagicComponentType::Active => (4, true),
|
||||||
RogueMagicComponentType::Attach => (5, false),
|
// RogueMagicComponentType::Attach => (5, false),
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
let slot_index = &mut index[slot_type as usize - 3];
|
// let slot_index = &mut index[slot_type as usize - 3];
|
||||||
battle_scepter.magic_list.push(BattleRogueMagicUnit {
|
// battle_scepter.magic_list.push(BattleRogueMagicUnit {
|
||||||
level: component.level,
|
// level: component.level,
|
||||||
unit_id: component.id,
|
// unit_id: component.id,
|
||||||
slot_id: *slot_index,
|
// slot_id: *slot_index,
|
||||||
locked,
|
// locked,
|
||||||
counter_map: Default::default(),
|
// counter_map: Default::default(),
|
||||||
});
|
// });
|
||||||
*slot_index += 1;
|
// *slot_index += 1;
|
||||||
*battle_scepter.trench_count.get_mut(&slot_type).unwrap() += 1;
|
// *battle_scepter.trench_count.get_mut(&slot_type).unwrap() += 1;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// battle_scepter
|
||||||
|
// })
|
||||||
|
// .collect(),
|
||||||
|
// })),
|
||||||
|
// }),
|
||||||
|
// modifier_content: Some(BattleRogueMagicModifierInfo {
|
||||||
|
// rogue_magic_battle_const: 5,
|
||||||
|
// }),
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
battle_scepter
|
// Global buffs
|
||||||
})
|
let (mut has_castorice_global_buff, mut has_silver_wolf_global_buff) = (false, false);
|
||||||
.collect(),
|
for buff in &battle_info.buff_list {
|
||||||
})),
|
if buff.id == 140703 {
|
||||||
}),
|
has_castorice_global_buff = true;
|
||||||
scepter: Some(Plgjihifpag { egmebanhhnf: 5 }),
|
}
|
||||||
});
|
if buff.id == 150602 {
|
||||||
|
has_silver_wolf_global_buff = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global Buff
|
if !has_castorice_global_buff && player.enable_castorice_global.unwrap_or(true) {
|
||||||
if !battle_info.buff_list.iter().any(|b| b.id == 140703) {
|
|
||||||
battle_info.buff_list.push(BattleBuff {
|
battle_info.buff_list.push(BattleBuff {
|
||||||
id: 140703,
|
id: 140703,
|
||||||
level: 1,
|
level: 1,
|
||||||
owner_id: u32::MAX,
|
owner_index: u32::MAX,
|
||||||
|
wave_flag: u32::MAX,
|
||||||
|
target_index_list: Vec::with_capacity(0),
|
||||||
|
dynamic_values: HashMap::with_capacity(0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has_silver_wolf_global_buff && player.enable_sw_global.unwrap_or(true) {
|
||||||
|
battle_info.buff_list.push(BattleBuff {
|
||||||
|
id: 150602,
|
||||||
|
level: 1,
|
||||||
|
owner_index: u32::MAX,
|
||||||
wave_flag: u32::MAX,
|
wave_flag: u32::MAX,
|
||||||
target_index_list: Vec::with_capacity(0),
|
target_index_list: Vec::with_capacity(0),
|
||||||
dynamic_values: HashMap::with_capacity(0),
|
dynamic_values: HashMap::with_capacity(0),
|
||||||
|
|||||||
@@ -1,18 +1,27 @@
|
|||||||
use common::structs::MultiPathAvatar;
|
use common::structs::MultiPathAvatar;
|
||||||
|
use proto::chat_data::ExtendType;
|
||||||
|
use std::path::Path;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
use crate::{net::PlayerSession, util::cur_timestamp_ms};
|
use crate::{
|
||||||
|
net::PlayerSession,
|
||||||
|
util::{self, cur_timestamp_ms},
|
||||||
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const SERVER_UID: u32 = 727;
|
const SERVER_UID: u32 = 727;
|
||||||
const SERVER_HEAD_ICON: u32 = 201402;
|
const SERVER_HEAD_ICON: u32 = 201402;
|
||||||
const SERVER_CHAT_BUBBLE_ID: u32 = 220005;
|
const SERVER_CHAT_BUBBLE_ID: u32 = 220005;
|
||||||
const SERVER_CHAT_HISTORY: [&str; 5] = [
|
const SERVER_CHAT_HISTORY: &[&str] = &[
|
||||||
|
"'lua {path_to_lua_script}' execute lua script",
|
||||||
|
"'sw {on/off}' enable/disable silver wolf global buff",
|
||||||
|
"'castorice {on/off}' enable/disable castorice global buff",
|
||||||
"'sync' to synchronize stats between json and in-game view",
|
"'sync' to synchronize stats between json and in-game view",
|
||||||
"'mc {mc_id}' mc_id can be set from 8001 to 8008",
|
"'mc {mc_id}' mc_id can be set from 8001 to 8008",
|
||||||
"'march {march_id}' march_id can be set 1001 or 1224",
|
"'march {march_id}' march_id can be set 1001 or 1224",
|
||||||
"available commands:",
|
"available commands:",
|
||||||
"visit srtools.pages.dev to configure the PS! (you configure relics, equipment, monsters from there)",
|
"visit srtools.neonteam.dev to configure the PS! (you configure relics, equipment, monsters from there)",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub async fn on_get_friend_login_info_cs_req(
|
pub async fn on_get_friend_login_info_cs_req(
|
||||||
@@ -20,6 +29,7 @@ pub async fn on_get_friend_login_info_cs_req(
|
|||||||
_req: &GetFriendLoginInfoCsReq,
|
_req: &GetFriendLoginInfoCsReq,
|
||||||
res: &mut GetFriendLoginInfoScRsp,
|
res: &mut GetFriendLoginInfoScRsp,
|
||||||
) {
|
) {
|
||||||
|
res.black_uid_list = vec![SERVER_UID];
|
||||||
res.friend_uid_list = vec![SERVER_UID];
|
res.friend_uid_list = vec![SERVER_UID];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,11 +38,11 @@ pub async fn on_get_friend_list_info_cs_req(
|
|||||||
_req: &GetFriendListInfoCsReq,
|
_req: &GetFriendListInfoCsReq,
|
||||||
res: &mut GetFriendListInfoScRsp,
|
res: &mut GetFriendListInfoScRsp,
|
||||||
) {
|
) {
|
||||||
res.friend_list = vec![FriendListInfo {
|
res.friend_list = vec![FriendSimpleInfo {
|
||||||
friend_name: String::from("RobinSR"),
|
remark_name: String::from("RobinSR"),
|
||||||
simple_info: Some(SimpleInfo {
|
player_info: Some(PlayerSimpleInfo {
|
||||||
uid: SERVER_UID,
|
uid: SERVER_UID,
|
||||||
platform_type: PlatformType::Pc.into(),
|
platform: PlatformType::Pc.into(),
|
||||||
online_status: FriendOnlineStatus::Online.into(),
|
online_status: FriendOnlineStatus::Online.into(),
|
||||||
head_icon: SERVER_HEAD_ICON,
|
head_icon: SERVER_HEAD_ICON,
|
||||||
chat_bubble_id: SERVER_CHAT_BUBBLE_ID,
|
chat_bubble_id: SERVER_CHAT_BUBBLE_ID,
|
||||||
@@ -42,7 +52,7 @@ pub async fn on_get_friend_list_info_cs_req(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
is_marked: true,
|
is_marked: true,
|
||||||
sent_time: 0,
|
create_time: 0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -53,18 +63,29 @@ pub async fn on_get_private_chat_history_cs_req(
|
|||||||
res: &mut GetPrivateChatHistoryScRsp,
|
res: &mut GetPrivateChatHistoryScRsp,
|
||||||
) {
|
) {
|
||||||
let cur_time = cur_timestamp_ms();
|
let cur_time = cur_timestamp_ms();
|
||||||
res.chat_list = SERVER_CHAT_HISTORY
|
res.chat_message_list = SERVER_CHAT_HISTORY
|
||||||
.iter()
|
.iter()
|
||||||
.map(|text| Chat {
|
.map(|text| ChatMessageData {
|
||||||
msg_type: MsgType::CustomText.into(),
|
create_time: cur_time,
|
||||||
sent_time: cur_time,
|
ckhpffenobe: Some(Eknabklpeel {
|
||||||
text: String::from(*text),
|
kpobmnlklok: 1,
|
||||||
sender_uid: SERVER_UID,
|
role_id: SERVER_UID,
|
||||||
|
}),
|
||||||
|
bkoalkhdlob: Some(Eknabklpeel {
|
||||||
|
kpobmnlklok: 1,
|
||||||
|
role_id: SERVER_UID,
|
||||||
|
}),
|
||||||
|
message_datas: vec![MessageChatData {
|
||||||
|
message_type: 1,
|
||||||
|
chat_data: Some(ChatData {
|
||||||
|
extend_type: Some(ExtendType::MessageText(text.to_string())),
|
||||||
|
}),
|
||||||
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
res.to_uid = req.to_uid;
|
res.target_side = req.target_side;
|
||||||
res.from_uid = SERVER_UID;
|
res.contact_side = SERVER_UID;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn on_send_msg_cs_req(
|
pub async fn on_send_msg_cs_req(
|
||||||
@@ -77,20 +98,66 @@ pub async fn on_send_msg_cs_req(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((cmd, args)) = parse_command(&body.text) {
|
let msg = body
|
||||||
|
.message_datas
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.chat_data.as_ref())
|
||||||
|
.and_then(|x| x.extend_type.as_ref())
|
||||||
|
.and_then(|x| match x {
|
||||||
|
ExtendType::MessageText(s) => Some(s.as_str()),
|
||||||
|
_ => Option::<&str>::None,
|
||||||
|
})
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
|
if let Some((cmd, args)) = parse_command(msg) {
|
||||||
match cmd {
|
match cmd {
|
||||||
"sync" => {
|
"sync" => {
|
||||||
session.sync_player().await;
|
let _ = session.sync_player().await;
|
||||||
session
|
session
|
||||||
.send(RevcMsgScNotify {
|
.send(create_send_message(
|
||||||
msg_type: body.msg_type,
|
25,
|
||||||
text: String::from("Inventory Synced"),
|
SERVER_UID,
|
||||||
emote: body.emote,
|
body.message_datas
|
||||||
from_uid: SERVER_UID,
|
.as_ref()
|
||||||
to_uid: 25,
|
.map(|v| v.message_type)
|
||||||
chat_type: body.chat_type,
|
.unwrap_or_default(),
|
||||||
hnbepabnbng: body.hnbepabnbng,
|
body.chat_type,
|
||||||
})
|
String::from("Inventory Synced"),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
"sw" | "castorice" => {
|
||||||
|
let status = args.first().unwrap_or(&"on").to_lowercase();
|
||||||
|
let enabled = match status.as_str() {
|
||||||
|
"on" | "1" | "true" => true,
|
||||||
|
"off" | "0" | "false" => false,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if cmd == "sw" {
|
||||||
|
json.enable_sw_global = Some(enabled);
|
||||||
|
} else {
|
||||||
|
json.enable_castorice_global = Some(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
json.save_persistent().await;
|
||||||
|
|
||||||
|
session
|
||||||
|
.send(create_send_message(
|
||||||
|
25,
|
||||||
|
SERVER_UID,
|
||||||
|
body.message_datas
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.message_type)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
body.chat_type,
|
||||||
|
format!(
|
||||||
|
"{} Global Buff: {}",
|
||||||
|
if cmd == "sw" { "SW" } else { "Castorice" },
|
||||||
|
if enabled { "Enabled" } else { "Disabled" }
|
||||||
|
),
|
||||||
|
))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -113,18 +180,19 @@ pub async fn on_send_msg_cs_req(
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
session.sync_player().await;
|
let _ = session.sync_player().await;
|
||||||
|
|
||||||
session
|
session
|
||||||
.send(RevcMsgScNotify {
|
.send(create_send_message(
|
||||||
msg_type: body.msg_type,
|
25,
|
||||||
text: format!("Success change mc to {:#?}", mc),
|
SERVER_UID,
|
||||||
emote: body.emote,
|
body.message_datas
|
||||||
from_uid: SERVER_UID,
|
.as_ref()
|
||||||
to_uid: 25,
|
.map(|v| v.message_type)
|
||||||
chat_type: body.chat_type,
|
.unwrap_or_default(),
|
||||||
hnbepabnbng: body.hnbepabnbng,
|
body.chat_type,
|
||||||
})
|
format!("Success change mc to {mc:#?}"),
|
||||||
|
))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -154,17 +222,84 @@ pub async fn on_send_msg_cs_req(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
session
|
session
|
||||||
.send(RevcMsgScNotify {
|
.send(create_send_message(
|
||||||
msg_type: body.msg_type,
|
25,
|
||||||
text: format!("Success change march to {:#?}", march_type),
|
SERVER_UID,
|
||||||
emote: body.emote,
|
body.message_datas
|
||||||
from_uid: SERVER_UID,
|
.as_ref()
|
||||||
to_uid: 25,
|
.map(|v| v.message_type)
|
||||||
chat_type: body.chat_type,
|
.unwrap_or_default(),
|
||||||
hnbepabnbng: body.hnbepabnbng,
|
body.chat_type,
|
||||||
|
format!("Success change march to {march_type:#?}"),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
"lua" => {
|
||||||
|
let path = Path::new(args.first().unwrap_or(&""));
|
||||||
|
|
||||||
|
if !path.is_file() {
|
||||||
|
session
|
||||||
|
.send(create_send_message(
|
||||||
|
25,
|
||||||
|
SERVER_UID,
|
||||||
|
body.message_datas
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.message_type)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
body.chat_type,
|
||||||
|
format!("File {path:?} does not exist!"),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = match fs::read(&path).await {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(err) => {
|
||||||
|
session
|
||||||
|
.send(create_send_message(
|
||||||
|
25,
|
||||||
|
SERVER_UID,
|
||||||
|
body.message_datas
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.message_type)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
body.chat_type,
|
||||||
|
format!("Failed to read file: {err:?}"),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
session
|
||||||
|
.send(ClientDownloadDataScNotify {
|
||||||
|
download_data: Some(ClientDownloadData {
|
||||||
|
version: 51,
|
||||||
|
time: util::cur_timestamp_ms() as i64,
|
||||||
|
data,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
session
|
||||||
|
.send(create_send_message(
|
||||||
|
25,
|
||||||
|
SERVER_UID,
|
||||||
|
body.message_datas
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.message_type)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
body.chat_type,
|
||||||
|
format!("Executed {path:?}"),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -180,3 +315,34 @@ fn parse_command(command: &str) -> Option<(&str, Vec<&str>)> {
|
|||||||
|
|
||||||
Some((parts[0], parts[1..].to_vec()))
|
Some((parts[0], parts[1..].to_vec()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_send_message(
|
||||||
|
to: u32,
|
||||||
|
from: u32,
|
||||||
|
message_type: i32,
|
||||||
|
chat_type: i32,
|
||||||
|
msg: String,
|
||||||
|
) -> RevcMsgScNotify {
|
||||||
|
RevcMsgScNotify {
|
||||||
|
chat_type,
|
||||||
|
pffpfkoglpo: to,
|
||||||
|
recv_message_data: Some(ChatMessageData {
|
||||||
|
create_time: cur_timestamp_ms(),
|
||||||
|
ckhpffenobe: Some(Eknabklpeel {
|
||||||
|
kpobmnlklok: 1,
|
||||||
|
role_id: from,
|
||||||
|
}),
|
||||||
|
bkoalkhdlob: Some(Eknabklpeel {
|
||||||
|
kpobmnlklok: 1,
|
||||||
|
role_id: from,
|
||||||
|
}),
|
||||||
|
message_datas: vec![MessageChatData {
|
||||||
|
message_type,
|
||||||
|
chat_data: Some(ChatData {
|
||||||
|
extend_type: Some(chat_data::ExtendType::MessageText(msg)),
|
||||||
|
}),
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ pub async fn on_get_gacha_info_cs_req(
|
|||||||
end_time: 1924992000,
|
end_time: 1924992000,
|
||||||
begin_time: 0,
|
begin_time: 0,
|
||||||
gacha_ceiling: Some(GachaCeiling::default()),
|
gacha_ceiling: Some(GachaCeiling::default()),
|
||||||
up_info: vec![23002, 1003, 1101, 1104, 23000, 23003],
|
prize_item_list: vec![23002, 1003, 1101, 1104, 23000, 23003],
|
||||||
featured: vec![
|
item_detail_list: vec![
|
||||||
1001, 1002, 1008, 1009, 1013, 1103, 1105, 1106, 1108, 1109, 1110, 1111, 1201, 1202,
|
1001, 1002, 1008, 1009, 1013, 1103, 1105, 1106, 1108, 1109, 1110, 1111, 1201, 1202,
|
||||||
1206, 1207, 1210, 1003, 1004, 1101, 1107, 1104, 1209, 1211, 21000, 21001, 21002, 21003,
|
1206, 1207, 1210, 1003, 1004, 1101, 1107, 1104, 1209, 1211, 21000, 21001, 21002, 21003,
|
||||||
21004, 21005, 21006, 21007, 21008, 21009, 21010, 21011, 21012, 21013, 21014, 21015,
|
21004, 21005, 21006, 21007, 21008, 21009, 21010, 21011, 21012, 21013, 21014, 21015,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use common::sr_tools::FreesrData;
|
use common::{resources::GAME_RES, 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;
|
||||||
|
|
||||||
use super::UNLOCKED_AVATARS;
|
use super::BASE_AVATAR_IDS;
|
||||||
|
|
||||||
pub async fn on_get_bag_cs_req(
|
pub async fn on_get_bag_cs_req(
|
||||||
session: &mut PlayerSession,
|
session: &mut PlayerSession,
|
||||||
@@ -109,21 +109,22 @@ pub async fn on_get_big_data_all_recommend_cs_req(
|
|||||||
match req.big_data_recommend_type() {
|
match req.big_data_recommend_type() {
|
||||||
BigDataRecommendType::RelicAvatar => {
|
BigDataRecommendType::RelicAvatar => {
|
||||||
res.recommend_type = Some(RecommendType::RelicAvatar(BigDataRecommendRelicAvatar {
|
res.recommend_type = Some(RecommendType::RelicAvatar(BigDataRecommendRelicAvatar {
|
||||||
recommended_avatar_info_list: UNLOCKED_AVATARS
|
recommended_avatar_info_list: GAME_RES
|
||||||
.into_iter()
|
.relic_avatar_recommend
|
||||||
.map(|recommend_avatar_id| RecomendedAvatarInfo {
|
.iter()
|
||||||
avatar_id_list: vec![],
|
.map(|(set_id, avatar_list)| RecomendedAvatarInfo {
|
||||||
recommend_avatar_id,
|
avatar_id_list: avatar_list.clone(),
|
||||||
relic_set_id: 0,
|
recommend_avatar_id: avatar_list.first().copied().unwrap_or_default(),
|
||||||
|
relic_set_id: *set_id,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
BigDataRecommendType::AvatarRelic => {
|
BigDataRecommendType::AvatarRelic => {
|
||||||
res.recommend_type = Some(RecommendType::AvatarRelic(BigDataRecommendAvatarRelic {
|
res.recommend_type = Some(RecommendType::AvatarRelic(BigDataRecommendAvatarRelic {
|
||||||
recomended_relic_info_list: UNLOCKED_AVATARS
|
recomended_relic_info_list: BASE_AVATAR_IDS
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|avatar_id| RecommendedRelicInfo {
|
.map(|avatar_id| BigDataAvatarRelicRecommend {
|
||||||
avatar_id,
|
avatar_id,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
@@ -235,7 +236,7 @@ fn equip_relic(player: &mut FreesrData, req: &DressRelicAvatarCsReq) -> Option<P
|
|||||||
let mut avatar_ids_to_sy = vec![];
|
let mut avatar_ids_to_sy = vec![];
|
||||||
let mut relic_index_to_sync = vec![];
|
let mut relic_index_to_sync = vec![];
|
||||||
|
|
||||||
for param in &req.param_list {
|
for param in &req.switch_list {
|
||||||
let Some(target_relic_idx) = player
|
let Some(target_relic_idx) = player
|
||||||
.relics
|
.relics
|
||||||
.iter()
|
.iter()
|
||||||
@@ -244,10 +245,9 @@ fn equip_relic(player: &mut FreesrData, req: &DressRelicAvatarCsReq) -> Option<P
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let cur_avatar_relic_idx = player
|
let cur_avatar_relic_idx = player.relics.iter().position(|r| {
|
||||||
.relics
|
r.equip_avatar == target_avatar.avatar_id && r.get_slot() == param.relic_type
|
||||||
.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
|
// 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 {
|
if let Some(cur_avatar_relic_idx) = cur_avatar_relic_idx {
|
||||||
@@ -288,7 +288,7 @@ fn unequip_relic(player: &mut FreesrData, req: &TakeOffRelicCsReq) -> Option<Pla
|
|||||||
|
|
||||||
let mut relic_index_to_sync = vec![];
|
let mut relic_index_to_sync = vec![];
|
||||||
|
|
||||||
for slot in &req.slot_list {
|
for slot in &req.relic_type_list {
|
||||||
let relics = player
|
let relics = player
|
||||||
.relics
|
.relics
|
||||||
.iter()
|
.iter()
|
||||||
@@ -324,13 +324,20 @@ fn build_sync(
|
|||||||
) {
|
) {
|
||||||
let avatar_list = avatar_ids
|
let avatar_list = avatar_ids
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|id| player.get_avatar_proto(*id))
|
.filter_map(|id| {
|
||||||
|
player.get_avatar_proto(*id, player.main_character as u32, player.march_type as u32)
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
ret.avatar_sync = Some(AvatarSync { avatar_list });
|
|
||||||
ret.multi_path_avatar_type_info_list = avatar_ids
|
let avatar_path_data_info_list = avatar_ids
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|id| player.get_avatar_multipath_proto(id))
|
.filter_map(|id| player.get_avatar_path_data_proto(id))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
ret.avatar_sync = Some(AvatarSync {
|
||||||
|
avatar_list,
|
||||||
|
avatar_path_data_info_list,
|
||||||
|
});
|
||||||
ret.relic_list = relic_indexes
|
ret.relic_list = relic_indexes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|id| (&player.relics[id]).into())
|
.map(|id| (&player.relics[id]).into())
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ pub async fn on_replace_lineup_cs_req(
|
|||||||
|
|
||||||
let lineups = &mut player.lineups;
|
let lineups = &mut player.lineups;
|
||||||
for (slot, avatar_id) in &mut *lineups {
|
for (slot, avatar_id) in &mut *lineups {
|
||||||
if let Some(lineup) = req.slots.get(*slot as usize) {
|
if let Some(lineup) = req.lineup_slot_list.get(*slot as usize) {
|
||||||
*avatar_id = lineup.id;
|
*avatar_id = lineup.id;
|
||||||
} else {
|
} else {
|
||||||
*avatar_id = 0;
|
*avatar_id = 0;
|
||||||
@@ -131,15 +131,15 @@ async fn refresh_lineup(session: &mut PlayerSession) {
|
|||||||
|
|
||||||
session
|
session
|
||||||
.send(SceneGroupRefreshScNotify {
|
.send(SceneGroupRefreshScNotify {
|
||||||
group_refresh_info: vec![SceneGroupRefreshInfo {
|
group_refresh_list: vec![GroupRefreshInfo {
|
||||||
group_id: 0,
|
group_id: 0,
|
||||||
state: 0,
|
state: 0,
|
||||||
group_refresh_type: SceneGroupRefreshType::Loaded.into(),
|
refresh_type: SceneGroupRefreshType::Loaded.into(),
|
||||||
refresh_entity: new_entities,
|
refresh_entity: new_entities,
|
||||||
bccgjihncdn: Vec::with_capacity(0),
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
floor_id,
|
floor_id,
|
||||||
gfhglffhfbd: 0,
|
dimension_id: 0,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub async fn on_get_mail_cs_req(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
mail_type: MailType::Normal.into(),
|
mail_type: 0,
|
||||||
template_id: 0,
|
template_id: 0,
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ pub async fn on_get_mission_status_cs_req(
|
|||||||
body: &GetMissionStatusCsReq,
|
body: &GetMissionStatusCsReq,
|
||||||
res: &mut GetMissionStatusScRsp,
|
res: &mut GetMissionStatusScRsp,
|
||||||
) {
|
) {
|
||||||
res.finished_main_mission_id_list = body.main_mission_id_list.clone();
|
res.finished_main_mission_id_list = body.sub_mission_id_list.clone();
|
||||||
res.sub_mission_status_list = body
|
res.sub_mission_status_list = body
|
||||||
.sub_mission_id_list
|
.main_mission_id_list
|
||||||
.iter()
|
.iter()
|
||||||
.map(|id| Mission {
|
.map(|id| Mission {
|
||||||
id: *id,
|
id: *id,
|
||||||
|
|||||||
@@ -29,30 +29,6 @@ pub use mission::*;
|
|||||||
pub use player::*;
|
pub use player::*;
|
||||||
pub use scene::*;
|
pub use scene::*;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use proto::{
|
|
||||||
CmdActivityType::*, CmdAdventureType::*, CmdAetherDivideType::*, CmdAlleyType::*,
|
|
||||||
CmdArchiveType::*, CmdAvatarType::*, CmdBattleCollegeType::*, CmdBattlePassType::*,
|
|
||||||
CmdBattleType::*, CmdBoxingClubType::*, CmdChallengeType::*, CmdChatType::*,
|
|
||||||
CmdChessRogueType::*, CmdClockParkType::*, CmdContentPackageType::*, CmdDailyActiveType::*,
|
|
||||||
CmdDrinkMakerType::*, CmdExpeditionType::*, CmdFantasticStoryActivityType::*,
|
|
||||||
CmdFeverTimeActivityType::*, CmdFightActivityType::*, CmdFightMathc3Type::*, CmdFightType::*,
|
|
||||||
CmdFriendType::*, CmdGachaType::*, CmdHeartdialType::*, CmdHeliobusType::*, CmdItemType::*,
|
|
||||||
CmdJukeboxType::*, CmdLineupType::*, CmdLobbyType::*, CmdMailType::*, CmdMapRotationType::*,
|
|
||||||
CmdMatchThreeModuleType::*, CmdMatchType::*, CmdMessageType::*, CmdMiscModuleType::*,
|
|
||||||
CmdMissionType::*, CmdMonopolyType::*, CmdMultiplayerType::*, CmdMultipleDropType::*,
|
|
||||||
CmdMuseumType::*, CmdOfferingType::*, CmdPamMissionType::*, CmdPhoneType::*,
|
|
||||||
CmdPlayerBoardType::*, CmdPlayerReturnType::*, CmdPlayerSync::*, CmdPlayerType::*,
|
|
||||||
CmdPlotType::*, CmdPunkLordType::*, CmdQuestType::*, CmdRaidCollectionType::*, CmdRaidType::*,
|
|
||||||
CmdRechargeGiftType::*, CmdRecommendType::*, CmdRedDotType::*, CmdReplayType::*,
|
|
||||||
CmdRndOptionType::*, CmdRogueCommonType::*, CmdRogueEndless::*, CmdRogueModifierType::*,
|
|
||||||
CmdRogueTournType::*, CmdRogueType::*, CmdRollShopType::*, CmdSceneType::*,
|
|
||||||
CmdServerPrefsType::*, CmdShopType::*, CmdSpaceZooType::*, CmdStarFightType::*,
|
|
||||||
CmdStoryLineType::*, CmdStrongChallengeActivityType::*, CmdTalkRewardType::*,
|
|
||||||
CmdTelevisionActivityType::*, CmdTextJoinType::*, CmdTrainVisitorType::*,
|
|
||||||
CmdTreasureDungeonType::*, CmdTutorialType::*, CmdWaypointType::*, CmdWolfBroType::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
macro_rules! dummy {
|
macro_rules! dummy {
|
||||||
($($cmd:ident),* $(,)*) => {
|
($($cmd:ident),* $(,)*) => {
|
||||||
paste! {
|
paste! {
|
||||||
@@ -60,7 +36,7 @@ macro_rules! dummy {
|
|||||||
pub const fn should_send_dummy_rsp(cmd_id: u16) -> bool {
|
pub const fn should_send_dummy_rsp(cmd_id: u16) -> bool {
|
||||||
match cmd_id {
|
match cmd_id {
|
||||||
$(
|
$(
|
||||||
x if x == [<Cmd $cmd CsReq>] as u16 => true,
|
x if x == <proto::[<$cmd CsReq>] as proto::CmdID>::CMD_ID => true,
|
||||||
)*
|
)*
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@@ -69,7 +45,9 @@ macro_rules! dummy {
|
|||||||
pub async fn send_dummy_response(&mut self, req_id: u16) -> Result<()> {
|
pub async fn send_dummy_response(&mut self, req_id: u16) -> Result<()> {
|
||||||
let cmd_type = match req_id {
|
let cmd_type = match req_id {
|
||||||
$(
|
$(
|
||||||
x if x == [<Cmd $cmd CsReq>] as u16 => [<Cmd $cmd ScRsp>] as u16,
|
x if x == <proto::[<$cmd CsReq>] as proto::CmdID>::CMD_ID => {
|
||||||
|
<proto::[<$cmd ScRsp>] as proto::CmdID>::CMD_ID
|
||||||
|
},
|
||||||
)*
|
)*
|
||||||
_ => return Err(anyhow::anyhow!("Invalid request id {req_id:?}")),
|
_ => return Err(anyhow::anyhow!("Invalid request id {req_id:?}")),
|
||||||
};
|
};
|
||||||
@@ -90,7 +68,7 @@ macro_rules! dummy {
|
|||||||
dummy! {
|
dummy! {
|
||||||
// SceneEntityMove,
|
// SceneEntityMove,
|
||||||
GetLevelRewardTakenList,
|
GetLevelRewardTakenList,
|
||||||
GetRogueScoreRewardInfo,
|
// GetRogueScoreRewardInfo, // ?3.7.51
|
||||||
// GetGachaInfo,
|
// GetGachaInfo,
|
||||||
QueryProductInfo,
|
QueryProductInfo,
|
||||||
GetQuestData,
|
GetQuestData,
|
||||||
@@ -98,33 +76,33 @@ dummy! {
|
|||||||
// GetFriendListInfo,
|
// GetFriendListInfo,
|
||||||
// GetFriendApplyListInfo,
|
// GetFriendApplyListInfo,
|
||||||
GetCurAssist,
|
GetCurAssist,
|
||||||
GetRogueHandbookData,
|
// GetRogueHandbookData, // ?3.7.51
|
||||||
GetDailyActiveInfo,
|
GetDailyActiveInfo,
|
||||||
GetFightActivityData,
|
GetFightActivityData,
|
||||||
GetMultipleDropInfo,
|
// GetMultipleDropInfo, // ?3.7.51
|
||||||
GetPlayerReturnMultiDropInfo,
|
// GetPlayerReturnMultiDropInfo, // ?3.7.51
|
||||||
GetShareData,
|
// GetShareData, // ?3.7.51
|
||||||
GetTreasureDungeonActivityData,
|
// GetTreasureDungeonActivityData, // ?3.7.51
|
||||||
PlayerReturnInfoQuery,
|
// PlayerReturnInfoQuery, // ?3.7.51
|
||||||
// GetBag,
|
// GetBag,
|
||||||
GetPlayerBoardData,
|
GetPlayerBoardData,
|
||||||
GetActivityScheduleConfig,
|
GetActivityScheduleConfig,
|
||||||
GetMissionData,
|
GetMissionData,
|
||||||
GetChallenge,
|
GetChallenge,
|
||||||
GetCurChallenge,
|
GetCurChallenge,
|
||||||
GetRogueInfo,
|
// GetRogueInfo, // ?3.7.51
|
||||||
GetExpeditionData,
|
GetExpeditionData,
|
||||||
// GetRogueDialogueEventData,
|
// GetRogueDialogueEventData,
|
||||||
GetJukeboxData,
|
GetJukeboxData,
|
||||||
SyncClientResVersion,
|
SyncClientResVersion,
|
||||||
DailyFirstMeetPam,
|
// DailyFirstMeetPam, // ?3.7.51
|
||||||
GetMuseumInfo,
|
// GetMuseumInfo, // ?3.7.51
|
||||||
GetLoginActivity,
|
GetLoginActivity,
|
||||||
GetRaidInfo,
|
GetRaidInfo,
|
||||||
GetTrialActivityData,
|
GetTrialActivityData,
|
||||||
GetBoxingClubInfo,
|
// GetBoxingClubInfo, // ?3.7.51
|
||||||
GetNpcStatus,
|
GetNpcStatus,
|
||||||
TextJoinQuery,
|
// TextJoinQuery, // ?3.7.51
|
||||||
// GetSpringRecoverData, // Removed 2.7.51
|
// GetSpringRecoverData, // Removed 2.7.51
|
||||||
// GetChatFriendHistory,
|
// GetChatFriendHistory,
|
||||||
GetSecretKeyInfo,
|
GetSecretKeyInfo,
|
||||||
@@ -134,6 +112,12 @@ dummy! {
|
|||||||
// PlayerLoginFinish,
|
// PlayerLoginFinish,
|
||||||
InteractProp,
|
InteractProp,
|
||||||
FinishTalkMission,
|
FinishTalkMission,
|
||||||
GetRechargeGiftInfo
|
GetRechargeGiftInfo,
|
||||||
// RelicRecommend
|
// RelicRecommend
|
||||||
|
GetPreAvatarGrowthInfo,
|
||||||
|
GetPreAvatarActivityList,
|
||||||
|
// GetUnreleasedBlockInfo,
|
||||||
|
GetFriendAssistList,
|
||||||
|
GetAssistList,
|
||||||
|
B51RacingGetData
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub async fn on_get_basic_info_cs_req(
|
pub async fn on_get_basic_info_cs_req(
|
||||||
@@ -22,8 +20,8 @@ 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("BAEwB0dldFR5cGUIR2V0RmllbGQIR2V0VmFsdWUCQ1MGU3lzdGVtC0NvbGxlY3Rpb25zB0dlbmVyaWMETGlzdAZTdHJpbmcCY24DQWRkAmVuAmtyAmpwB1RvQXJyYXkDUlBHBkNsaWVudApHbG9iYWxWYXJzFXNfTG9jYWxpemF0aW9uTWFuYWdlchRfYWxsb3dlZExhbmd1YWdlS2V5cxFfYWxsb3dlZEF1ZGlvS2V5cwhHYW1lQ29yZRlBbGxvd2VkTGFuZ3VhZ2VFeGNlbFRhYmxlCGRhdGFEaWN0BXBhaXJzDExhbmd1YWdlTGlzdAtVbml0eUVuZ2luZQtBcHBsaWNhdGlvbgdNQVhfRlBTD3RhcmdldEZyYW1lUmF0ZQ9RdWFsaXR5U2V0dGluZ3MKdlN5bmNDb3VudApHYW1lT2JqZWN0BEZpbmQoVUlSb290L0Fib3ZlRGlhbG9nL0JldGFIaW50RGlhbG9nKENsb25lKQ1Mb2NhbGl6ZWRUZXh0BnR5cGVvZhZHZXRDb21wb25lbnRJbkNoaWxkcmVuB0JFVEFfV00EdGV4dAtWZXJzaW9uVGV4dCI8Y29sb3I9IzAwZTFmZj5Sb2JpblNSISB8IDwvY29sb3I+Dzxjb2xvcj0jMDBlMWZmPg1zX1ZlcnNpb25EYXRhF0dldFNlcnZlclBha1R5cGVWZXJzaW9uCDwvY29sb3I+BnhwY2FsbApJU19QQVRDSEVECQEBAAAAAAEWAAEAAAACAAAABgIAAAAADRQCABsAAAAAFQICAgYEAQAEBT4AFAICkQEAAAAVAgQCBgQAABQCAvMCAAAAFQIDABYCAAADAwEDAgMDAAQAAAAFAAAAAAAfDAIDAAIEAMAPAQL5BAAAAA8AAesFAAAADAEHAAYEAMAVAAICBgEAABUBAQIFBAgAFAIBXwkAAAAVAgMBBQQKABQCAV8JAAAAFQIDAQUECwAUAgFfCQAAABUCAwEFBAwAFAIBXwkAAAAVAgMBFAIBOQ0AAAAVAgIAFgIAAA4DBAMFAwYEAgQAwAMHAwgDCQQGBADAAwoDCwMMAw0DDgMPAAsAAAAGAAAAAAA/DAIDAAIEAMAPAQKsBAAAAA8AAaAFAAAAFAEAGwYAAAAVAQICBQMHAAQEPgAUAQGRCAAAABUBBAIGAwAAFAEB8wkAAAAVAQMCBQQKABQCAV8LAAAAFQIDAQUEDAAUAgFfCwAAABUCAwEFBA0AFAIBXwsAAAAVAgMBBQQOABQCAV8LAAAAFQIDARQCABsGAAAAFQICAgUEDwAEBT4AFAICkQgAAAAVAgQCBgQAABQCAvMJAAAAFQIDAgUFCgAUAwJfCwAAABUDAwEFBQwAFAMCXwsAAAAVAwMBBQUNABQDAl8LAAAAFQMDAQUFDgAUAwJfCwAAABUDAwEWAAEAEAMEAxADEQQCBADAAxIDEwMBAxQDAgMDAwoDCwMMAw0DDgMVABQAAAAHAAEAAAASDAIDAAIEAMAPAQLwBAAAAA8AAbkFAAAACQEAABUBAQIMAgcAAABgQAYDAAAVAgIEQQICABABBjIIAAAAPgL9/wIAAAAWAAEACQMEAxADFgQCBADAAxcDGAMZBAAAYEADGgAiAAAAAgAAAAAADAwAAwACBADABwEAygQAAAAQAQADBQAAAAwABwAGBADABAEAABABANAIAAAAFgABAAkDBAMbAxwEAgQAwAMdAx4DHwQGBADAAyAALAAAAAkAAAAAADcMAQMAAgQAwA8AAXYEAAAABQEFABUAAgIMBAgABxgAwA8DBBQJAAAATSwDAgwCCwAAAKBAFQICAhQAALEMAAAAFQADAgwBDgAAANBAEAEA1Q8AAAAMAQMAAgQAwA8AAXYEAAAABQEQABUAAgIMBAgABxgAwA8DBBQJAAAATSwDAgwCCwAAAKBAFQICAhQAALEMAAAAFQADAgUCEQAFAxIADAgIAAcYAMAPBwisEwAAAA8GByMUAAAAFAYGkRUAAAAVBgICBgQGAAUFFgA1AQIFEAEA1Q8AAAAWAAEAFwMEAxsDIQQCBADAAyIDIwMQAxEEBxgAwAMkAyUEAACgQAMmAycEAADQQAMoAykDKgMrAxIDLAMtAy4AMQAAAAMABQAAACQHAADKAAAAAFEABAAAAACABABoAQgAAMoAAAAADAACAAAAEEAJAQAACQIBABUAAwEMAAIAAAAQQAkBAgAJAgEAFQADAQcAACsDAAAAUgACAAEAAIAWAAEAAwABAAgAACsDAAAADAACAAAAEEAJAQMACQIBABUAAwEMAAIAAAAQQAkBBAAJAgEAFQADARYAAQAEAx0DLwQAABBAAzAAPwAAAAsAAAEAABVFAAAARAAAAEQBAQBEAgIARAMDAEQEBABKAAIARAUFAEQGBgBEBwcASgAFAEoAAABKAAYASgAEAEoAAwAMCAkAAACAQAYJBwAGCgAAFQgDARYAAQAKBgAGAQYCBgMGBAYFBgYGBwMvBAAAgEAIAAECAwQFBgcBAAAACA==").unwrap(),
|
data: rbase64::decode("BAEcA2xvZwJDUwtVbml0eUVuZ2luZQtBcHBsaWNhdGlvbg90YXJnZXRGcmFtZVJhdGUPUXVhbGl0eVNldHRpbmdzCnZTeW5jQ291bnQIcGF0Y2hGcHMKR2FtZU9iamVjdARGaW5kKFVJUm9vdC9BYm92ZURpYWxvZy9CZXRhSGludERpYWxvZyhDbG9uZSkDUlBHBkNsaWVudA1Mb2NhbGl6ZWRUZXh0BnR5cGVvZhZHZXRDb21wb25lbnRJbkNoaWxkcmVuJjxjb2xvcj0jMDBlMWZmPnQubWUvbmVvbnRlYW0yNTwvY29sb3I+BHRleHQLVmVyc2lvblRleHQiPGNvbG9yPSMwMGUxZmY+Um9iaW5TUiEgfCA8L2NvbG9yPg88Y29sb3I9IzAwZTFmZj4KR2xvYmFsVmFycw1zX1ZlcnNpb25EYXRhF0dldFNlcnZlclBha1R5cGVWZXJzaW9uCDwvY29sb3I+C3BhdGNoQmV0YVdtBG1haW4GeHBjYWxsBQEBAAAAAAEWAAEAAAAEAQEYAAUAAAAAAgAAAAAACwwAAwACBADABAFoARABAAMEAAAADAAGAAUEAMAEAQAAEAEA0AcAAAAWAAEACAMCAwMDBAQCBADAAwUDBgQFBADAAwcABwgBGAAAAAAAAQAAAAABCAAAAAAJAAAAAAA2DAEDAAIEAMAPAAF2BAAAAAUBBQAVAAICDAQIAAcYAMAPAwQUCQAAAE0sAwIMAgsAAACgQBUCAgAUAACxDAAAABUAAAIFAQ0AEAEA1Q4AAAAMAQMAAgQAwA8AAXYEAAAABQEPABUAAgIMBAgABxgAwA8DBBQJAAAATSwDAgwCCwAAAKBAFQICABQAALEMAAAAFQAAAgUCEAAFAxEADAgIAAcYAMAPBwisEgAAAA8GByMTAAAAFAYGkRQAAAAVBgICBgQGAAUFFQA1AQIFEAEA1Q4AAAAWAAEAFgMCAwMDCQQCBADAAwoDCwMMAw0EBxgAwAMOAw8EAACgQAMQAxEDEgMTAxQDFQMWAxcDGAMZAAwaARgAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAQEBAAAAAAAAAAAAAQD8AAUOAAAAAAEAAQAAAAMJAAAAFQABARYAAQAAABkbARgAAAEaAAAAAAcAAAEAAAxFAAAARAAAAEQBAQBEAgIARAMDAEoAAgAMBAUAAABAQAYFAwAGBgAAFQQDARYAAQAGBgAGAQYCBgMDHAQAAEBABAABAgMBAAEYAAMDBQ0ABAAAAAAAAQAAAAAE").unwrap(),
|
||||||
haehhcpoapp: 0
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,15 +32,18 @@ pub async fn on_player_login_finish_cs_req(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
session
|
session
|
||||||
.send(ContentPackageSyncDataScNotify {
|
.send(ContentPackageSyncDataScNotify {
|
||||||
data: Some(PackageData {
|
data: Some(ContentPackageData {
|
||||||
info_list: [
|
content_package_list: [
|
||||||
200001, 200002, 200003, 200004, 150017, 150015, 150021, 150018, 130011, 130012,
|
200001, 200002, 200003, 200004, 200005, 200006, 200007, 200008, 200009, 200010,
|
||||||
130013,
|
200011, 200012, 150017, 150015, 150021, 150018, 130011, 130012, 130013, 150025,
|
||||||
|
140006, 150026, 130014, 150034, 150029, 150035, 150041, 150039, 150045, 150057,
|
||||||
|
150042, 150067, 150064, 150063, 150024, 171002, 150068, 150070, 150071, 150073,
|
||||||
|
150074, 150075, 150076, 150077, 150078, 150079,
|
||||||
]
|
]
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|v| ContentInfo {
|
.map(|v| ContentPackageInfo {
|
||||||
status: ContentPackageStatus::Finished.into(),
|
status: ContentPackageStatus::Finished.into(),
|
||||||
content_id: *v,
|
content_id: v,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -52,21 +53,3 @@ pub async fn on_player_login_finish_cs_req(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn on_get_multi_path_avatar_info_cs_req(
|
|
||||||
session: &mut PlayerSession,
|
|
||||||
_req: &GetMultiPathAvatarInfoCsReq,
|
|
||||||
res: &mut GetMultiPathAvatarInfoScRsp,
|
|
||||||
) {
|
|
||||||
let Some(json) = session.json_data.get() else {
|
|
||||||
tracing::error!("data is not set!");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
res.current_multi_path_avatar_id = HashMap::from([
|
|
||||||
(8001, json.main_character.get_type().into()),
|
|
||||||
(1001, json.march_type.get_type().into()),
|
|
||||||
]);
|
|
||||||
|
|
||||||
res.multi_path_avatar_type_info_list = json.get_multi_path_info();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ pub async fn on_enter_scene_cs_req(
|
|||||||
req: &EnterSceneCsReq,
|
req: &EnterSceneCsReq,
|
||||||
res: &mut EnterSceneScRsp,
|
res: &mut EnterSceneScRsp,
|
||||||
) {
|
) {
|
||||||
if load_scene(session, req.entry_id, true, Some(req.teleport_id))
|
if load_scene(session, req.entry_id, true, Some(req.entry_id2))
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
res.retcode = Retcode::RetSceneEntryIdNotMatch as u32;
|
res.retcode = 2605;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,34 +55,33 @@ pub async fn on_get_scene_map_info_cs_req(
|
|||||||
req: &GetSceneMapInfoCsReq,
|
req: &GetSceneMapInfoCsReq,
|
||||||
res: &mut GetSceneMapInfoScRsp,
|
res: &mut GetSceneMapInfoScRsp,
|
||||||
) {
|
) {
|
||||||
for floor_id in &req.floor_id_list {
|
for floor_id in req.scene_identifiers.iter().map(|v| v.floor_id) {
|
||||||
let mut map_info = MazeMapData {
|
let mut map_info = SceneMapInfo {
|
||||||
retcode: 0,
|
chest_list: vec![
|
||||||
unlocked_chest_list: vec![
|
ChestInfo {
|
||||||
MazeChest {
|
chest_type: 101,
|
||||||
map_info_chest_type: MapInfoChestType::Normal.into(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
MazeChest {
|
ChestInfo {
|
||||||
map_info_chest_type: MapInfoChestType::Puzzle.into(),
|
chest_type: 102,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
MazeChest {
|
ChestInfo {
|
||||||
map_info_chest_type: MapInfoChestType::Challenge.into(),
|
chest_type: 104,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
floor_id: *floor_id,
|
floor_id: floor_id,
|
||||||
|
scene_identifier: Some(SceneIdentifier {
|
||||||
|
floor_id,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
for i in 0..100 {
|
let floor_configs = GAME_RES
|
||||||
map_info.lighten_section_list.push(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
let group_config = GAME_RES
|
|
||||||
.map_default_entrance_map
|
.map_default_entrance_map
|
||||||
.get(floor_id)
|
.get(&floor_id)
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
GAME_RES
|
GAME_RES
|
||||||
.level_output_configs
|
.level_output_configs
|
||||||
@@ -90,28 +89,46 @@ pub async fn on_get_scene_map_info_cs_req(
|
|||||||
.and_then(|v| v.iter().next())
|
.and_then(|v| v.iter().next())
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((_, group_config)) = group_config {
|
if let Some((_, floor_config)) = floor_configs {
|
||||||
for (group_id, group) in group_config.scenes.iter() {
|
for (group_id, group) in floor_config.scenes.iter() {
|
||||||
map_info.maze_group_list.push(MazeGroup {
|
map_info.group_list.push(MapInfoGroup {
|
||||||
group_id: *group_id,
|
group_id: *group_id,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
for teleport in group.teleports.keys() {
|
for teleport in group.teleports.keys() {
|
||||||
map_info.unlocked_teleport_list.push(*teleport)
|
map_info.unlock_teleport_list.push(*teleport)
|
||||||
}
|
}
|
||||||
|
|
||||||
for prop in &group.props {
|
for prop in &group.props {
|
||||||
map_info.maze_prop_list.push(MazeProp {
|
map_info.map_info_prop_list.push(MazePropState {
|
||||||
group_id: prop.group_id,
|
group_id: prop.group_id,
|
||||||
state: prop.prop_state,
|
state: prop.prop_state,
|
||||||
config_id: prop.inst_id,
|
config_id: prop.inst_id,
|
||||||
|
extra_info: Option::<PropExtraInfo>::None,
|
||||||
});
|
});
|
||||||
}
|
// map_info.maze_group_list.push(MazeGroup {
|
||||||
|
// group_id: prop.group_id,
|
||||||
|
// state: prop.prop_state,
|
||||||
|
// config_id: prop.inst_id,
|
||||||
|
// extra_info: Option::None,
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.map_list.push(map_info)
|
map_info.lighten_section_list = floor_config.sections.clone();
|
||||||
|
map_info.floor_saved_value_map = floor_config.saved_values.clone();
|
||||||
|
// #TODO!
|
||||||
|
// map_info
|
||||||
|
// .chest_unlock_progress_list
|
||||||
|
// .push(ChestUnlockProgress {
|
||||||
|
// r#type: 0,
|
||||||
|
// total_chest_count: 25,
|
||||||
|
// unlocked_chest_count: 25,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.scene_map_info_list.push(map_info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +174,7 @@ pub async fn on_get_entered_scene_cs_req(
|
|||||||
_req: &GetEnteredSceneCsReq,
|
_req: &GetEnteredSceneCsReq,
|
||||||
res: &mut GetEnteredSceneScRsp,
|
res: &mut GetEnteredSceneScRsp,
|
||||||
) {
|
) {
|
||||||
res.entered_scene_info = GAME_RES
|
res.entered_scene_info_list = GAME_RES
|
||||||
.level_output_configs
|
.level_output_configs
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(_, v)| {
|
.flat_map(|(_, v)| {
|
||||||
@@ -235,6 +252,36 @@ async fn load_scene(
|
|||||||
} else {
|
} else {
|
||||||
scene.world_id
|
scene.world_id
|
||||||
},
|
},
|
||||||
|
lighten_section_list: scene.sections.clone(),
|
||||||
|
opened_chests_list: scene
|
||||||
|
.scenes
|
||||||
|
.values()
|
||||||
|
.flat_map(|v| v.chests.clone())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
scene_mission_info: Some(MissionStatusBySceneInfo {
|
||||||
|
finished_main_mission_id_list: scene
|
||||||
|
.scenes
|
||||||
|
.values()
|
||||||
|
.flat_map(|s| s.finished_main_missions.clone())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
sub_mission_status_list: scene
|
||||||
|
.scenes
|
||||||
|
.values()
|
||||||
|
.flat_map(|s| {
|
||||||
|
s.finished_sub_missions.iter().map(|sm| Mission {
|
||||||
|
id: *sm,
|
||||||
|
status: MissionStatus::MissionFinish.into(),
|
||||||
|
progress: 0,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
floor_saved_data: scene.saved_values.clone(),
|
||||||
|
scene_identifier: Some(SceneIdentifier {
|
||||||
|
floor_id,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -353,18 +400,19 @@ async fn load_scene(
|
|||||||
|
|
||||||
scene_info.entity_group_list.push(group_info);
|
scene_info.entity_group_list.push(group_info);
|
||||||
|
|
||||||
scene_info.group_state_list.push(SceneGroupState {
|
// TODO: ?
|
||||||
group_id: *group_id,
|
// scene_info.group_state_list.push(SceneGroupState {
|
||||||
is_default: true,
|
// group_id: *group_id,
|
||||||
state: 0,
|
// is_default: true,
|
||||||
});
|
// state: 0,
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
// load player entity
|
// load player entity
|
||||||
scene_info.entity_group_list.push(SceneEntityGroupInfo {
|
scene_info.entity_group_list.push(SceneEntityGroupInfo {
|
||||||
state: 0,
|
state: 0,
|
||||||
group_id: 0,
|
group_id: 0,
|
||||||
hejamoojbcj: HashMap::with_capacity(0),
|
banbecdcdhg: HashMap::with_capacity(0),
|
||||||
entity_list: json
|
entity_list: json
|
||||||
.lineups
|
.lineups
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -3,29 +3,6 @@ use paste::paste;
|
|||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
|
|
||||||
use proto::*;
|
use proto::*;
|
||||||
#[allow(unused_imports)]
|
|
||||||
use proto::{
|
|
||||||
CmdActivityType::*, CmdAdventureType::*, CmdAetherDivideType::*, CmdAlleyType::*,
|
|
||||||
CmdArchiveType::*, CmdAvatarType::*, CmdBattleCollegeType::*, CmdBattlePassType::*,
|
|
||||||
CmdBattleType::*, CmdBoxingClubType::*, CmdChallengeType::*, CmdChatType::*,
|
|
||||||
CmdChessRogueType::*, CmdClockParkType::*, CmdContentPackageType::*, CmdDailyActiveType::*,
|
|
||||||
CmdDrinkMakerType::*, CmdExpeditionType::*, CmdFantasticStoryActivityType::*,
|
|
||||||
CmdFeverTimeActivityType::*, CmdFightActivityType::*, CmdFightMathc3Type::*, CmdFightType::*,
|
|
||||||
CmdFriendType::*, CmdGachaType::*, CmdHeartdialType::*, CmdHeliobusType::*, CmdItemType::*,
|
|
||||||
CmdJukeboxType::*, CmdLineupType::*, CmdLobbyType::*, CmdMailType::*, CmdMapRotationType::*,
|
|
||||||
CmdMatchThreeModuleType::*, CmdMatchType::*, CmdMessageType::*, CmdMiscModuleType::*,
|
|
||||||
CmdMissionType::*, CmdMonopolyType::*, CmdMultiplayerType::*, CmdMultipleDropType::*,
|
|
||||||
CmdMuseumType::*, CmdOfferingType::*, CmdPamMissionType::*, CmdPhoneType::*,
|
|
||||||
CmdPlayerBoardType::*, CmdPlayerReturnType::*, CmdPlayerSync::*, CmdPlayerType::*,
|
|
||||||
CmdPlotType::*, CmdPunkLordType::*, CmdQuestType::*, CmdRaidCollectionType::*, CmdRaidType::*,
|
|
||||||
CmdRecommendType::*, CmdRedDotType::*, CmdReplayType::*, CmdRndOptionType::*,
|
|
||||||
CmdRogueCommonType::*, CmdRogueEndless::*, CmdRogueModifierType::*, CmdRogueTournType::*,
|
|
||||||
CmdRogueType::*, CmdRollShopType::*, CmdSceneType::*, CmdServerPrefsType::*, CmdShopType::*,
|
|
||||||
CmdSpaceZooType::*, CmdStarFightType::*, CmdStoryLineType::*,
|
|
||||||
CmdStrongChallengeActivityType::*, CmdTalkRewardType::*, CmdTelevisionActivityType::*,
|
|
||||||
CmdTextJoinType::*, CmdTrainVisitorType::*, CmdTreasureDungeonType::*, CmdTutorialType::*,
|
|
||||||
CmdWaypointType::*, CmdWolfBroType::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::PlayerSession;
|
use super::PlayerSession;
|
||||||
use super::handlers::*;
|
use super::handlers::*;
|
||||||
@@ -138,27 +115,27 @@ macro_rules! trait_handler {
|
|||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
async fn on_message(session: &mut PlayerSession, cmd_type: u16, payload: Vec<u8>) -> Result<()> {
|
async fn on_message(session: &mut PlayerSession, cmd_id: u16, payload: Vec<u8>) -> Result<()> {
|
||||||
use ::prost::Message;
|
use ::prost::Message;
|
||||||
if PlayerSession::should_send_dummy_rsp(cmd_type) {
|
if PlayerSession::should_send_dummy_rsp(cmd_id) {
|
||||||
session.send_dummy_response(cmd_type).await?;
|
session.send_dummy_response(cmd_id).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
match cmd_type {
|
match cmd_id {
|
||||||
$(
|
$(
|
||||||
cmd_type if cmd_type == paste! { [<Cmd$name CsReq>] as u16 } => {
|
cmd_id if cmd_id == paste! { <proto::[<$name CsReq>] as proto::CmdID>::CMD_ID } => {
|
||||||
let body = paste! { proto::[<$name CsReq>]::decode(&mut &payload[..])? };
|
let body = paste! { proto::[<$name CsReq>]::decode(&mut &payload[..])? };
|
||||||
paste! {
|
paste! {
|
||||||
Self::[<on_$name:snake _cs_req>](session, &body)
|
Self::[<on_$name:snake _cs_req>](session, &body)
|
||||||
.instrument(tracing::info_span!(stringify!([<on_$name:snake>]), cmd_type = cmd_type))
|
.instrument(tracing::info_span!(stringify!([<on_$name:snake>]), cmd_id = cmd_id))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
_ => {
|
_ => {
|
||||||
tracing::warn!("Unknown command type: {cmd_type}");
|
tracing::warn!("Unknown command ID: {cmd_id}");
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -172,13 +149,13 @@ trait_handler! {
|
|||||||
PlayerLogin;
|
PlayerLogin;
|
||||||
GetMissionStatus;
|
GetMissionStatus;
|
||||||
GetBasicInfo;
|
GetBasicInfo;
|
||||||
GetMultiPathAvatarInfo;
|
|
||||||
GetAvatarData;
|
GetAvatarData;
|
||||||
GetAllLineupData;
|
GetAllLineupData;
|
||||||
GetCurLineupData;
|
GetCurLineupData;
|
||||||
GetCurSceneInfo;
|
GetCurSceneInfo;
|
||||||
PlayerHeartBeat;
|
PlayerHeartBeat;
|
||||||
|
SetAvatarEnhancedId;
|
||||||
|
TakePromotionReward;
|
||||||
|
|
||||||
// Entity move (dummy!)
|
// Entity move (dummy!)
|
||||||
SceneEntityMove;
|
SceneEntityMove;
|
||||||
@@ -209,6 +186,7 @@ trait_handler! {
|
|||||||
PveBattleResult;
|
PveBattleResult;
|
||||||
SceneCastSkill;
|
SceneCastSkill;
|
||||||
QuickStartCocoonStage;
|
QuickStartCocoonStage;
|
||||||
|
SceneEnterStage;
|
||||||
|
|
||||||
// Teleport
|
// Teleport
|
||||||
GetEnteredScene;
|
GetEnteredScene;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use tokio::{
|
|||||||
sync::{Mutex, watch},
|
sync::{Mutex, watch},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util;
|
use crate::{net::handlers::BASE_AVATAR_IDS, util};
|
||||||
|
|
||||||
use super::{NetPacket, packet::CommandHandler};
|
use super::{NetPacket, packet::CommandHandler};
|
||||||
|
|
||||||
@@ -41,10 +41,12 @@ impl PlayerSession {
|
|||||||
let (shutdown_tx, shutdown_rx) = watch::channel(());
|
let (shutdown_tx, shutdown_rx) = watch::channel(());
|
||||||
Self {
|
Self {
|
||||||
token,
|
token,
|
||||||
kcp: Arc::new(Mutex::new(Kcp::new(conv, token, false, RemoteEndPoint {
|
kcp: Arc::new(Mutex::new(Kcp::new(
|
||||||
socket,
|
conv,
|
||||||
addr,
|
token,
|
||||||
}))),
|
false,
|
||||||
|
RemoteEndPoint { socket, addr },
|
||||||
|
))),
|
||||||
start_time: util::cur_timestamp_secs(),
|
start_time: util::cur_timestamp_secs(),
|
||||||
json_data: OnceLock::new(),
|
json_data: OnceLock::new(),
|
||||||
shutdown_rx,
|
shutdown_rx,
|
||||||
@@ -114,68 +116,90 @@ impl PlayerSession {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sync_player(&self) {
|
pub async fn sync_player(&self) -> Result<()> {
|
||||||
let Some(json) = self.json_data.get() else {
|
let Some(json) = self.json_data.get() else {
|
||||||
tracing::error!("data is not init!");
|
tracing::error!("data is not init!");
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
// clear relics & lightcones
|
// clear relics & lightcones
|
||||||
self.send(PlayerSyncScNotify {
|
self.send(PlayerSyncScNotify {
|
||||||
del_equipment_list: (2000..=3500).collect(),
|
del_relic_list: (1..=3000).collect(),
|
||||||
del_relic_list: (1..=2000).collect(),
|
del_equipment_list: (3001..=3500).collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Sync avatars
|
// clear all avatars equip item
|
||||||
self.send(PlayerSyncScNotify {
|
self.send(PlayerSyncScNotify {
|
||||||
avatar_sync: Some(AvatarSync {
|
avatar_sync: Some(AvatarSync {
|
||||||
avatar_list: json
|
avatar_list: BASE_AVATAR_IDS
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|id| {
|
||||||
|
json.avatars.get(&id).map(|v| {
|
||||||
|
v.to_avatar_proto(
|
||||||
|
Option::None,
|
||||||
|
json.main_character as u32,
|
||||||
|
json.march_type as u32,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
avatar_path_data_info_list: json
|
||||||
.avatars
|
.avatars
|
||||||
.values()
|
.values()
|
||||||
.map(|avatar| avatar.to_avatar_proto(Option::None, vec![]))
|
.map(|avatar| {
|
||||||
|
avatar.to_avatar_path_data_proto(Option::None, Vec::with_capacity(0))
|
||||||
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
}),
|
}),
|
||||||
multi_path_avatar_type_info_list: json.get_multi_path_info(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Sync new relics
|
// Sync new relics & lightcones
|
||||||
self.send(PlayerSyncScNotify {
|
self.send(PlayerSyncScNotify {
|
||||||
relic_list: json.relics.iter().map(|v| v.into()).collect(),
|
relic_list: json.relics.iter().map(|v| v.into()).collect(),
|
||||||
equipment_list: json.lightcones.iter().map(|v| v.into()).collect(),
|
equipment_list: json.lightcones.iter().map(|v| v.into()).collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Sync new lightcones
|
// Sync new avatars equip item
|
||||||
self.send(PlayerSyncScNotify {
|
self.send(PlayerSyncScNotify {
|
||||||
avatar_sync: Some(AvatarSync {
|
avatar_sync: Some(AvatarSync {
|
||||||
avatar_list: json
|
avatar_list: BASE_AVATAR_IDS
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|avatar_id| {
|
||||||
|
json.avatars.get(&avatar_id).map(|v| {
|
||||||
|
v.to_avatar_proto(
|
||||||
|
json.lightcones.iter().find(|v| v.equip_avatar == avatar_id),
|
||||||
|
json.main_character as u32,
|
||||||
|
json.march_type as u32,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
avatar_path_data_info_list: json
|
||||||
.avatars
|
.avatars
|
||||||
.values()
|
.values()
|
||||||
.map(|avatar| {
|
.map(|avatar| {
|
||||||
avatar.to_avatar_proto(
|
avatar.to_avatar_path_data_proto(
|
||||||
json.lightcones
|
json.lightcones
|
||||||
.iter()
|
.iter()
|
||||||
.find(|v| v.equip_avatar == avatar.avatar_id),
|
.find(|v| v.equip_avatar == avatar.avatar_id),
|
||||||
json.relics
|
json.relics
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|v| v.equip_avatar == avatar.avatar_id)
|
.filter(|r| r.equip_avatar == avatar.avatar_id)
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect::<Vec<_>>(),
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn session_time(&self) -> u64 {
|
fn session_time(&self) -> u64 {
|
||||||
|
|||||||
+2
-6
@@ -16,7 +16,7 @@ use bytes::{Buf, BufMut, BytesMut};
|
|||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||||
|
|
||||||
use crate::{error::Error, KcpResult};
|
use crate::{KcpResult, error::Error};
|
||||||
|
|
||||||
const KCP_RTO_NDL: u32 = 20; // no delay min rto
|
const KCP_RTO_NDL: u32 = 20; // no delay min rto
|
||||||
const KCP_RTO_MIN: u32 = 100; // normal min rto
|
const KCP_RTO_MIN: u32 = 100; // normal min rto
|
||||||
@@ -528,11 +528,7 @@ impl<Output> Kcp<Output> {
|
|||||||
self.rx_srtt = rtt;
|
self.rx_srtt = rtt;
|
||||||
self.rx_rttval = rtt / 2;
|
self.rx_rttval = rtt / 2;
|
||||||
} else {
|
} else {
|
||||||
let delta = if rtt > self.rx_srtt {
|
let delta = rtt.abs_diff(self.rx_srtt);
|
||||||
rtt - self.rx_srtt
|
|
||||||
} else {
|
|
||||||
self.rx_srtt - rtt
|
|
||||||
};
|
|
||||||
self.rx_rttval = (3 * self.rx_rttval + delta) / 4;
|
self.rx_rttval = (3 * self.rx_rttval + delta) / 4;
|
||||||
self.rx_srtt = ((7 * u64::from(self.rx_srtt) + u64::from(rtt)) / 8) as u32;
|
self.rx_srtt = ((7 * u64::from(self.rx_srtt) + u64::from(rtt)) / 8) as u32;
|
||||||
if self.rx_srtt < 1 {
|
if self.rx_srtt < 1 {
|
||||||
|
|||||||
+2
-2
@@ -7,11 +7,11 @@ mod kcp;
|
|||||||
|
|
||||||
/// The `KCP` prelude
|
/// The `KCP` prelude
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::{get_conv, Kcp, KCP_OVERHEAD};
|
pub use super::{KCP_OVERHEAD, Kcp, get_conv};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use kcp::{get_conv, get_sn, set_conv, Kcp, KCP_OVERHEAD};
|
pub use kcp::{KCP_OVERHEAD, Kcp, get_conv, get_sn, set_conv};
|
||||||
|
|
||||||
/// KCP result
|
/// KCP result
|
||||||
pub type KcpResult<T> = Result<T, Error>;
|
pub type KcpResult<T> = Result<T, Error>;
|
||||||
|
|||||||
+15
-13
@@ -1,21 +1,23 @@
|
|||||||
{
|
{
|
||||||
"lineups": {
|
"lineups": {
|
||||||
"0": 1403,
|
"0": 1513,
|
||||||
"1": 1407,
|
"1": 1308,
|
||||||
"2": 8001,
|
"2": 1403,
|
||||||
"3": 1405
|
"3": 1409
|
||||||
},
|
},
|
||||||
"position": {
|
"position": {
|
||||||
"x": -26968,
|
"x": 674182,
|
||||||
"y": 78953,
|
"y": 75562,
|
||||||
"z": 14457,
|
"z": -16482,
|
||||||
"rot_y": 11858
|
"rot_y": 204639
|
||||||
},
|
},
|
||||||
"scene": {
|
"scene": {
|
||||||
"plane_id": 20411,
|
"plane_id": 20511,
|
||||||
"floor_id": 20411001,
|
"floor_id": 20511001,
|
||||||
"entry_id": 2041101
|
"entry_id": 2051101
|
||||||
},
|
},
|
||||||
"main_character": "FemaleRememberance",
|
"main_character": "FemaleElation",
|
||||||
"march_type": "MarchHunt"
|
"march_type": "MarchHunt",
|
||||||
|
"enable_sw_global": true,
|
||||||
|
"enable_castorice_global": true
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
+12344
-58815
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{ToTokens, quote};
|
||||||
use syn::{parse_macro_input, DeriveInput, Meta, MetaList};
|
use syn::{DeriveInput, Meta, MetaList, parse_macro_input};
|
||||||
|
|
||||||
#[proc_macro_derive(CmdID, attributes(cmdid))]
|
#[proc_macro_derive(CmdID, attributes(cmdid))]
|
||||||
pub fn message_id_derive(input: TokenStream) -> TokenStream {
|
pub fn message_id_derive(input: TokenStream) -> TokenStream {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
ignore = [
|
||||||
|
"proto/"
|
||||||
|
]
|
||||||
@@ -9,6 +9,7 @@ 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
|
||||||
|
|
||||||
# JSON
|
# JSON
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|||||||
@@ -1,27 +1,127 @@
|
|||||||
use std::{collections::HashMap, sync::OnceLock};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use anyhow::{Result, anyhow};
|
||||||
|
use prost::Message as _;
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn instance() -> &'static HashMap<String, VersionConfig> {
|
impl VersionConfig {
|
||||||
static INSTANCE: OnceLock<HashMap<String, VersionConfig>> = OnceLock::new();
|
const CNPROD_HOST: &str = "prod-gf-cn-dp01.bhsr.com";
|
||||||
INSTANCE.get_or_init(|| {
|
const CNBETA_HOST: &str = "beta-release01-cn.bhsr.com";
|
||||||
|
const OSPROD_HOST: &str = "prod-official-asia-dp01.starrails.com";
|
||||||
|
const OSBETA_HOST: &str = "beta-release01-asia.starrails.com";
|
||||||
|
|
||||||
|
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";
|
const CONFIG_PATH: &str = "versions.json";
|
||||||
|
|
||||||
let data = std::fs::read_to_string(CONFIG_PATH).unwrap_or_else(|_| {
|
let data = match fs::read_to_string(CONFIG_PATH).await {
|
||||||
std::fs::write(CONFIG_PATH, DEFAULT_VERSIONS).expect("Failed to create versions file");
|
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()
|
||||||
});
|
}
|
||||||
|
};
|
||||||
|
|
||||||
serde_json::from_str(&data).unwrap_or_else(|e| panic!("Failed to parse versions.json: {e}"))
|
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.asset_bundle_url.is_empty() && decoded.ex_resource_url.is_empty() {
|
||||||
|
return Err(anyhow::format_err!(
|
||||||
|
"asset_bundle_url and ex_resource_url are empty! 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()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-1
@@ -1,9 +1,15 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use axum::http::Method;
|
use axum::http::Method;
|
||||||
use axum::http::header::CONTENT_TYPE;
|
use axum::http::header::CONTENT_TYPE;
|
||||||
use axum::routing::{get, post};
|
use axum::routing::{get, post};
|
||||||
|
use config::version_config::VersionConfig;
|
||||||
use services::{auth, dispatch, errors, sr_tools};
|
use services::{auth, dispatch, errors, sr_tools};
|
||||||
|
use tokio::fs;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
@@ -12,9 +18,51 @@ mod services;
|
|||||||
|
|
||||||
const PORT: u16 = 21000;
|
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<()> {
|
pub async fn start_sdkserver() -> Result<()> {
|
||||||
let span = tracing::span!(Level::DEBUG, "main");
|
let span = tracing::span!(Level::DEBUG, "main");
|
||||||
let _ = span.enter();
|
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()
|
let router = Router::new()
|
||||||
.route(
|
.route(
|
||||||
@@ -42,16 +90,22 @@ pub async fn start_sdkserver() -> Result<()> {
|
|||||||
sr_tools::SRTOOLS_UPLOAD_ENDPOINT,
|
sr_tools::SRTOOLS_UPLOAD_ENDPOINT,
|
||||||
post(sr_tools::sr_tool_save),
|
post(sr_tools::sr_tool_save),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
auth::APN_LOGIN_BY_PASSWORD_ENDPOINT,
|
||||||
|
post(auth::apn_login_with_password),
|
||||||
|
)
|
||||||
|
.route(auth::APN_VERIFY_TOKEN_ENDPOINT, post(auth::apn_veriy_token))
|
||||||
.layer(
|
.layer(
|
||||||
CorsLayer::new()
|
CorsLayer::new()
|
||||||
.allow_origin(Any)
|
.allow_origin(Any)
|
||||||
.allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE])
|
.allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE])
|
||||||
.allow_headers([CONTENT_TYPE]),
|
.allow_headers([CONTENT_TYPE]),
|
||||||
)
|
)
|
||||||
|
.with_state(state)
|
||||||
.fallback(errors::not_found);
|
.fallback(errors::not_found);
|
||||||
|
|
||||||
let addr = format!("0.0.0.0:{PORT}");
|
let addr = format!("0.0.0.0:{PORT}");
|
||||||
let server = axum_server::bind(addr.parse()?);
|
let server = axum_server::bind(addr.parse::<std::net::SocketAddr>()?);
|
||||||
|
|
||||||
tracing::info!("sdkserver is listening at {addr}");
|
tracing::info!("sdkserver is listening at {addr}");
|
||||||
server.serve(router.into_make_service()).await?;
|
server.serve(router.into_make_service()).await?;
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ use serde_json::json;
|
|||||||
|
|
||||||
pub const LOGIN_WITH_PASSWORD_ENDPOINT: &str = "/{product_name}/mdk/shield/api/login";
|
pub const LOGIN_WITH_PASSWORD_ENDPOINT: &str = "/{product_name}/mdk/shield/api/login";
|
||||||
pub const LOGIN_WITH_SESSION_TOKEN_ENDPOINT: &str = "/{product_name}/mdk/shield/api/verify";
|
pub const LOGIN_WITH_SESSION_TOKEN_ENDPOINT: &str = "/{product_name}/mdk/shield/api/verify";
|
||||||
pub const GRANTER_LOGIN_VERIFICATION_ENDPOINT: &str = "/{product_name}/combo/granter/login/v2/login";
|
pub const GRANTER_LOGIN_VERIFICATION_ENDPOINT: &str =
|
||||||
|
"/{product_name}/combo/granter/login/v2/login";
|
||||||
pub const RISKY_API_CHECK_ENDPOINT: &str = "/account/risky/api/check";
|
pub const RISKY_API_CHECK_ENDPOINT: &str = "/account/risky/api/check";
|
||||||
|
pub const APN_LOGIN_BY_PASSWORD_ENDPOINT: &str = "/account/ma-cn-passport/app/loginByPassword";
|
||||||
|
pub const APN_VERIFY_TOKEN_ENDPOINT: &str = "/account/ma-cn-session/app/verify";
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn login_with_password() -> Json<serde_json::Value> {
|
pub async fn login_with_password() -> Json<serde_json::Value> {
|
||||||
@@ -78,3 +81,51 @@ pub async fn risky_api_check() -> Json<serde_json::Value> {
|
|||||||
"retcode": 0
|
"retcode": 0
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn apn_login_with_password() -> Json<serde_json::Value> {
|
||||||
|
Json(json!({
|
||||||
|
"data": {
|
||||||
|
"token": {
|
||||||
|
"token": "mostsecuretokenever",
|
||||||
|
"token_type": 1
|
||||||
|
},
|
||||||
|
"user_info": {
|
||||||
|
"aid": "1337",
|
||||||
|
"mid": "1337",
|
||||||
|
"is_email_verify": 1,
|
||||||
|
"area_code": "**",
|
||||||
|
"country": "US",
|
||||||
|
"is_adult": 1,
|
||||||
|
"email": "motorized@wheel.chair"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"message": "OK",
|
||||||
|
"retcode": 0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn apn_veriy_token() -> Json<serde_json::Value> {
|
||||||
|
Json(json!({
|
||||||
|
"data": {
|
||||||
|
"tokens": [
|
||||||
|
{
|
||||||
|
"token": "mostsecuretokenever",
|
||||||
|
"token_type": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"user_info": {
|
||||||
|
"aid": "1337",
|
||||||
|
"mid": "1337",
|
||||||
|
"is_email_verify": 1,
|
||||||
|
"area_code": "**",
|
||||||
|
"country": "US",
|
||||||
|
"is_adult": 1,
|
||||||
|
"email": "motorized@wheel.chair"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"message": "OK",
|
||||||
|
"retcode": 0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
use crate::config::version_config;
|
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::{Dispatch, GateServer, RegionInfo};
|
||||||
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";
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn query_dispatch() -> String {
|
pub async fn query_dispatch() -> String {
|
||||||
let rsp = DispatchRegionData {
|
let rsp = Dispatch {
|
||||||
retcode: 0,
|
retcode: 0,
|
||||||
region_list: vec![RegionEntry {
|
region_list: vec![RegionInfo {
|
||||||
name: String::from("RobinSR"),
|
name: String::from("RobinSR"),
|
||||||
title: String::from("RobinSR"),
|
title: String::from("RobinSR"),
|
||||||
env_type: String::from("22"),
|
env_type: String::from("9"),
|
||||||
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,37 +34,35 @@ 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) = version_config::instance().get(¶meters.version) {
|
State(state): State<Arc<RwLock<AppState>>>,
|
||||||
Gateserver {
|
parameters: Query<QueryGatewayParameters>,
|
||||||
retcode: 0,
|
) -> String {
|
||||||
|
let mut lock = state.write().await;
|
||||||
|
let config = lock
|
||||||
|
.get_or_insert_hotfix(¶meters.version, ¶meters.dispatch_seed)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let rsp = GateServer {
|
||||||
ip: String::from("127.0.0.1"),
|
ip: String::from("127.0.0.1"),
|
||||||
port: 23301,
|
port: 23301,
|
||||||
asset_bundle_url: config.asset_bundle_url.clone(),
|
asset_bundle_url: config.asset_bundle_url.clone(),
|
||||||
|
asset_bundle_url_android: config.asset_bundle_url.clone(),
|
||||||
ex_resource_url: config.ex_resource_url.clone(),
|
ex_resource_url: config.ex_resource_url.clone(),
|
||||||
lua_url: config.lua_url.clone(),
|
lua_url: config.lua_url.clone(),
|
||||||
ifix_version: String::from("0"),
|
ifix_version: String::from("0"),
|
||||||
enable_design_data_version_update: true,
|
unk1: true,
|
||||||
enable_version_update: true,
|
unk2: true,
|
||||||
enable_upload_battle_log: true,
|
unk3: true,
|
||||||
network_diagnostic: true,
|
unk4: true,
|
||||||
close_redeem_code: true,
|
unk5: true,
|
||||||
enable_android_middle_package: true,
|
unk6: true,
|
||||||
enable_watermark: true,
|
unk7: true,
|
||||||
event_tracking_open: true,
|
|
||||||
enable_cdn_ipv6: 1,
|
|
||||||
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();
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ pub async fn sr_tool_save(Json(json): Json<SrToolDataReq>) -> Json<SrToolDataRsp
|
|||||||
Ok(json) => json,
|
Ok(json) => json,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Json(SrToolDataRsp {
|
return Json(SrToolDataRsp {
|
||||||
message: format!("malformed json: {}", err),
|
message: format!("malformed json: {err}"),
|
||||||
status: 500,
|
status: 500,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -43,8 +43,7 @@ pub async fn sr_tool_save(Json(json): Json<SrToolDataReq>) -> Json<SrToolDataRsp
|
|||||||
if let Err(err) = fs::write(&path, json).await {
|
if let Err(err) = fs::write(&path, json).await {
|
||||||
return Json(SrToolDataRsp {
|
return Json(SrToolDataRsp {
|
||||||
message: format!(
|
message: format!(
|
||||||
"failed to write freesr-data.json: {} at path: {:#?} env: {:#?}",
|
"failed to write freesr-data.json: {err} at path: {path:#?} env: {env:#?}"
|
||||||
err, path, env
|
|
||||||
),
|
),
|
||||||
status: 500,
|
status: 500,
|
||||||
});
|
});
|
||||||
|
|||||||
+45
-47
@@ -1,58 +1,56 @@
|
|||||||
{
|
{
|
||||||
"OSBETAWin3.1.51": {
|
"CNBETAWin3.5.55": {
|
||||||
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966",
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_11797411_74704086c081_d40a71b41ba492",
|
||||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
|
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_11823033_c9e8c870bcc7_b78f37c36487d0",
|
||||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_11798340_8c6543a1d6c5_2d60cd9b1584b8",
|
||||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
|
||||||
},
|
},
|
||||||
"CNBETAWin3.1.51": {
|
"CNBETAWin3.6.51": {
|
||||||
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966",
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
|
||||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
|
"ex_resource_url": "https://autopatchcn-bhsr.neonteam.dev/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
|
||||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
|
||||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
|
||||||
},
|
},
|
||||||
"CNBETAAndroid3.1.51": {
|
"CNBETAWin3.6.52": {
|
||||||
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966",
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12150127_00d6d096d968_cd76a04beb7ba6",
|
||||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
|
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12178965_e246796e0bb6_05bcce36cd648b",
|
||||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12150614_5279f6d8029a_bdecff99d2d817",
|
||||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12164593_8e3fba5163df_b2b6fc46de4c06"
|
||||||
},
|
},
|
||||||
|
"CNBETAWin3.7.51": {
|
||||||
"OSBETAWin3.1.52": {
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12579793_48327ff319b5_1b794dd1071e3a",
|
||||||
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9614772_b674c1e08556",
|
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12611332_5f583f2f54ae_c04979f13c950d",
|
||||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9614804_4a927001828b",
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12579929_9566349ee5fb_c6341faaf9b027",
|
||||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9615588_bd4aff54bc1a",
|
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
|
||||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
|
||||||
},
|
},
|
||||||
"CNBETAWin3.1.52": {
|
"OSBETAWin3.8.51": {
|
||||||
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9614772_b674c1e08556",
|
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_13490885_bf25e1553b38_e9adc23a6b6710",
|
||||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9614804_4a927001828b",
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_13532881_eb1de9bedabd_c74ab2513059ad",
|
||||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9615588_bd4aff54bc1a",
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_13493894_ec7d6d879305_8b2cfd39d6e545",
|
||||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_13491130_ecfa51828143_4bd221b76c0d9a"
|
||||||
},
|
},
|
||||||
"CNBETAAndroid3.1.52": {
|
"OSBETAWin4.0.51": {
|
||||||
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9614772_b674c1e08556",
|
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_14026982_55a517a26691_89300298bf2b2e",
|
||||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9614804_4a927001828b",
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_14036546_28a134460a9a_5805431f75f0ff",
|
||||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9615588_bd4aff54bc1a",
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_14028456_7088b08fd431_59194837d90977",
|
||||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
|
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
|
||||||
},
|
},
|
||||||
|
"CNBETAWin4.1.51": {
|
||||||
"CNBETAWin3.1.53": {
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_14372365_fa78e03ad599_36972b4e3bb553",
|
||||||
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_9697258_e86620fd557e",
|
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_14372319_01febed213ae_b1f343e62353c2",
|
||||||
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_9706936_f0d9c0308622",
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_14358961_e055a63b3c34_b199a0234548b5",
|
||||||
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_9685341_7378b1f100ee",
|
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_14374543_04d971175704_ab00cb3f54bb00"
|
||||||
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253"
|
|
||||||
},
|
},
|
||||||
"OSBETAWin3.1.53": {
|
"CNBETAWin4.2.51": {
|
||||||
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_9697258_e86620fd557e",
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_14851990_9aa8ee8de0fa_ec7d1e42f851f1",
|
||||||
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_9706936_f0d9c0308622",
|
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_14890490_fcc969a5461f_1f9258ee9068a5",
|
||||||
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_9685341_7378b1f100ee",
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_14852119_8789f55c1c30_0c0cf5d3c9089b",
|
||||||
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253"
|
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_14887825_65010135064b_71536d98b8abbf"
|
||||||
},
|
},
|
||||||
"CNBETAAndroid3.1.53": {
|
"OSBETAWin4.4.51": {
|
||||||
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_9697258_e86620fd557e",
|
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15711835_1986f76c287e_9c3d058458edf0",
|
||||||
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_9706936_f0d9c0308622",
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15753142_3bb8238470fc_a7cec92c089da7",
|
||||||
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_9685341_7378b1f100ee",
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15712109_65eeab393c89_0c116c2699518d",
|
||||||
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253"
|
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user