Compare commits
10 commits
Author | SHA1 | Date | |
---|---|---|---|
|
a051391f5b | ||
|
a3e0be4117 | ||
|
26c0111a10 | ||
|
5cf7c4f6f7 | ||
|
26b54248a6 | ||
|
04b9d60ab4 | ||
87bb318d63 | |||
6c3e82adec | |||
7b4479b191 | |||
66ad49dcb2 |
19 changed files with 293 additions and 124 deletions
28
README.md
28
README.md
|
@ -3,6 +3,10 @@
|
||||||
A Server emulator for the game [`Honkai: Star Rail`](https://hsr.hoyoverse.com/en-us/)
|
A Server emulator for the game [`Honkai: Star Rail`](https://hsr.hoyoverse.com/en-us/)
|
||||||
![screenshot](https://git.xeondev.com/reversedrooms/FireflySR/raw/branch/master/screenshot.png)
|
![screenshot](https://git.xeondev.com/reversedrooms/FireflySR/raw/branch/master/screenshot.png)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- [MongoDB](https://www.mongodb.com/try/download/community)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### From Source
|
### From Source
|
||||||
|
@ -10,7 +14,8 @@ A Server emulator for the game [`Honkai: Star Rail`](https://hsr.hoyoverse.com/e
|
||||||
#### Requirements
|
#### Requirements
|
||||||
|
|
||||||
- [Rust](https://www.rust-lang.org/tools/install)
|
- [Rust](https://www.rust-lang.org/tools/install)
|
||||||
- [MongoDB](https://www.mongodb.com/try/download/community)
|
- [MongoDB](https://www.mongodb.com/try/download/community-edition)
|
||||||
|
- [Protobuf Compiler (protoc)](https://github.com/protocolbuffers/protobuf/releases/download/v26.1/protoc-26.1-win64.zip). You should manually extract it to somewhere and add `bin` subfolder to the system environment variable `PATH`.
|
||||||
|
|
||||||
#### Building
|
#### Building
|
||||||
|
|
||||||
|
@ -54,12 +59,29 @@ By default, servers will try to use local mongodb (at 127.0.0.1:27017),
|
||||||
this should work out of box if you installed MongoDB on your machine.
|
this should work out of box if you installed MongoDB on your machine.
|
||||||
<br>
|
<br>
|
||||||
You can change this in configuration file of specific server.
|
You can change this in configuration file of specific server.
|
||||||
Currently only 2 servers communicate with database, so if you **need** to configure it,
|
Currently only sdkserver and gameserver communicate with database, so if you **need** to configure it,
|
||||||
edit sdkserver.json and gameserver.json files.
|
edit sdkserver.json and gameserver.json files.
|
||||||
|
|
||||||
## Connecting
|
## Connecting
|
||||||
|
|
||||||
[Get 2.3 beta client](https://autopatchos.starrails.com/client/Beta/20240501125700_dUBAjS7YiX9nF7mJ/StarRail_2.2.51.zip),
|
### For the latest 2.3 Beta patch
|
||||||
|
|
||||||
|
If you want to play the latest Beta patch, the `mhypbase.dll` patch for 2.2.51 OS is not usable. You should use a separate proxy like [FireflySR.Tool.Proxy](https://git.xeondev.com/YYHEggEgg/FireflySR.Tool.Proxy) (Prebuilt binary can be downloaded [here](https://git.xeondev.com/YYHEggEgg/FireflySR.Tool.Proxy/releases)).
|
||||||
|
|
||||||
|
Also, you need to configure `disable_password_check` in `sdkserver.json`. That is to say, if you have used a older version, you should go to the configuration file **in the root directory** and check if it's `true`.
|
||||||
|
|
||||||
|
Clients (2.3 Beta v4):
|
||||||
|
|
||||||
|
- [OS - 2.2.54](https://autopatchos.starrails.com/client/Beta/20240524111944_IhuuEu6NfLJtObzr/StarRail_2.2.54.zip)
|
||||||
|
- [CN - 2.2.54](https://autopatchcn.bhsr.com/client/beta/20240524111603_TZHPxvCZGt1eRV0w/StarRail_2.2.54.zip)
|
||||||
|
- [OS hdiff - 2.2.53-2.2.54](https://autopatchos.starrails.com/client/hkrpg_global/game_2.2.53_2.2.54_hdiff_vTcPduNiNYcyOqnd.zip)
|
||||||
|
- [CN hdiff - 2.2.53-2.2.54](https://autopatchcn.bhsr.com/client/hkrpg_cn/game_2.2.53_2.2.54_hdiff_GUdpMZLRReCpnJdX.zip)
|
||||||
|
|
||||||
|
Notice: **CN Package may not have languages other than Chinese, and vice versa**. Be careful when choosing which to download; luckily they are all compatiable with this server.
|
||||||
|
|
||||||
|
### For v1 Players
|
||||||
|
|
||||||
|
[Get 2.3 beta client v1(.51)](https://autopatchos.starrails.com/client/Beta/20240501125700_dUBAjS7YiX9nF7mJ/StarRail_2.2.51.zip),
|
||||||
replace [mhypbase.dll](https://git.xeondev.com/reversedrooms/FireflySR/raw/branch/master/mhypbase.dll)
|
replace [mhypbase.dll](https://git.xeondev.com/reversedrooms/FireflySR/raw/branch/master/mhypbase.dll)
|
||||||
file in your game folder, it will redirect game traffic (and disable in-game censorship)
|
file in your game folder, it will redirect game traffic (and disable in-game censorship)
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,20 @@ pub struct RuntimeGroupInstanceInfo {
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct RuntimeGroupInfo {
|
pub struct RuntimeGroupInfo {
|
||||||
|
pub load_on_initial: bool,
|
||||||
|
pub load_side: GroupLoadSide,
|
||||||
pub group_name: Option<String>,
|
pub group_name: Option<String>,
|
||||||
pub anchor_list: Option<Vec<LevelAnchorInfo>>,
|
pub anchor_list: Option<Vec<LevelAnchorInfo>>,
|
||||||
pub prop_list: Option<Vec<LevelPropInfo>>,
|
pub prop_list: Option<Vec<LevelPropInfo>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize, PartialEq, Eq)]
|
||||||
|
pub enum GroupLoadSide {
|
||||||
|
#[default]
|
||||||
|
Client,
|
||||||
|
Server,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize)]
|
#[derive(Default, Deserialize)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
|
@ -17,6 +17,30 @@
|
||||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_7033392_aaca9c1b456b",
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_7033392_aaca9c1b456b",
|
||||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_7050564_f05a0f949b10",
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_7050564_f05a0f949b10",
|
||||||
"lua_version": "7050564"
|
"lua_version": "7050564"
|
||||||
|
},
|
||||||
|
"CNBETAWin2.2.53": {
|
||||||
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_7128256_5f77b249238a",
|
||||||
|
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_7134377_b1f36fb2d9b8",
|
||||||
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_7120090_469169697c23",
|
||||||
|
"lua_version": "7120090"
|
||||||
|
},
|
||||||
|
"OSBETAWin2.2.53": {
|
||||||
|
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_7128256_5f77b249238a",
|
||||||
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_7134377_b1f36fb2d9b8",
|
||||||
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_7120090_469169697c23",
|
||||||
|
"lua_version": "7120090"
|
||||||
|
},
|
||||||
|
"CNBETAWin2.2.54": {
|
||||||
|
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_7165870_7fb7dbd8a7a4",
|
||||||
|
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_7171381_bb763a07e196",
|
||||||
|
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_7165994_1569306165f0",
|
||||||
|
"lua_version": "7165994"
|
||||||
|
},
|
||||||
|
"OSBETAWin2.2.54": {
|
||||||
|
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_7165870_7fb7dbd8a7a4",
|
||||||
|
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_7171381_bb763a07e196",
|
||||||
|
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_7165994_1569306165f0",
|
||||||
|
"lua_version": "7165994"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
61
gameserver/src/game/commands/avatar.rs
Normal file
61
gameserver/src/game/commands/avatar.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
use common::data::EXCEL_COLLECTION;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub async fn max_traces(args: &[&str], session: &PlayerSession) -> Result<()> {
|
||||||
|
let Some(Ok(avatar_id)) = args.get(0).map(|s| s.parse::<u32>()) else {
|
||||||
|
return send_text(session, "Usage: /avatar max_traces [avatar_id]").await;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut player_info = session.context.player.borrow_mut();
|
||||||
|
let avatar_comp = player_info.data.avatar_bin.as_mut().unwrap();
|
||||||
|
|
||||||
|
let Some(avatar) = avatar_comp
|
||||||
|
.avatar_list
|
||||||
|
.iter_mut()
|
||||||
|
.find(|a| a.avatar_id == avatar_id)
|
||||||
|
else {
|
||||||
|
return send_text(session, &format!("Avatar {avatar_id} doesn't exist")).await;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXCEL_COLLECTION
|
||||||
|
.avatar_skill_tree_configs
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.avatar_id == avatar_id)
|
||||||
|
.map(|c| (c.point_id, c.max_level))
|
||||||
|
.for_each(|(pt, lv)| {
|
||||||
|
if let Some(skill_tree) = avatar
|
||||||
|
.skill_tree_list
|
||||||
|
.iter_mut()
|
||||||
|
.find(|st| st.point_id == pt)
|
||||||
|
{
|
||||||
|
skill_tree.level = lv
|
||||||
|
} else {
|
||||||
|
avatar.skill_tree_list.push(AvatarSkillTreeBin {
|
||||||
|
point_id: pt,
|
||||||
|
level: lv,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let avatar_mgr = session.context.avatar_mgr.borrow();
|
||||||
|
session
|
||||||
|
.send(
|
||||||
|
CMD_PLAYER_SYNC_SC_NOTIFY,
|
||||||
|
PlayerSyncScNotify {
|
||||||
|
avatar_sync: Some(AvatarSync {
|
||||||
|
avatar_list: avatar_mgr.avatar_list_proto(),
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
send_text(
|
||||||
|
session,
|
||||||
|
&format!("Successfully maxed out traces of avatar {avatar_id}"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
72
gameserver/src/game/commands/mod.rs
Normal file
72
gameserver/src/game/commands/mod.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use proto::*;
|
||||||
|
|
||||||
|
use crate::{net::PlayerSession, util};
|
||||||
|
|
||||||
|
mod avatar;
|
||||||
|
mod relic;
|
||||||
|
mod scene;
|
||||||
|
|
||||||
|
macro_rules! commands {
|
||||||
|
($($category:ident $action:ident;)*) => {
|
||||||
|
pub async fn execute_command(command: &str, session: &PlayerSession) -> Result<()> {
|
||||||
|
let input = command[1..].split(" ").collect::<Vec<&str>>();
|
||||||
|
|
||||||
|
let (Some(category), Some(action)) = (input.get(0), input.get(1)) else {
|
||||||
|
let mut help_text = "Available Commands: ".to_string();
|
||||||
|
$(
|
||||||
|
help_text.push_str(stringify!($category));
|
||||||
|
help_text.push_str(" ");
|
||||||
|
help_text.push_str(stringify!($action));
|
||||||
|
help_text.push_str("; ");
|
||||||
|
)*
|
||||||
|
let _ = send_text(session, &help_text).await;
|
||||||
|
let _ = send_text(session, "Usage: /[category] [action] [arg1] [arg2] ...").await;
|
||||||
|
return send_text(session, "Type /[category] [action] to get more detailed help.").await;
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = &input[2..];
|
||||||
|
if let Err(_) = match (*category, *action) {
|
||||||
|
$(
|
||||||
|
(stringify!($category), stringify!($action)) => {
|
||||||
|
$category::$action(args, session).await
|
||||||
|
}
|
||||||
|
)*,
|
||||||
|
_ => send_text(session, "Unknown command").await,
|
||||||
|
} {
|
||||||
|
return send_text(
|
||||||
|
session,
|
||||||
|
"Command execution failed. Re-check your input and try again.",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
commands! {
|
||||||
|
avatar max_traces;
|
||||||
|
relic give;
|
||||||
|
scene enter;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_text(session: &PlayerSession, content: &str) -> Result<()> {
|
||||||
|
session
|
||||||
|
.send(
|
||||||
|
CMD_GET_PRIVATE_CHAT_HISTORY_SC_RSP,
|
||||||
|
GetPrivateChatHistoryScRsp {
|
||||||
|
contact_id: 13371337,
|
||||||
|
chat_message_list: vec![ChatMessageData {
|
||||||
|
sender_id: 13371337,
|
||||||
|
message_type: MsgType::CustomText.into(),
|
||||||
|
timestamp: util::cur_timestamp_seconds(),
|
||||||
|
content: content.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
42
gameserver/src/game/commands/relic.rs
Normal file
42
gameserver/src/game/commands/relic.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const GIVE_RELIC_USAGE: &'static str = "Usage: /relic give [id] [level] [main_affix] [sub_affix_count] [sub_affix1_id] [sub_affix1_cnt] ...";
|
||||||
|
pub async fn give(args: &[&str], session: &PlayerSession) -> Result<()> {
|
||||||
|
if args.len() < 4 {
|
||||||
|
return send_text(session, GIVE_RELIC_USAGE).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = args[0].parse::<u32>()?;
|
||||||
|
let level = args[1].parse::<u32>()?;
|
||||||
|
let main_affix = args[2].parse::<u32>()?;
|
||||||
|
let sub_affix_count = args[3].parse::<usize>()?;
|
||||||
|
|
||||||
|
if args.len() - 4 < (sub_affix_count * 2) as usize {
|
||||||
|
return send_text(session, GIVE_RELIC_USAGE).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sub_affix_params = Vec::with_capacity(sub_affix_count);
|
||||||
|
|
||||||
|
let args = &args[4..];
|
||||||
|
for i in 0..sub_affix_count {
|
||||||
|
let sub_affix_id = args[i * 2].parse::<u32>()?;
|
||||||
|
let sub_affix_cnt = args[i * 2 + 1].parse::<u32>()?;
|
||||||
|
|
||||||
|
sub_affix_params.push((sub_affix_id, sub_affix_cnt));
|
||||||
|
}
|
||||||
|
|
||||||
|
let item_mgr = session.context.item_mgr.borrow();
|
||||||
|
item_mgr.give_relic(id, level, main_affix, sub_affix_params)?;
|
||||||
|
|
||||||
|
session
|
||||||
|
.send(
|
||||||
|
CMD_PLAYER_SYNC_SC_NOTIFY,
|
||||||
|
PlayerSyncScNotify {
|
||||||
|
relic_list: item_mgr.relic_list_proto(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
send_text(session, "Relic added successfully").await
|
||||||
|
}
|
25
gameserver/src/game/commands/scene.rs
Normal file
25
gameserver/src/game/commands/scene.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub async fn enter(args: &[&str], session: &PlayerSession) -> Result<()> {
|
||||||
|
let Some(Ok(entry_id)) = args.get(0).map(|s| s.parse::<u32>()) else {
|
||||||
|
return send_text(session, "Usage: /scene enter [entry_id]").await;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut scene_mgr = session.context.scene_mgr.borrow_mut();
|
||||||
|
let scene = match scene_mgr.enter_scene(entry_id) {
|
||||||
|
Ok(scene_info) => Some(scene_info),
|
||||||
|
Err(_) => return send_text(session, &format!("Failed to enter scene {entry_id}.")).await,
|
||||||
|
};
|
||||||
|
|
||||||
|
let lineup_mgr = session.context.lineup_mgr.borrow();
|
||||||
|
session
|
||||||
|
.send(
|
||||||
|
CMD_ENTER_SCENE_BY_SERVER_SC_NOTIFY,
|
||||||
|
EnterSceneByServerScNotify {
|
||||||
|
scene,
|
||||||
|
lineup: Some(lineup_mgr.cur_lineup_proto()),
|
||||||
|
reason: EnterSceneReason::None.into(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
|
@ -16,7 +16,6 @@ pub struct GameContext {
|
||||||
logged_in: OnceCell<()>,
|
logged_in: OnceCell<()>,
|
||||||
pub player: Arc<AtomicRefCell<PlayerInfo>>,
|
pub player: Arc<AtomicRefCell<PlayerInfo>>,
|
||||||
pub avatar_mgr: Arc<AtomicRefCell<AvatarManager>>,
|
pub avatar_mgr: Arc<AtomicRefCell<AvatarManager>>,
|
||||||
pub command_mgr: Arc<AtomicRefCell<CommandManager>>,
|
|
||||||
pub hero_basic_type_mgr: Arc<AtomicRefCell<HeroBasicTypeManager>>,
|
pub hero_basic_type_mgr: Arc<AtomicRefCell<HeroBasicTypeManager>>,
|
||||||
pub item_mgr: Arc<AtomicRefCell<ItemManager>>,
|
pub item_mgr: Arc<AtomicRefCell<ItemManager>>,
|
||||||
pub lineup_mgr: Arc<AtomicRefCell<LineupManager>>,
|
pub lineup_mgr: Arc<AtomicRefCell<LineupManager>>,
|
||||||
|
@ -38,8 +37,7 @@ impl GameContext {
|
||||||
Self {
|
Self {
|
||||||
logged_in: OnceCell::new(),
|
logged_in: OnceCell::new(),
|
||||||
player: player.clone(),
|
player: player.clone(),
|
||||||
avatar_mgr: avatar_mgr.clone(),
|
avatar_mgr,
|
||||||
command_mgr: Arc::new(AtomicRefCell::new(CommandManager::new(item_mgr.clone()))),
|
|
||||||
hero_basic_type_mgr: Arc::new(AtomicRefCell::new(HeroBasicTypeManager::new(
|
hero_basic_type_mgr: Arc::new(AtomicRefCell::new(HeroBasicTypeManager::new(
|
||||||
player.clone(),
|
player.clone(),
|
||||||
))),
|
))),
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
use crate::{net::PlayerSession, util};
|
|
||||||
use anyhow::Result;
|
|
||||||
use proto::*;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub struct CommandManager {
|
|
||||||
item_mgr: Arc<AtomicRefCell<ItemManager>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandManager {
|
|
||||||
pub fn new(item_mgr: Arc<AtomicRefCell<ItemManager>>) -> Self {
|
|
||||||
Self { item_mgr }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn execute_command(&self, command: &str, session: &PlayerSession) -> Result<()> {
|
|
||||||
let input = command.split(" ").collect::<Vec<&str>>();
|
|
||||||
|
|
||||||
let (Some(category), Some(action)) = (input.get(0), input.get(1)) else {
|
|
||||||
return self
|
|
||||||
.send_text(session, "Usage: /[category] [action] [arg1] [arg2] ...")
|
|
||||||
.await;
|
|
||||||
};
|
|
||||||
|
|
||||||
let args = &input[2..];
|
|
||||||
if let Err(_) = match (*category, *action) {
|
|
||||||
("/relic", "give") => self.give_relic(args, session).await,
|
|
||||||
_ => self.send_text(session, "Unknown command").await,
|
|
||||||
} {
|
|
||||||
return self
|
|
||||||
.send_text(
|
|
||||||
session,
|
|
||||||
"Command execution failed. Re-check your input and try again.",
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
const GIVE_RELIC_USAGE: &'static str = "Usage: /relic give [id] [level] [main_affix] [sub_affix_count] [sub_affix1_id] [sub_affix1_cnt] ...";
|
|
||||||
async fn give_relic(&self, args: &[&str], session: &PlayerSession) -> Result<()> {
|
|
||||||
if args.len() < 4 {
|
|
||||||
return self.send_text(session, Self::GIVE_RELIC_USAGE).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = args[0].parse::<u32>()?;
|
|
||||||
let level = args[1].parse::<u32>()?;
|
|
||||||
let main_affix = args[2].parse::<u32>()?;
|
|
||||||
let sub_affix_count = args[3].parse::<usize>()?;
|
|
||||||
|
|
||||||
if args.len() - 4 < (sub_affix_count * 2) as usize {
|
|
||||||
return self.send_text(session, Self::GIVE_RELIC_USAGE).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut sub_affix_params = Vec::with_capacity(sub_affix_count);
|
|
||||||
|
|
||||||
let args = &args[4..];
|
|
||||||
for i in 0..sub_affix_count {
|
|
||||||
let sub_affix_id = args[i * 2].parse::<u32>()?;
|
|
||||||
let sub_affix_cnt = args[i * 2 + 1].parse::<u32>()?;
|
|
||||||
|
|
||||||
sub_affix_params.push((sub_affix_id, sub_affix_cnt));
|
|
||||||
}
|
|
||||||
|
|
||||||
let item_mgr = self.item_mgr.borrow();
|
|
||||||
item_mgr.give_relic(id, level, main_affix, sub_affix_params)?;
|
|
||||||
|
|
||||||
session
|
|
||||||
.send(
|
|
||||||
CMD_PLAYER_SYNC_SC_NOTIFY,
|
|
||||||
PlayerSyncScNotify {
|
|
||||||
relic_list: item_mgr.relic_list_proto(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.send_text(session, "Relic added successfully").await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_text(&self, session: &PlayerSession, content: &str) -> Result<()> {
|
|
||||||
session
|
|
||||||
.send(
|
|
||||||
CMD_GET_PRIVATE_CHAT_HISTORY_SC_RSP,
|
|
||||||
GetPrivateChatHistoryScRsp {
|
|
||||||
contact_id: 13371337,
|
|
||||||
chat_message_list: vec![ChatMessageData {
|
|
||||||
sender_id: 13371337,
|
|
||||||
message_type: MsgType::CustomText.into(),
|
|
||||||
timestamp: util::cur_timestamp_seconds(),
|
|
||||||
content: content.to_string(),
|
|
||||||
..Default::default()
|
|
||||||
}],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,7 +3,6 @@ use atomic_refcell::AtomicRefCell;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
mod avatar;
|
mod avatar;
|
||||||
mod command;
|
|
||||||
mod hero_basic_type;
|
mod hero_basic_type;
|
||||||
mod item;
|
mod item;
|
||||||
mod lineup;
|
mod lineup;
|
||||||
|
@ -12,7 +11,6 @@ mod time;
|
||||||
mod tutorial;
|
mod tutorial;
|
||||||
|
|
||||||
pub use avatar::AvatarManager;
|
pub use avatar::AvatarManager;
|
||||||
pub use command::CommandManager;
|
|
||||||
pub use hero_basic_type::HeroBasicTypeManager;
|
pub use hero_basic_type::HeroBasicTypeManager;
|
||||||
pub use item::ItemManager;
|
pub use item::ItemManager;
|
||||||
pub use lineup::LineupManager;
|
pub use lineup::LineupManager;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod commands;
|
||||||
mod context;
|
mod context;
|
||||||
mod gameplay_config;
|
mod gameplay_config;
|
||||||
pub mod managers;
|
pub mod managers;
|
||||||
|
|
|
@ -5,7 +5,10 @@ pub use group::SceneGroup;
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
use common::data::{level::LevelAnchorInfo, LEVEL_TABLE};
|
use common::data::{
|
||||||
|
level::{GroupLoadSide, LevelAnchorInfo},
|
||||||
|
LEVEL_TABLE,
|
||||||
|
};
|
||||||
use proto::{AvatarType, VectorBin};
|
use proto::{AvatarType, VectorBin};
|
||||||
|
|
||||||
use self::entity::{EntityType, SceneActor, SceneEntity, SceneProp};
|
use self::entity::{EntityType, SceneActor, SceneEntity, SceneProp};
|
||||||
|
@ -87,11 +90,19 @@ impl GameWorld {
|
||||||
pub fn init_groups(&mut self) {
|
pub fn init_groups(&mut self) {
|
||||||
let floor = LEVEL_TABLE.level_floors.get(&self.floor_id).unwrap();
|
let floor = LEVEL_TABLE.level_floors.get(&self.floor_id).unwrap();
|
||||||
|
|
||||||
for group_instance in &floor.group_instance_list {
|
for group_instance in floor
|
||||||
|
.group_instance_list
|
||||||
|
.iter()
|
||||||
|
.filter(|inst| !inst.is_delete)
|
||||||
|
{
|
||||||
let level_group = LEVEL_TABLE
|
let level_group = LEVEL_TABLE
|
||||||
.get_level_group(self.floor_id, group_instance.id)
|
.get_level_group(self.floor_id, group_instance.id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
if level_group.load_side == GroupLoadSide::Client {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let mut prop_entity_list = Vec::new();
|
let mut prop_entity_list = Vec::new();
|
||||||
if let Some(prop_list) = level_group.prop_list.as_ref() {
|
if let Some(prop_list) = level_group.prop_list.as_ref() {
|
||||||
for level_prop in prop_list {
|
for level_prop in prop_list {
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
use crate::util;
|
use crate::{game::commands, util};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub async fn on_send_msg_cs_req(session: &PlayerSession, body: &SendMsgCsReq) -> Result<()> {
|
pub async fn on_send_msg_cs_req(session: &PlayerSession, body: &SendMsgCsReq) -> Result<()> {
|
||||||
let command_mgr = session.context.command_mgr.borrow();
|
|
||||||
if body.message_text.starts_with("/") {
|
if body.message_text.starts_with("/") {
|
||||||
command_mgr
|
commands::execute_command(&body.message_text, session).await?;
|
||||||
.execute_command(&body.message_text, session)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
session
|
session
|
||||||
|
|
|
@ -59,7 +59,7 @@ impl NetPacket {
|
||||||
|
|
||||||
macro_rules! trait_handler {
|
macro_rules! trait_handler {
|
||||||
($($name:ident $cmd_type:expr;)*) => {
|
($($name:ident $cmd_type:expr;)*) => {
|
||||||
pub trait CommandHandler {
|
pub trait NetCommandHandler {
|
||||||
$(
|
$(
|
||||||
paste! {
|
paste! {
|
||||||
async fn [<on_$name:snake>](session: &PlayerSession, body: &$name) -> Result<()> {
|
async fn [<on_$name:snake>](session: &PlayerSession, body: &$name) -> Result<()> {
|
||||||
|
|
|
@ -10,7 +10,7 @@ use tokio::{
|
||||||
|
|
||||||
use crate::game::{GameContext, PlayerInfo};
|
use crate::game::{GameContext, PlayerInfo};
|
||||||
|
|
||||||
use super::{packet::CommandHandler, NetPacket};
|
use super::{packet::NetCommandHandler, NetPacket};
|
||||||
|
|
||||||
pub struct PlayerSession {
|
pub struct PlayerSession {
|
||||||
client_socket: Arc<Mutex<TcpStream>>,
|
client_socket: Arc<Mutex<TcpStream>>,
|
||||||
|
@ -82,4 +82,4 @@ impl PlayerSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto implemented
|
// Auto implemented
|
||||||
impl CommandHandler for PlayerSession {}
|
impl NetCommandHandler for PlayerSession {}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// This file is @generated by prost-build.
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct PlayerBasicCompBin {
|
pub struct PlayerBasicCompBin {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"http_port": 21000,
|
"http_port": 21000,
|
||||||
"dispatch_endpoint": "http://127.0.0.1:21041",
|
"dispatch_endpoint": "http://127.0.0.1:21041",
|
||||||
|
"disable_password_check": true,
|
||||||
"database": {
|
"database": {
|
||||||
"connection_string": "mongodb://127.0.0.1:27017",
|
"connection_string": "mongodb://127.0.0.1:27017",
|
||||||
"name": "FireflySR",
|
"name": "FireflySR",
|
||||||
|
|
|
@ -14,6 +14,7 @@ pub struct SDKServerConfiguration {
|
||||||
pub http_port: u16,
|
pub http_port: u16,
|
||||||
pub dispatch_endpoint: String,
|
pub dispatch_endpoint: String,
|
||||||
pub database: DatabaseConfig,
|
pub database: DatabaseConfig,
|
||||||
|
pub disable_password_check: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use common::document::AccountDocument;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::{database, util, SdkContext};
|
use crate::{config::CONFIGURATION, database, util, SdkContext};
|
||||||
|
|
||||||
const LOGIN: &str = "/:product_name/mdk/shield/api/login";
|
const LOGIN: &str = "/:product_name/mdk/shield/api/login";
|
||||||
const VERIFY: &str = "/:product_name/mdk/shield/api/verify";
|
const VERIFY: &str = "/:product_name/mdk/shield/api/verify";
|
||||||
|
@ -38,9 +38,15 @@ async fn login(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Ok(password) = util::decrypt_string(&request.password) else {
|
let mut password_opt: Option<String> = None;
|
||||||
|
if !CONFIGURATION.disable_password_check
|
||||||
|
{
|
||||||
|
if let Ok(password) = util::decrypt_string(&request.password) {
|
||||||
|
password_opt = Some(password);
|
||||||
|
} else {
|
||||||
return fail_json(-10, "Your patch is outdated.\r\nGet new one at https://discord.gg/reversedrooms\r\n(Password decryption failed)");
|
return fail_json(-10, "Your patch is outdated.\r\nGet new one at https://discord.gg/reversedrooms\r\n(Password decryption failed)");
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let account = match database::get_account_by_name(&context.db_client, &request.account).await {
|
let account = match database::get_account_by_name(&context.db_client, &request.account).await {
|
||||||
Ok(Some(account)) => account,
|
Ok(Some(account)) => account,
|
||||||
|
@ -48,7 +54,7 @@ async fn login(
|
||||||
Err(_) => return fail_json(-1, "Internal server error"),
|
Err(_) => return fail_json(-1, "Internal server error"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if util::verify_password(&password, &account.account_password).is_err() {
|
if !CONFIGURATION.disable_password_check && util::verify_password(&password_opt.unwrap(), &account.account_password).is_err() {
|
||||||
return fail_json(-101, "Account or password error");
|
return fail_json(-101, "Account or password error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue