diff --git a/src/server/game_server.rs b/src/server/game_server.rs index 3bd8326..568eb2f 100644 --- a/src/server/game_server.rs +++ b/src/server/game_server.rs @@ -12,6 +12,8 @@ use crate::JsonManager; use crate::LuaManager; use crate::server::LoginManager; use std::sync::Arc; +use crate::subsystems::{NpcSubsystem, ShopSubsystem}; +use crate::subsystems::misc::{PauseSubsystem, SceneSubsystem, SocialSubsystem}; pub struct GameServer { packets_to_process_rx: mpsc::Receiver, @@ -29,7 +31,13 @@ impl GameServer { let jm = Arc::new(JsonManager::new("./data/json")); let lm = LoginManager::new(db.clone(), jm.clone(), packets_to_send_tx.clone()); let lum = Arc::new(LuaManager::new("./data/lua")); + let em = EntitySubsystem::new(lum.clone(), jm.clone(), db.clone(), packets_to_send_tx.clone()); + let nt = NpcSubsystem::new(packets_to_send_tx.clone()); + let ss = ShopSubsystem::new(jm.clone(), db.clone(), packets_to_send_tx.clone()); + let scs = SceneSubsystem::new(packets_to_send_tx.clone()); + let ps = PauseSubsystem::new(packets_to_send_tx.clone()); + let socs = SocialSubsystem::new(db.clone(), packets_to_send_tx.clone()); let gs = GameServer { packets_to_process_rx: packets_to_process_rx, @@ -38,7 +46,7 @@ impl GameServer { login_manager: lm, database_manager: db.clone(), json_manager: jm.clone(), - processors: vec![Box::new(em)], + processors: vec![Box::new(em), Box::new(nt), Box::new(ss), Box::new(scs), Box::new(ps), Box::new(socs)], }; return gs; diff --git a/src/subsystems/misc/mod.rs b/src/subsystems/misc/mod.rs new file mode 100644 index 0000000..5811469 --- /dev/null +++ b/src/subsystems/misc/mod.rs @@ -0,0 +1,11 @@ +mod npc; +mod shop; +mod scene; +mod pause; +mod social; + +pub use npc::NpcSubsystem; +pub use shop::ShopSubsystem; +pub use scene::SceneSubsystem; +pub use pause::PauseSubsystem; +pub use social::SocialSubsystem; \ No newline at end of file diff --git a/src/subsystems/misc/npc.rs b/src/subsystems/misc/npc.rs new file mode 100644 index 0000000..cd6ac74 --- /dev/null +++ b/src/subsystems/misc/npc.rs @@ -0,0 +1,47 @@ +use std::sync::{mpsc::{self, Sender, Receiver}, Arc, Mutex}; +use std::thread; +use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::Entry::{Occupied, Vacant}; + +use crate::server::IpcMessage; + +use prost::Message; + +use proto; +use proto::{PacketId, CombatTypeArgument, ForwardType, ProtEntityType}; + +use packet_processor_macro::*; +#[macro_use] +use packet_processor::*; +use serde_json::de::Read; +use crate::{DatabaseManager, JsonManager, LuaManager}; +use crate::utils::{IdManager, TimeManager}; + +#[packet_processor( +NpcTalkReq, +)] +pub struct NpcSubsystem { + packets_to_send_tx: Sender, +} + +impl NpcSubsystem { + pub fn new(packets_to_send_tx: Sender) -> Self { + let mut nt = Self { + packets_to_send_tx: packets_to_send_tx, + packet_callbacks: HashMap::new(), + }; + + nt.register(); + + return nt; + } + + fn process_npc_talk(&self, user_id: u32, metadata: &proto::PacketHead, req: &proto::NpcTalkReq, rsp: &mut proto::NpcTalkRsp) { + // TODO: Real server should analyze data sent by the client and produce extra packets (about quest, rewards, etc) + // As of now we just confirming to the client that he's correct + // TODO: We also don't consider "npc_entity_id" field here. + // It's omitted most of the time (in fact, I've never seen it in the traffic), but maybe it's important... + rsp.cur_talk_id = req.talk_id; + rsp.entity_id = req.entity_id; + } +} \ No newline at end of file diff --git a/src/subsystems/misc/pause.rs b/src/subsystems/misc/pause.rs new file mode 100644 index 0000000..5deeee8 --- /dev/null +++ b/src/subsystems/misc/pause.rs @@ -0,0 +1,42 @@ +use std::sync::{mpsc::{self, Sender, Receiver}, Arc, Mutex}; +use std::thread; +use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::Entry::{Occupied, Vacant}; + +use crate::server::IpcMessage; + +use prost::Message; + +use proto; +use proto::{PacketId, CombatTypeArgument, ForwardType, ProtEntityType}; + +use packet_processor_macro::*; +#[macro_use] +use packet_processor::*; +use serde_json::de::Read; +use crate::{DatabaseManager, JsonManager, LuaManager}; +use crate::utils::{IdManager, TimeManager}; + +#[packet_processor( +PlayerSetPauseReq, +)] +pub struct PauseSubsystem { + packets_to_send_tx: Sender, +} + +impl PauseSubsystem { + pub fn new(packets_to_send_tx: Sender) -> Self { + let mut ps = Self { + packets_to_send_tx: packets_to_send_tx, + packet_callbacks: HashMap::new(), + }; + + ps.register(); + + return ps; + } + + fn process_player_set_pause(&self, user_id: u32, metadata: &proto::PacketHead, req: &proto::PlayerSetPauseReq, rsp: &mut proto::PlayerSetPauseRsp) { + // Nothing to do here, maybe check req.is_paused + } +} \ No newline at end of file diff --git a/src/subsystems/misc/scene.rs b/src/subsystems/misc/scene.rs new file mode 100644 index 0000000..eb2b598 --- /dev/null +++ b/src/subsystems/misc/scene.rs @@ -0,0 +1,59 @@ +use std::sync::{mpsc::{self, Sender, Receiver}, Arc, Mutex}; +use std::thread; +use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::Entry::{Occupied, Vacant}; + +use crate::server::IpcMessage; + +use prost::Message; + +use proto; +use proto::{PacketId, CombatTypeArgument, ForwardType, ProtEntityType}; + +use packet_processor_macro::*; +#[macro_use] +use packet_processor::*; +use serde_json::de::Read; +use crate::{DatabaseManager, JsonManager, LuaManager}; +use crate::utils::{IdManager, TimeManager}; + +#[packet_processor( +GetSceneAreaReq, +GetScenePointReq, +)] +pub struct SceneSubsystem { + packets_to_send_tx: Sender, +} + +impl SceneSubsystem { + pub fn new(packets_to_send_tx: Sender) -> Self { + let mut scs = Self { + packets_to_send_tx: packets_to_send_tx, + packet_callbacks: HashMap::new(), + }; + + scs.register(); + + return scs; + } + + fn process_get_scene_area(&self, user_id: u32, metadata: &proto::PacketHead, req: &proto::GetSceneAreaReq, rsp: &mut proto::GetSceneAreaRsp) { + rsp.scene_id = req.scene_id; + // TODO: hardcoded data! + rsp.area_id_list = (1..20).collect(); + rsp.city_info_list = vec![ + build!(CityInfo { city_id: 1, level: 10,}), + build!(CityInfo { city_id: 2, level: 10,}), + build!(CityInfo { city_id: 3, level: 10,}), + ]; + } + + fn process_get_scene_point(&self, user_id: u32, metadata: &proto::PacketHead, req: &proto::GetScenePointReq, rsp: &mut proto::GetScenePointRsp) { + rsp.scene_id = req.scene_id; + // TODO: hardcoded data! + rsp.unlocked_point_list = (1..250).collect(); + rsp.unlock_area_list = (1..11).collect(); + //locked_point_list=vec![]; + } + +} \ No newline at end of file diff --git a/src/subsystems/misc/shop.rs b/src/subsystems/misc/shop.rs new file mode 100644 index 0000000..11611ed --- /dev/null +++ b/src/subsystems/misc/shop.rs @@ -0,0 +1,123 @@ +use std::sync::{mpsc::{self, Sender, Receiver}, Arc, Mutex}; +use std::thread; +use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::Entry::{Occupied, Vacant}; + +use crate::server::IpcMessage; + +use prost::Message; + +use proto; +use proto::{PacketId, CombatTypeArgument, ForwardType, ProtEntityType}; + +use packet_processor_macro::*; +#[macro_use] +use packet_processor::*; +use serde_json::de::Read; +use crate::{DatabaseManager, JsonManager, LuaManager}; +use crate::utils::{IdManager, TimeManager}; + +#[packet_processor( +GetShopReq, +BuyGoodsReq, +)] +pub struct ShopSubsystem { + packets_to_send_tx: Sender, + json_manager: Arc, + db_manager: Arc, +} + +impl ShopSubsystem { + pub fn new(jm: Arc, db: Arc, packets_to_send_tx: Sender) -> Self { + let mut ss = Self { + packets_to_send_tx: packets_to_send_tx, + packet_callbacks: HashMap::new(), + json_manager: jm.clone(), + db_manager: db.clone(), + }; + + ss.register(); + + return ss; + } + + fn process_get_shop(&self, user_id: u32, metadata: &proto::PacketHead, req: &proto::GetShopReq, rsp: &mut proto::GetShopRsp) { + let fuck_you_borrow_checker: Vec = vec![]; + + let shop_goods = self.json_manager.shop_goods.get(&req.shop_type).unwrap_or(&fuck_you_borrow_checker); + + // TODO: each item should have it's own refresh time! + let next_refresh_time = TimeManager::timestamp() as u32 + 86400; + + let player_level = self.db_manager.get_player_level(user_id).unwrap(); + + let goods = shop_goods.iter().filter_map(|item| { + // If player's AR is too low or too high, then we don't even show this item to him + if player_level >= item.min_show_level || player_level <= item.max_show_level.unwrap_or(99) { + let item_id = match item.item_id { + Some(item_id) => item_id, + None => match item.rotate_id { + Some(rotate_id) => { + let rotate = match self.json_manager.shop_rotate.get(&rotate_id) { + Some(rotate) => rotate, + None => panic!("Rotate {} not found!", rotate_id), + }; + + rotate[0].item_id // TODO: should be rotated obviously! + }, + None => { + panic!("Both item_id and rotate_id are empty for item {}!", item.goods_id) + } + } + }; + + let item_refresh_time = self.get_shop_refresh_time(req.shop_type, item_id); + + let item_refresh_time = std::cmp::min(item_refresh_time, next_refresh_time); + + let begin_time = match item.begin_time { Some(t) => t.timestamp() as u32, None => 0 }; + let end_time = match item.end_time { Some(t) => t.timestamp() as u32, None => 0 }; + + let good = build!(ShopGoods { + goods_id: item.goods_id, + goods_item: Some(build!(ItemParam { item_id: item_id, count: item.item_count, })), + begin_time: begin_time, + end_time: end_time, + next_refresh_time: item_refresh_time, + min_level: item.min_show_level, + max_level: item.max_show_level.unwrap_or(0), + buy_limit: item.buy_limit.unwrap_or(0), + + cost_item_list: item.cost_items.iter().filter_map(|ci| if ci.item_id > 0 { Some(build!(ItemParam { item_id: ci.item_id, count: ci.count, })) } else { None }).collect(), + + hcoin: item.cost_hcoin.unwrap_or(0), + mcoin: item.cost_mcoin.unwrap_or(0), + scoin: item.cost_scoin.unwrap_or(0), + + // TODO: handle preconditions! + }); + + // TODO: SubTabId / secondary_sheet_id is not filled by a server? + + Some(good) + } else { + None + } + }).collect(); + + rsp.shop = Some(build!(Shop { + shop_type: req.shop_type, + goods_list: goods, + next_refresh_time: next_refresh_time, + })); + } + + fn process_buy_goods(&self, user_id: u32, metadata: &proto::PacketHead, req: &proto::BuyGoodsReq, rsp: &mut proto::BuyGoodsRsp) { + + } + + fn get_shop_refresh_time(&self, shop_type: u32, item_id: u32) -> u32 { + // TODO: handle daily, weekly and monthly updates + (TimeManager::timestamp() + 86400) as u32 + } +} \ No newline at end of file diff --git a/src/subsystems/misc/social.rs b/src/subsystems/misc/social.rs new file mode 100644 index 0000000..b0a4bcc --- /dev/null +++ b/src/subsystems/misc/social.rs @@ -0,0 +1,93 @@ +use std::sync::{mpsc::{self, Sender, Receiver}, Arc, Mutex}; +use std::thread; +use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::Entry::{Occupied, Vacant}; + +use crate::server::IpcMessage; + +use chrono::Datelike; + +use prost::Message; + +use proto; +use proto::{PacketId, CombatTypeArgument, ForwardType, ProtEntityType}; + +use packet_processor_macro::*; +#[macro_use] +use packet_processor::*; +use serde_json::de::Read; +use crate::{DatabaseManager, JsonManager, LuaManager}; +use crate::utils::{IdManager, TimeManager}; + +#[packet_processor( +GetPlayerBlacklistReq, +GetPlayerFriendListReq, +GetPlayerSocialDetailReq, +)] +pub struct SocialSubsystem { + packets_to_send_tx: Sender, + db: Arc, +} + +impl SocialSubsystem { + pub fn new(db: Arc, packets_to_send_tx: Sender) -> Self { + let mut socs = Self { + packets_to_send_tx: packets_to_send_tx, + packet_callbacks: HashMap::new(), + db: db.clone(), + }; + + socs.register(); + + return socs; + } + + fn process_get_player_blacklist(&self, user_id: u32, metadata: &proto::PacketHead, req: &proto::GetPlayerBlacklistReq, rsp: &mut proto::GetPlayerBlacklistRsp) { + // TODO! + } + + fn process_get_player_friend_list(&self, user_id: u32, metadata: &proto::PacketHead, req: &proto::GetPlayerFriendListReq, rsp: &mut proto::GetPlayerFriendListRsp) { + // TODO! + } + + fn process_get_player_social_detail(&self, user_id: u32, metadata: &proto::PacketHead, req: &proto::GetPlayerSocialDetailReq, rsp: &mut proto::GetPlayerSocialDetailRsp) { + let user = match self.db.get_player_info(user_id) { + Some(user) => user, + None => panic!("User {} not found!", user_id), + }; + + let props = self.db.get_player_props(user_id).unwrap_or_else(|| panic!("Failed to get properties for user {}!", user_id)); + + let user_level = props[&(proto::PropType::PropPlayerLevel as u32)] as u32; + let world_level = props[&(proto::PropType::PropPlayerWorldLevel as u32)] as u32; + + let avatar_info = build!(SocialShowAvatarInfo { + avatar_id: user.avatar_id, + level: 80, // TODO + }); + + let details = build!(SocialDetail { + uid: user_id, + nickname: user.nick_name.clone(), + level: user_level, + //avatar_id: user.avatar_id, + signature: user.signature.clone(), + birthday: Some(proto::Birthday {month: user.birthday.month(), day: user.birthday.day()}), + world_level: world_level, + online_state: proto::FriendOnlineState::FriendOnline as i32, // TODO + is_friend: true, // TODO + is_mp_mode_available: true, // TODO + name_card_id: user.namecard_id, + finish_achievement_num: user.finish_achievement_num, // TODO + tower_floor_index: user.tower_floor_index as u32, + tower_level_index: user.tower_level_index as u32, + show_avatar_info_list: vec![avatar_info], // TODO + show_name_card_id_list: vec![user.namecard_id], // TODO: add all namecards! + profile_picture: Some(build!(ProfilePicture { + avatar_id: 10000007, + })), + }); + + rsp.detail_data = Some(details); + } +} \ No newline at end of file diff --git a/src/subsystems/mod.rs b/src/subsystems/mod.rs index 9e49537..2330e39 100644 --- a/src/subsystems/mod.rs +++ b/src/subsystems/mod.rs @@ -1,3 +1,6 @@ pub mod entity_subsystem; +pub mod misc; -pub use self::entity_subsystem::EntitySubsystem; \ No newline at end of file +pub use self::entity_subsystem::EntitySubsystem; +pub use self::misc::NpcSubsystem; +pub use self::misc::ShopSubsystem; \ No newline at end of file