diff --git a/Cargo.lock b/Cargo.lock index 78977f7..c20d443 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.22" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "jobserver", "libc", @@ -3026,7 +3026,7 @@ dependencies = [ [[package]] name = "wicked-waifus-protocol" version = "0.1.0" -source = "git+https://git.xeondev.com/wickedwaifus/wicked-waifus-proto#6f2733c09b78a6e874f379254c9b71fbef23c3a0" +source = "git+https://git.xeondev.com/wickedwaifus/wicked-waifus-proto#b358a1ae29335c5c6fd0f1502471c9a90e7f1bf4" dependencies = [ "byteorder", "crc32fast", @@ -3044,7 +3044,7 @@ dependencies = [ [[package]] name = "wicked-waifus-protocol-derive" version = "0.1.0" -source = "git+https://git.xeondev.com/wickedwaifus/wicked-waifus-proto#6f2733c09b78a6e874f379254c9b71fbef23c3a0" +source = "git+https://git.xeondev.com/wickedwaifus/wicked-waifus-proto#b358a1ae29335c5c6fd0f1502471c9a90e7f1bf4" dependencies = [ "proc-macro2", "quote", diff --git a/wicked-waifus-data/src/fly_skin_config.rs b/wicked-waifus-data/src/fly_skin_config.rs new file mode 100644 index 0000000..70af953 --- /dev/null +++ b/wicked-waifus-data/src/fly_skin_config.rs @@ -0,0 +1,55 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))] +#[serde(rename_all = "PascalCase")] +pub struct FlySkinConfigData { + pub id: i32, + pub skin_type: i32, + #[cfg(feature = "strict_json_fields")] + pub model_id: i32, + #[cfg(feature = "strict_json_fields")] + pub stand_anim: String, + pub quality_id: i32, + pub skin_grade: i32, + #[cfg(feature = "strict_json_fields")] + pub name: String, + #[cfg(feature = "strict_json_fields")] + pub type_description: String, + #[cfg(feature = "strict_json_fields")] + pub attributes_description: String, + #[cfg(feature = "strict_json_fields")] + pub bg_description: String, + #[cfg(feature = "strict_json_fields")] + pub icon: String, + #[cfg(feature = "strict_json_fields")] + pub icon_middle: String, + #[cfg(feature = "strict_json_fields")] + pub icon_small: String, + #[cfg(feature = "strict_json_fields")] + pub mesh: String, + #[cfg(feature = "strict_json_fields")] + pub obtained_show_description: String, + #[cfg(feature = "strict_json_fields")] + pub show_in_bag: bool, + #[cfg(feature = "strict_json_fields")] + pub item_access: Vec, + #[cfg(feature = "strict_json_fields")] + pub sort_index: i32, + #[cfg(feature = "strict_json_fields")] + pub red_dot_disable_rule: i32, + #[cfg(feature = "strict_json_fields")] + pub preview_texture_in_buy_view: String, + #[cfg(feature = "strict_json_fields")] + pub preview_texture_in_pay_shop: String, + #[cfg(feature = "strict_json_fields")] + pub preview_texture_in_pop: String, + #[cfg(feature = "strict_json_fields")] + pub skin_obtain_color1: String, + #[cfg(feature = "strict_json_fields")] + pub skin_obtain_color2: String, + #[cfg(feature = "strict_json_fields")] + pub skin_obtain_image: String, + #[cfg(feature = "strict_json_fields")] + pub is_special_view_after_obtain: bool, +} diff --git a/wicked-waifus-data/src/lib.rs b/wicked-waifus-data/src/lib.rs index 176e29c..785dcb3 100644 --- a/wicked-waifus-data/src/lib.rs +++ b/wicked-waifus-data/src/lib.rs @@ -125,6 +125,7 @@ json_data! { FavorLevel; FavorStory; FavorWord; + FlySkinConfig; ForgeFormula; FunctionCondition; Gacha; @@ -145,7 +146,6 @@ json_data! { ResonanceAmplification; ResonantChain; RoleBreach; - RoleExpItem; RoleInfo; RoleLevelConsume; RolePropertyGrowth; @@ -159,6 +159,7 @@ json_data! { WeaponLevel; WeaponPropertyGrowth; WeaponReson; + WeaponSkin; } json_hash_table_data! { @@ -167,6 +168,7 @@ json_hash_table_data! { BlueprintConfig, blueprint_type, String; DragonPool, id, i32; DropPackage, id, i32; + RoleExpItem, id, i32; TemplateConfig, blueprint_type, String; } @@ -184,7 +186,10 @@ pub mod level_entity_config_data { } pub fn get(map_id: i32, entity_id: i64) -> Option<&'static Data> { - TABLE.get().unwrap().get(&create_key_internal(map_id, entity_id)) + TABLE + .get() + .unwrap() + .get(&create_key_internal(map_id, entity_id)) } #[inline(always)] @@ -207,10 +212,9 @@ fn load_json_entity_level_config_data(base_path: &str) -> Result<(), LoadDataErr serde_json::from_reader::, Vec>(reader)? .into_iter() .map(|element| (level_entity_config_data::create_key(&element), element)) - .collect::>() + .collect::>(), ); tracing::info!("Loading data finished: {path}"); - Ok(()) -} \ No newline at end of file +} diff --git a/wicked-waifus-data/src/weapon_skin.rs b/wicked-waifus-data/src/weapon_skin.rs new file mode 100644 index 0000000..ad949ed --- /dev/null +++ b/wicked-waifus-data/src/weapon_skin.rs @@ -0,0 +1,54 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))] +#[serde(rename_all = "PascalCase")] +pub struct WeaponSkinData { + pub id: i32, + pub weapon_skin_type: i32, + #[cfg(feature = "strict_json_fields")] + pub name: String, + pub hide_in_skin_view: bool, + pub quality_id: i32, + #[cfg(feature = "strict_json_fields")] + pub model_id: i32, + #[cfg(feature = "strict_json_fields")] + pub transform_id: i32, + #[cfg(feature = "strict_json_fields")] + pub models: Vec, + #[cfg(feature = "strict_json_fields")] + pub type_description: String, + #[cfg(feature = "strict_json_fields")] + pub attributes_description: String, + #[cfg(feature = "strict_json_fields")] + pub bg_description: String, + #[cfg(feature = "strict_json_fields")] + pub card_icon_path: String, + #[cfg(feature = "strict_json_fields")] + pub icon: String, + #[cfg(feature = "strict_json_fields")] + pub icon_middle: String, + #[cfg(feature = "strict_json_fields")] + pub icon_small: String, + #[cfg(feature = "strict_json_fields")] + #[serde(rename = "MaxCapcity")] // kuro! + pub max_capacity: i32, + #[cfg(feature = "strict_json_fields")] + pub item_access: Vec, + #[cfg(feature = "strict_json_fields")] + pub obtained_show: i32, + #[cfg(feature = "strict_json_fields")] + pub obtained_show_description: String, + #[cfg(feature = "strict_json_fields")] + pub num_limit: i32, + #[cfg(feature = "strict_json_fields")] + pub show_in_bag: bool, + #[cfg(feature = "strict_json_fields")] + pub sort_index: i32, + #[cfg(feature = "strict_json_fields")] + pub hidden_time: i32, + #[cfg(feature = "strict_json_fields")] + pub destructible: bool, + #[cfg(feature = "strict_json_fields")] + pub red_dot_disable_rule: i32, +} diff --git a/wicked-waifus-game-server/gameserver.default.toml b/wicked-waifus-game-server/gameserver.default.toml index c41f0b3..d367b4e 100644 --- a/wicked-waifus-game-server/gameserver.default.toml +++ b/wicked-waifus-game-server/gameserver.default.toml @@ -19,7 +19,7 @@ load_textmaps = true quadrant_size = 1000000 [asset_config] -asset_url = "https://git.xeondev.com/wickedwaifus/wicked-waifus-data/releases/download/pioneer_2.4.1/bundle.zip" +asset_url = "https://git.xeondev.com/wickedwaifus/wicked-waifus-data/releases/download/pioneer_2.4.4/bundle.zip" buffer_size = 268435456 [default_unlocks] @@ -28,8 +28,15 @@ unlock_all_roles_max_level = false unlock_all_roles_all_sequences = false unlock_all_mc_elements = true unlock_all_weapons = false +unlock_all_weapons_max_level = false +unlock_all_weapons_all_reson = false unlock_all_adventures = false unlock_all_functions = true unlock_all_guides = false unlock_all_tutorials = false unlock_all_teleporter = false +unlock_all_role_skins = false +# TODO: Set this to the same value as unlock_all_role_skins, without it, it fails(maybe jinshi weapon skin problem??) +unlock_all_weapon_skins = false +unlock_all_fly_skins = false +unlock_all_wing_skins = false \ No newline at end of file diff --git a/wicked-waifus-game-server/src/config.rs b/wicked-waifus-game-server/src/config.rs index 58270d4..7db903b 100644 --- a/wicked-waifus-game-server/src/config.rs +++ b/wicked-waifus-game-server/src/config.rs @@ -37,12 +37,18 @@ pub struct DefaultUnlocks { pub unlock_all_roles_max_level: bool, pub unlock_all_roles_all_sequences: bool, pub unlock_all_mc_elements: bool, - pub unlock_all_weapons: bool, // TODO: + pub unlock_all_weapons: bool, + pub unlock_all_weapons_max_level: bool, + pub unlock_all_weapons_all_reson: bool, pub unlock_all_adventures: bool, pub unlock_all_functions: bool, pub unlock_all_guides: bool, pub unlock_all_tutorials: bool, pub unlock_all_teleporter: bool, + pub unlock_all_role_skins: bool, + pub unlock_all_weapon_skins: bool, + pub unlock_all_fly_skins: bool, + pub unlock_all_wing_skins: bool, } impl TomlConfig for ServiceConfig { diff --git a/wicked-waifus-game-server/src/logic/components/mod.rs b/wicked-waifus-game-server/src/logic/components/mod.rs index dd9c9ab..6c0dc6a 100644 --- a/wicked-waifus-game-server/src/logic/components/mod.rs +++ b/wicked-waifus-game-server/src/logic/components/mod.rs @@ -19,6 +19,7 @@ mod summoner; mod tag; mod visibility; mod vision_skill; +mod weapon_skin; pub use attribute::Attribute; pub use autonomous::Autonomous; @@ -41,3 +42,4 @@ pub use summoner::Summoner; pub use tag::Tag; pub use visibility::Visibility; pub use vision_skill::VisionSkill; +pub use weapon_skin::WeaponSkin; diff --git a/wicked-waifus-game-server/src/logic/components/weapon_skin.rs b/wicked-waifus-game-server/src/logic/components/weapon_skin.rs new file mode 100644 index 0000000..bee3626 --- /dev/null +++ b/wicked-waifus-game-server/src/logic/components/weapon_skin.rs @@ -0,0 +1,17 @@ +use wicked_waifus_protocol::entity_component_pb::ComponentPb; +use wicked_waifus_protocol::{EntityComponentPb, WeaponSkinComponentPb}; +use crate::logic::ecs::component::Component; + +pub struct WeaponSkin { + pub skin_id: i32, +} + +impl Component for WeaponSkin { + fn set_pb_data(&self, pb: &mut wicked_waifus_protocol::EntityPb) { + pb.component_pbs.push(EntityComponentPb { + component_pb: Some(ComponentPb::WeaponSkinComponentPb(WeaponSkinComponentPb { + weapon_skin_id: self.skin_id, + })), + }) + } +} diff --git a/wicked-waifus-game-server/src/logic/ecs/component.rs b/wicked-waifus-game-server/src/logic/ecs/component.rs index b5a99e2..c8556ee 100644 --- a/wicked-waifus-game-server/src/logic/ecs/component.rs +++ b/wicked-waifus-game-server/src/logic/ecs/component.rs @@ -44,6 +44,7 @@ impl_component_container! { Summoner; SoarWingSkin; ParaglidingSkin; + WeaponSkin; } pub trait Component { diff --git a/wicked-waifus-game-server/src/logic/gacha/gacha_pool.rs b/wicked-waifus-game-server/src/logic/gacha/gacha_pool.rs index 83b3a99..e78570b 100644 --- a/wicked-waifus-game-server/src/logic/gacha/gacha_pool.rs +++ b/wicked-waifus-game-server/src/logic/gacha/gacha_pool.rs @@ -113,7 +113,13 @@ impl GachaPool { match player.role_list.get(&item_id) { None => { if required_role_ids.contains(&item_id) { - player.role_list.insert(item_id, Role::new(item_id)); + let role = Role::new(item_id); + let role_id = role.role_id; + let weapon_id = role.equip_weapon; + player.role_list.insert(item_id, role); + // TODO notifies player update + player.inventory.add_weapon(role_id, 0, 1, 0, 0, 0, weapon_id).unwrap(); + // TODO notifies weapon update } } Some(role) => { diff --git a/wicked-waifus-game-server/src/logic/handler/dummy.rs b/wicked-waifus-game-server/src/logic/handler/dummy.rs index 9f78205..8501869 100644 --- a/wicked-waifus-game-server/src/logic/handler/dummy.rs +++ b/wicked-waifus-game-server/src/logic/handler/dummy.rs @@ -33,7 +33,6 @@ dummy_handler! { ExchangeReward; Liveness; PhotoMemory; - WeaponSkin; VisionEquipGroupInfo; UpdatePlayStationBlockAccount; AdventureManual; diff --git a/wicked-waifus-game-server/src/logic/handler/inventory.rs b/wicked-waifus-game-server/src/logic/handler/inventory.rs index cf8334e..4837ca0 100644 --- a/wicked-waifus-game-server/src/logic/handler/inventory.rs +++ b/wicked-waifus-game-server/src/logic/handler/inventory.rs @@ -12,12 +12,11 @@ pub fn on_normal_item_request( } pub fn on_weapon_item_request( - _player: &mut Player, + player: &mut Player, _: WeaponItemRequest, - _response: &mut WeaponItemResponse, + response: &mut WeaponItemResponse, ) { - // TODO: Implement this - tracing::warn!("Unhandled WeaponItemRequest"); + response.weapon_item_list = player.inventory.to_weapon_item_list(); } pub fn on_phantom_item_request( diff --git a/wicked-waifus-game-server/src/logic/handler/mod.rs b/wicked-waifus-game-server/src/logic/handler/mod.rs index da75c57..2fd2085 100644 --- a/wicked-waifus-game-server/src/logic/handler/mod.rs +++ b/wicked-waifus-game-server/src/logic/handler/mod.rs @@ -19,6 +19,7 @@ use wicked_waifus_protocol::message::Message; pub use skill::*; pub use teleport::*; pub use tutorial::*; +pub use weapon::*; mod advice; mod animal; @@ -40,6 +41,7 @@ mod scene; mod skill; mod teleport; mod tutorial; +mod weapon; macro_rules! handle_request { ($($name:ident $(, $inner_package:ident)?;)*) => { @@ -200,13 +202,19 @@ handle_request! { RoleFavorList; FormationAttr; UpdateFormation; + UnlockRoleSkinList; + RoleSkinChange; + FlySkinWear; + FlySkinWearAllRole; + RoleLevelUpView; + PbUpLevelRole; + RoleBreakThroughView; // Scene (TODO: Review this on_..., port some from go) SceneTrace; SceneLoadingFinish; UpdateSceneDate; AccessPathTimeServerConfig; - UnlockRoleSkinList; PlayerHeadData; // Shop (TODO: Review this on_..., port some from go) @@ -237,6 +245,12 @@ handle_request! { TutorialReceive; TutorialUnlock; + // Weapon + WeaponSkin; + EquipWeaponSkin; + SendEquipSkin; + EquipTakeOn; + // TODO: Implement all this properly, workaround for game enter EntityPatrolStop; InitRange; @@ -264,7 +278,6 @@ handle_request! { Liveness; WebSign; PhotoMemory; - WeaponSkin; VisionEquipGroupInfo; UpdatePlayStationBlockAccount; AdventureManual; diff --git a/wicked-waifus-game-server/src/logic/handler/role.rs b/wicked-waifus-game-server/src/logic/handler/role.rs index f17cf8a..9381ecd 100644 --- a/wicked-waifus-game-server/src/logic/handler/role.rs +++ b/wicked-waifus-game-server/src/logic/handler/role.rs @@ -1,14 +1,11 @@ use std::collections::HashSet; -use wicked_waifus_protocol::{ - ClientCurrentRoleReportRequest, ClientCurrentRoleReportResponse, ERemoveEntityType, ErrorCode, - FormationAttrRequest, FormationAttrResponse, PlayerMotionRequest, PlayerMotionResponse, - RoleFavorListRequest, RoleFavorListResponse, RoleShowListUpdateRequest, - RoleShowListUpdateResponse, UpdateFormationRequest, UpdateFormationResponse, -}; - -use crate::logic::player::Player; +use crate::logic::components::{ParaglidingSkin, RoleSkin, SoarWingSkin, WeaponSkin}; +use crate::logic::ecs::component::ComponentContainer; +use crate::logic::player::{ItemUsage, Player}; use crate::logic::role::{Role, RoleFormation}; +use crate::modify_component; +use wicked_waifus_protocol::{ArrayIntInt, ClientCurrentRoleReportRequest, ClientCurrentRoleReportResponse, ERemoveEntityType, EntityAddNotify, EntityEquipSkinChangeNotify, EntityFlySkinChangeData, EntityPb, EntityRemoveInfo, EntityRemoveNotify, EquipFlySkinData, ErrorCode, FlySkinConfigData, FlySkinWearAllRoleRequest, FlySkinWearAllRoleResponse, FlySkinWearRequest, FlySkinWearResponse, FormationAttrRequest, FormationAttrResponse, PbUpLevelRoleRequest, PbUpLevelRoleResponse, PlayerMotionRequest, PlayerMotionResponse, RoleBreakThroughViewRequest, RoleBreakThroughViewResponse, RoleFavorListRequest, RoleFavorListResponse, RoleFlyEquipChangeNotify, RoleLevelUpViewRequest, RoleLevelUpViewResponse, RoleShowListUpdateRequest, RoleShowListUpdateResponse, RoleSkinChangeRequest, RoleSkinChangeResponse, SoarWingOrParaglidingSkinChangeNotify, UnlockRoleSkinListRequest, UnlockRoleSkinListResponse, UpdateFormationRequest, UpdateFormationResponse, WeaponSkinComponentPb}; pub fn on_role_show_list_update_request( player: &mut Player, @@ -152,3 +149,421 @@ pub fn on_player_motion_request( Some(_) => response.error_id = ErrorCode::Success.into(), } } + +pub fn on_unlock_role_skin_list_request( + player: &Player, + _request: UnlockRoleSkinListRequest, + response: &mut UnlockRoleSkinListResponse, +) { + response.role_skin_list = player.unlocked_skins.role_skins.iter().cloned().collect(); +} + +pub fn on_role_skin_change_request( + player: &mut Player, + request: RoleSkinChangeRequest, + response: &mut RoleSkinChangeResponse, +) { + // TODO: Should we verify role id first against bindata? + let role = player.role_list.get_mut(&request.role_id); + let Some(role) = role else { + response.error_code = ErrorCode::NotValidRole.into(); + return; + }; + + // Verify Id exist in bindata + let Some(skin_data) = + wicked_waifus_data::role_skin_data::iter().find(|data| data.id == request.skin_id) + else { + response.error_code = ErrorCode::ErrRoleSkinConfig.into(); + return; + }; + + // Verify Skin is unlocked + if !player.unlocked_skins.role_skins.contains(&skin_data.id) { + response.error_code = ErrorCode::ErrRoleSkinLocked.into(); + return; + } + + // Check that role has this skin + if skin_data.role_id != role.role_id { + response.error_code = ErrorCode::ErrRoleSkinNotMatch.into(); + return; + } + + role.skin_id = request.skin_id; + if request.is_wear_weapon_skin { + if skin_data.suit_weapon_skin_id == 0 { + response.error_code = ErrorCode::ErrRoleSkinWeaponNotSuit.into(); + return; + } + role.weapon_skin_id = skin_data.suit_weapon_skin_id; + } + { + let world_ref = player.world.borrow(); + let world = world_ref.get_world_entity(); + let entity_id = world.get_entity_id(request.role_id); + modify_component!( + world.get_entity_components(entity_id as i32), + RoleSkin, + |skin_component: &mut RoleSkin| { + skin_component.skin_id = role.skin_id; + } + ); + if request.is_wear_weapon_skin { + // Check for suit_weapon_skin_id == 0 has already been done + modify_component!( + world.get_entity_components(entity_id as i32), + WeaponSkin, + |skin_component: &mut WeaponSkin| { + skin_component.skin_id = skin_data.suit_weapon_skin_id; + } + ); + // Since the whole entity is recreated this shouldn't be needed but meh, whatever + player.notify(EntityEquipSkinChangeNotify { + entity_id, + weapon_skin_component_pb: Some(WeaponSkinComponentPb { + weapon_skin_id:skin_data.suit_weapon_skin_id, + }), + }); + } + player.notify(EntityRemoveNotify { + remove_infos: vec![EntityRemoveInfo { + entity_id, + r#type: 0, + }], + is_remove: false, + }); + + let mut pb = EntityPb { + id: entity_id, + ..Default::default() + }; + + world + .get_entity_components(entity_id as i32) + .into_iter() + .for_each(|comp| comp.set_pb_data(&mut pb)); + + player.notify(EntityAddNotify { + entity_pbs: vec![pb], + remove_tag_ids: false, + }); + } + player.notify(player.build_update_formation_notify()); + response.error_code = ErrorCode::Success.into(); +} + +pub fn on_fly_skin_wear_request( + player: &mut Player, + request: FlySkinWearRequest, + response: &mut FlySkinWearResponse, +) { + let role = player.role_list.get_mut(&request.role_id); + let Some(role) = role else { + response.error_code = ErrorCode::NotValidRole.into(); + return; + }; + + // Verify Id exist in bindata + let skin = + wicked_waifus_data::fly_skin_config_data::iter().find(|&skin| skin.id == request.skin_id); + let Some(skin) = skin else { + response.error_code = ErrorCode::NoFlySkinItem.into(); + return; + }; + + match skin.skin_type { + 0 => { + // Verify Skin is unlocked + if !player.unlocked_skins.fly_skins.contains(&skin.id) { + response.error_code = ErrorCode::ErrRoleSkinLocked.into(); + return; + } + role.fly_skin_id = request.skin_id + } + 1 => { + if !player.unlocked_skins.wing_skins.contains(&skin.id) { + response.error_code = ErrorCode::ErrRoleSkinLocked.into(); + return; + } + role.wing_skin_id = request.skin_id + } + _ => { + response.error_code = ErrorCode::FlySkinTypeErr.into(); + return; + } + } + { + let world_ref = player.world.borrow(); + let world = world_ref.get_world_entity(); + let entity_id = world.get_entity_id(request.role_id); + match skin.skin_type { + 0 => { + modify_component!( + world.get_entity_components(entity_id as i32), + SoarWingSkin, + |skin_component: &mut SoarWingSkin| { + skin_component.skin_id = role.skin_id; + } + ); + } + 1 => { + modify_component!( + world.get_entity_components(entity_id as i32), + ParaglidingSkin, + |skin_component: &mut ParaglidingSkin| { + skin_component.skin_id = role.skin_id; + } + ); + } + _ => unreachable!("Already tested above"), + } + player.notify(SoarWingOrParaglidingSkinChangeNotify { + fly_skin_data: vec![EntityFlySkinChangeData { + entity_id, + fly_skin_config_data: vec![FlySkinConfigData { + skin_id: request.skin_id, + fly_skin_id: skin.skin_type, + }], + }], + }); + } + + player.notify(RoleFlyEquipChangeNotify { + fly_skin_data: vec![EquipFlySkinData { + role_id: request.role_id, + skin_id: request.skin_id, + }], + }); + response.error_code = ErrorCode::Success.into(); +} + +pub fn on_fly_skin_wear_all_role_request( + player: &mut Player, + request: FlySkinWearAllRoleRequest, + response: &mut FlySkinWearAllRoleResponse, +) { + let skin = + wicked_waifus_data::fly_skin_config_data::iter().find(|&skin| skin.id == request.skin_id); + let Some(skin) = skin else { + response.error_code = ErrorCode::NoFlySkinItem.into(); + return; + }; + + match skin.skin_type { + 0 => { + // Verify Skin is unlocked + if !player.unlocked_skins.fly_skins.contains(&skin.id) { + response.error_code = ErrorCode::ErrRoleSkinLocked.into(); + return; + } + for role in player.role_list.values_mut() { + role.fly_skin_id = request.skin_id; + } + } + 1 => { + if !player.unlocked_skins.wing_skins.contains(&skin.id) { + response.error_code = ErrorCode::ErrRoleSkinLocked.into(); + return; + } + for role in player.role_list.values_mut() { + role.wing_skin_id = request.skin_id; + } + } + _ => { + response.error_code = ErrorCode::FlySkinTypeErr.into(); + return; + } + } + player.notify(RoleFlyEquipChangeNotify { + fly_skin_data: player + .role_list + .values() + .map(|r| EquipFlySkinData { + role_id: r.role_id, + skin_id: request.skin_id, + }) + .collect::>(), + }); + { + let world_ref = player.world.borrow(); + let world = world_ref.get_world_entity(); + let data = player + .role_list + .values() + .filter_map(|role| { + let entity_id = world.get_entity_id(role.role_id); + if entity_id == -1 { + None + } else { + match skin.skin_type { + 0 => { + modify_component!( + world.get_entity_components(entity_id as i32), + SoarWingSkin, + |skin_component: &mut SoarWingSkin| { + skin_component.skin_id = role.skin_id; + } + ); + } + 1 => { + modify_component!( + world.get_entity_components(entity_id as i32), + ParaglidingSkin, + |skin_component: &mut ParaglidingSkin| { + skin_component.skin_id = role.skin_id; + } + ); + } + _ => unreachable!("Already tested above"), + } + Some(EntityFlySkinChangeData { + entity_id, + fly_skin_config_data: vec![FlySkinConfigData { + skin_id: request.skin_id, + fly_skin_id: skin.skin_type, + }], + }) + } + }) + .collect::>(); + player.notify(SoarWingOrParaglidingSkinChangeNotify { + fly_skin_data: data, + }); + } + response.error_code = ErrorCode::Success.into(); +} + +pub fn on_role_level_up_view_request( + player: &mut Player, + request: RoleLevelUpViewRequest, + response: &mut RoleLevelUpViewResponse, +) { + let role = player.role_list.get(&request.role_id); + let Some(role) = role else { + response.error_code = ErrorCode::NotValidRole.into(); + return; + }; + + response.level = role.level; + // TODO: shall we get from data? seems client can do it by himself + response.level_exp_info = vec![ArrayIntInt { key: 1, value: 200 }]; + response.exp = role.exp; + // it seems add_exp, final_prop, cost_list amd overflow_list are handled by client, so skip + + let items = wicked_waifus_data::role_exp_item_data::iter() + .map(|(&id, _)| id) + .collect::>(); + response.item_list = player.inventory.to_array_int_int_filtered(&items); + response.error_code = ErrorCode::Success.into(); +} + +pub fn on_pb_up_level_role_request( + player: &mut Player, + request: PbUpLevelRoleRequest, + response: &mut PbUpLevelRoleResponse, +) { + response.role_id = request.role_id; + let role = player.role_list.get(&request.role_id); + let Some(role) = role else { + response.error_code = ErrorCode::NotValidRole.into(); + return; + }; + + // TODO: no shell_credit??? :turtle_skull: + let items = player.inventory.consume_items( + &request + .item_list + .iter() + .map(|item| ItemUsage { + id: item.key, + quantity: -item.value, + }) + .collect::>(), + ); + let Ok(_items) = items else { + response.error_code = ErrorCode::ErrConsumeNotEnough.into(); + return; + }; + + let mut total_exp = request + .item_list + .iter() + .filter_map(|item| { + wicked_waifus_data::role_exp_item_data::get(&item.key) + .map(|exp| exp.basic_exp * item.value) + }) + .sum(); + + let role_level_consume_id = wicked_waifus_data::role_info_data::iter() + .find(|role| role.id == request.role_id) + .map(|role| role.level_consume_id) + .unwrap_or(10001); + + let mut levels_consume = wicked_waifus_data::role_level_consume_data::iter() + .filter(|consume| { + consume.consume_group_id == role_level_consume_id && consume.level > role.level + }) // TODO: add upper bound too(till breakthrough) + .collect::>(); + levels_consume.sort_by_key(|item| item.level); + + let mut level = role.level; + for level_consume in levels_consume { + if level_consume.exp_count > total_exp { + break; + } + total_exp -= level_consume.exp_count; + level = level_consume.level; + } + response.level = level; + response.exp = total_exp; + + // TODO is item_map the overflowing items or all items used? also should we sent more notifies? + response.error_code = ErrorCode::Success.into(); +} + +// on_role_break_through_view_request + +pub fn on_role_break_through_view_request( + player: &mut Player, + request: RoleBreakThroughViewRequest, + response: &mut RoleBreakThroughViewResponse, +) { + let role = player.role_list.get(&request.role_id); + let Some(role) = role else { + response.error_code = ErrorCode::NotValidRole.into(); + return; + }; + + // TODO: + // if !condition { + // response.error_code = ErrorCode::ErrRoleConditionNotFind.into(); + // response.is_condition_finish = false; + // } + + let role_breach_id = wicked_waifus_data::role_info_data::iter() + .find(|role| role.id == request.role_id) + .map(|role| role.breach_id) + .unwrap_or(request.role_id); + + let condition = wicked_waifus_data::role_breach_data::iter() + .find(|role_breach_data| { + role_breach_data.breach_group_id == role_breach_id + && role_breach_data.breach_level == role.breakthrough + }) + .unwrap(); // TODO: handling + + response.error_code = ErrorCode::Success.into(); + response.level_limit = condition.max_level; + + response.cost_list = condition + .breach_consume + .iter() + .map(|(&id, &count)| ArrayIntInt { + key: id, + value: count, + }) + .collect::>(); + // TODO: un_lock_skill_id, reward_list, final_prop + response.is_condition_finish = true; // is this last?? +} diff --git a/wicked-waifus-game-server/src/logic/handler/scene.rs b/wicked-waifus-game-server/src/logic/handler/scene.rs index 5fa42e6..de6cda2 100644 --- a/wicked-waifus-game-server/src/logic/handler/scene.rs +++ b/wicked-waifus-game-server/src/logic/handler/scene.rs @@ -1,4 +1,9 @@ -use wicked_waifus_protocol::{ErrorCode, SceneLoadingFinishRequest, SceneLoadingFinishResponse, SceneTraceRequest, SceneTraceResponse, UpdateSceneDateRequest, UpdateSceneDateResponse, AccessPathTimeServerConfigRequest, AccessPathTimeServerConfigResponse, PlayerHeadDataRequest, PlayerHeadDataResponse, UnlockRoleSkinListRequest, UnlockRoleSkinListResponse, JsPatchNotify}; +use wicked_waifus_protocol::{ + AccessPathTimeServerConfigRequest, AccessPathTimeServerConfigResponse, ErrorCode, + JsPatchNotify, PlayerHeadDataRequest, PlayerHeadDataResponse, SceneLoadingFinishRequest, + SceneLoadingFinishResponse, SceneTraceRequest, SceneTraceResponse, UpdateSceneDateRequest, + UpdateSceneDateResponse, +}; const WATER_MASK: &str = include_str!("../../../scripts/watermask-disable.js"); const UID_FIX: &str = include_str!("../../../scripts/uidfix.js"); @@ -65,14 +70,3 @@ pub fn on_player_head_data_request( // TODO: port this from golang response.pi = vec![]; } - -pub fn on_unlock_role_skin_list_request( - _player: &Player, - _request: UnlockRoleSkinListRequest, - response: &mut UnlockRoleSkinListResponse, -) { - // TODO: port this from golang - response.role_skin_list = wicked_waifus_data::role_skin_data::iter() - .map(|data| data.id) - .collect::>(); -} \ No newline at end of file diff --git a/wicked-waifus-game-server/src/logic/handler/weapon.rs b/wicked-waifus-game-server/src/logic/handler/weapon.rs new file mode 100644 index 0000000..559468e --- /dev/null +++ b/wicked-waifus-game-server/src/logic/handler/weapon.rs @@ -0,0 +1,175 @@ +use crate::logic::components::{Equip, WeaponSkin}; +use crate::logic::ecs::component::ComponentContainer; +use crate::logic::player::Player; +use crate::modify_component; +use wicked_waifus_protocol::{EntityEquipChangeNotify, EntityEquipSkinChangeNotify, EquipComponentPb, EquipTakeOnNotify, EquipTakeOnRequest, EquipTakeOnResponse, EquipWeaponSkinRequest, EquipWeaponSkinResponse, ErrorCode, LoadEquipData, SendEquipSkinRequest, SendEquipSkinResponse, WeaponSkinComponentPb, WeaponSkinDeleteNotify, WeaponSkinRequest, WeaponSkinResponse}; + +pub fn on_weapon_skin_request( + player: &Player, + _request: WeaponSkinRequest, + response: &mut WeaponSkinResponse, +) { + response.equip_list = player + .role_list + .values() + .filter(|role| role.weapon_skin_id != 0) + .map(|role| LoadEquipData { + role_id: role.role_id, + skin_id: role.weapon_skin_id, + }) + .collect(); + + response.error_code = ErrorCode::Success.into(); +} + +pub fn on_equip_weapon_skin_request( + player: &mut Player, + request: EquipWeaponSkinRequest, + response: &mut EquipWeaponSkinResponse, +) { + let Some(equip_data) = request.data else { + return; + }; + + let role = player.role_list.get_mut(&equip_data.role_id); + let Some(role) = role else { + response.error_code = ErrorCode::NotValidRole.into(); + return; + }; + + // Verify Id exist in bindata + let Some(skin_data) = + wicked_waifus_data::weapon_skin_data::iter().find(|data| data.id == equip_data.skin_id) + else { + response.error_code = ErrorCode::WeaponSkinDataErr.into(); + return; + }; + + // Verify Skin is unlocked + if !player.unlocked_skins.weapon_skins.contains(&skin_data.id) { + response.error_code = ErrorCode::WeaponSkinUnLockErr.into(); + return; + } + + role.weapon_skin_id = equip_data.skin_id; + { + let world_ref = player.world.borrow(); + let world = world_ref.get_world_entity(); + let entity_id = world.get_entity_id(equip_data.role_id); + modify_component!( + world.get_entity_components(entity_id as i32), + WeaponSkin, + |skin_component: &mut WeaponSkin| { + skin_component.skin_id = equip_data.skin_id; + } + ); + player.notify(EntityEquipSkinChangeNotify { + entity_id, + weapon_skin_component_pb: Some(WeaponSkinComponentPb { + weapon_skin_id: equip_data.skin_id, + }), + }); + } + + // Is the all list needed or only the new one?? + response.data_list = player + .role_list + .values() + .filter(|role| role.weapon_skin_id != 0) + .map(|role| LoadEquipData { + role_id: role.role_id, + skin_id: role.weapon_skin_id, + }) + .collect(); + response.error_code = ErrorCode::Success.into(); +} + +pub fn on_send_equip_skin_request( + player: &mut Player, + request: SendEquipSkinRequest, + response: &mut SendEquipSkinResponse, +) { + let role = player.role_list.get_mut(&request.role_id); + let Some(role) = role else { + response.error_code = ErrorCode::NotValidRole.into(); + return; + }; + + let old_skin_id = role.weapon_skin_id; + role.weapon_skin_id = 0; + { + let world_ref = player.world.borrow(); + let world = world_ref.get_world_entity(); + let entity_id = world.get_entity_id(request.role_id); + modify_component!( + world.get_entity_components(entity_id as i32), + WeaponSkin, + |skin_component: &mut WeaponSkin| { + skin_component.skin_id = 0; + } + ); + player.notify(EntityEquipSkinChangeNotify { + entity_id, + weapon_skin_component_pb: Some(WeaponSkinComponentPb { + weapon_skin_id: 0, + }), + }); + player.notify(WeaponSkinDeleteNotify { + role_id: request.role_id, + skin_id: old_skin_id, + }) + } + response.error_code = ErrorCode::Success.into(); +} + +pub fn on_equip_take_on_request( + player: &mut Player, + request: EquipTakeOnRequest, + response: &mut EquipTakeOnResponse, +) { + let Some(equip_data) = request.data else { + return; + }; + + // TODO: Add sanity checks(add from another role, a.k.a.: switch from roles) + player.notify(EquipTakeOnNotify { data_list: vec![equip_data] }); + + let role = player.role_list.get_mut(&equip_data.role_id); + let Some(role) = role else { + response.error_code = ErrorCode::NotValidRole.into(); + return; + }; + + let Some((id, breach)) = player.inventory.get_weapon_equip_info(equip_data.equip_inc_id) else { + response.error_code = ErrorCode::ErrItemNotFound.into(); + return; + }; + role.equip_weapon = id; + + // TODO: Change attributes based on weapon (PbRolePropsNotify + buffs + CombatNotifyAttributeChangedNotify) + + { + // TODO: remove from old one if in scene in case of weapon switch + let world_ref = player.world.borrow(); + let world = world_ref.get_world_entity(); + let entity_id = world.get_entity_id(equip_data.role_id); + modify_component!( + world.get_entity_components(entity_id as i32), + Equip, + |equip_component: &mut Equip| { + equip_component.weapon_id = id; + equip_component.weapon_breach_level = breach; + } + ); + player.notify(EntityEquipChangeNotify { + entity_id, + equip_component: Some(EquipComponentPb { + weapon_id: id, + weapon_breach_level: breach, + }), + }) + } + // TODO: Should we return all of them?? + response.data_list = vec![equip_data]; + response.error_code = ErrorCode::Success.into(); +} \ No newline at end of file diff --git a/wicked-waifus-game-server/src/logic/player/mod.rs b/wicked-waifus-game-server/src/logic/player/mod.rs index 02b0c0c..de59a9c 100644 --- a/wicked-waifus-game-server/src/logic/player/mod.rs +++ b/wicked-waifus-game-server/src/logic/player/mod.rs @@ -11,16 +11,16 @@ use wicked_waifus_protocol::{ AdventreTask, AdventureManualData, AdventureUpdateNotify, AdviceSettingNotify, BuffItemNotify, ControlInfoNotify, EEntityType, ERemoveEntityType, EnergyInfo, EnergyUpdateNotify, EntityAddNotify, EntityConfigType, EntityPb, EntityRemoveInfo, EntityRemoveNotify, EntityState, - FavorItem, FightFormationNotifyInfo, FightRoleInfo, FightRoleInfos, FormationRoleInfo, - GroupFormation, HostTeleportUnlockNotify, InstDataNotify, ItemPkgOpenNotify, - LevelPlayInfoNotify, LivingStatus, MailInfosNotify, MapUnlockFieldNotify, - MonthCardDailyRewardNotify, MoonChasingTargetGetCountNotify, - MoonChasingTrackMoonHandbookRewardNotify, NormalItemUpdateNotify, PassiveSkillNotify, - PbGetRoleListNotify, PlayerAttr, PlayerAttrKey, PlayerAttrNotify, PlayerAttrType, - PlayerFightFormations, PlayerVarNotify, ProtocolUnit, PushContextIdNotify, - PushDataCompleteNotify, RoguelikeCurrencyNotify, RoleChangeUnlockNotify, RoleFavor, - RoleFavorListNotify, RoleMotion, RoleMotionListNotify, SettingNotify, TeleportUpdateNotify, - UpdateFormationNotify, UpdateGroupFormationNotify, + FavorItem, FightFormationNotifyInfo, FightRoleInfo, FightRoleInfos, FlyEquipAddNotify, + FlySkinEquipData, GroupFormation, HostTeleportUnlockNotify, InstDataNotify, ItemPkgOpenNotify, + LevelPlayInfoNotify, LivingStatus, MailInfosNotify, MonthCardDailyRewardNotify, + MoonChasingTargetGetCountNotify, MoonChasingTrackMoonHandbookRewardNotify, + NormalItemUpdateNotify, PassiveSkillNotify, PbGetRoleListNotify, PlayerAttr, PlayerAttrKey, + PlayerAttrNotify, PlayerAttrType, PlayerFightFormations, PlayerVarNotify, ProtocolUnit, + PushContextIdNotify, PushDataCompleteNotify, RoguelikeCurrencyNotify, RoleChangeUnlockNotify, + RoleFavor, RoleFavorListNotify, RoleFlyEquipNotify, RoleMotion, RoleMotionListNotify, + SettingNotify, TeleportUpdateNotify, UnlockSkinDataNotify, UpdateFormationNotify, + UpdateGroupFormationNotify, }; use wicked_waifus_protocol_internal::{PlayerBasicData, PlayerRoleData, PlayerSaveData}; @@ -46,16 +46,17 @@ use crate::logic::player::player_mc_element::PlayerMcElement; use crate::logic::player::player_month_card::PlayerMonthCard; use crate::logic::player::player_teleports::{PlayerTeleport, PlayerTeleports}; use crate::logic::player::player_tutorials::{PlayerTutorial, PlayerTutorials}; +use crate::logic::player::Element::Spectro; use crate::logic::{ components::{ - Attribute, EntityConfig, Equip, FightBuff, Movement, OwnerPlayer, PlayerOwnedEntityMarker, - Position, Visibility, VisionSkill, SoarWingSkin + Attribute, EntityConfig, Equip, FightBuff, Movement, OwnerPlayer, ParaglidingSkin, + PlayerOwnedEntityMarker, Position, SoarWingSkin, Visibility, VisionSkill, WeaponSkin, }, ecs::component::ComponentContainer, }; use crate::session::Session; -use crate::{config, create_player_entity_pb, query_components}; -use crate::logic::player::Element::Spectro; +use crate::{config, create_player_entity_pb}; +use crate::logic::player::player_unlocked_skins::PlayerUnlockedSkins; mod basic_info; mod explore_tools; @@ -72,6 +73,7 @@ mod player_mc_element; mod player_month_card; mod player_teleports; mod player_tutorials; +mod player_unlocked_skins; pub struct Player { session: Option>, @@ -93,6 +95,7 @@ pub struct Player { pub map_trace: PlayerMapTrace, pub month_card: PlayerMonthCard, pub mc_element: PlayerMcElement, + pub unlocked_skins: PlayerUnlockedSkins, // Runtime pub world: Rc>, pub last_save_time: u64, @@ -123,6 +126,10 @@ impl Player { self.notify(self.explore_tools.build_roulette_update_notify()); self.notify(self.build_role_favor_list_notify()); self.notify(self.func.build_func_open_notify()); + self.notify(self.build_weapon_skin_notify()); + self.notify(self.build_fly_equip_notify()); + self.notify(self.build_role_fly_equip_notify()); + self.notify(InstDataNotify { enter_infos: vec![], // TODO: No effect in normal world, to implement for dungeon::logic() }); @@ -211,6 +218,11 @@ impl Player { self.role_list.insert(role_id, Role::new(role_id)); }); } + for role in self.role_list.values() { + self.inventory + .add_weapon(role.equip_weapon, 0, 1, 0, 0, 0, role.role_id) + .unwrap(); + } self.formation_list.insert(1, RoleFormation::default()); self.cur_formation_id = 1; @@ -305,8 +317,8 @@ impl Player { RoleFavorListNotify { favor_list: self .role_list - .iter() - .map(|(_, role)| RoleFavor { + .values() + .map(|role| RoleFavor { role_id: role.role_id, level: role.favor_level, exp: role.favor_exp, @@ -338,12 +350,73 @@ impl Player { } } + pub fn build_weapon_skin_notify(&self) -> UnlockSkinDataNotify { + UnlockSkinDataNotify { + phantom_skin_list: self.unlocked_skins.weapon_skins.iter().cloned().collect(), + is_login: true, + } + } + + pub fn build_fly_equip_notify(&self) -> FlyEquipAddNotify { + FlyEquipAddNotify { + unlock_fly_skin_ids: self + .unlocked_skins + .fly_skins + .iter() + .chain(&self.unlocked_skins.wing_skins) + .cloned() + .collect(), + } + } + + pub fn build_role_fly_equip_notify(&self) -> RoleFlyEquipNotify { + let merged: Vec<_> = self + .unlocked_skins + .fly_skins + .iter() + .chain(&self.unlocked_skins.wing_skins) + .cloned() + .collect(); + + let mut equipped_skins: HashMap> = HashMap::new(); + for role in self.role_list.values() { + if role.fly_skin_id != 0 { + equipped_skins + .entry(role.fly_skin_id) + .or_default() + .push(role.role_id); + } + if role.wing_skin_id != 0 { + equipped_skins + .entry(role.wing_skin_id) + .or_default() + .push(role.role_id); + } + } + + RoleFlyEquipNotify { + fly_skin_equip_data: merged + .iter() + .map(|&skin| match equipped_skins.get(&skin) { + Some(role_list) => FlySkinEquipData { + role_ids: role_list.to_vec(), + skin_id: skin, + }, + None => FlySkinEquipData { + role_ids: vec![], + skin_id: skin, + }, + }) + .collect(), + } + } + pub fn build_motion_list_notify(&self) -> RoleMotionListNotify { RoleMotionListNotify { motion_list: self .role_list - .iter() - .map(|(_, role)| { + .values() + .map(|role| { RoleMotion { role_id: role.role_id, motion_ids: motion_data::iter() @@ -404,15 +477,10 @@ impl Player { fight_role_infos: cur_formation .role_ids .iter() - .map(|&role_id| { - let entity_id = world.get_entity_id(role_id); - let role_skin = - query_components!(world, entity_id, RoleSkin).0.unwrap(); - FightRoleInfo { - role_id, - entity_id: world.get_entity_id(role_id), - on_stage_without_control: false, - } + .map(|&role_id| FightRoleInfo { + role_id, + entity_id: world.get_entity_id(role_id), + on_stage_without_control: false, }) .collect(), cur_role: cur_formation.cur_role, @@ -449,14 +517,7 @@ impl Player { tracing::warn!("Role {} not found in use role list", role_id); return Default::default(); } - let role = *role_map.get(&role_id).unwrap(); - FormationRoleInfo { - role_id: role.role_id, - max_hp: 0, - cur_hp: 0, - level: role.level, - role_skin_id: role.skin_id, - } + role_map.get(role_id).unwrap().to_formation_protobuf() }) .collect(), is_current: formation.is_current, @@ -514,7 +575,7 @@ impl Player { }], }); self.notify(NormalItemUpdateNotify { - normal_item_list: self.inventory.to_normal_item_list_filtered(vec![3]), + normal_item_list: self.inventory.to_normal_item_list_filtered(&[3]), no_tips: false, }); self.notify(MonthCardDailyRewardNotify { @@ -596,6 +657,10 @@ impl Player { .mc_element .map(PlayerMcElement::load_from_save) .unwrap_or_default(), + unlocked_skins: save_data + .unlocked_skins + .map(PlayerUnlockedSkins::load_from_save) + .unwrap_or_default(), world: Rc::new(RefCell::new(World::new())), last_save_time: time_util::unix_timestamp(), quadrant_id: 0, @@ -631,6 +696,7 @@ impl Player { map_trace: Some(self.map_trace.build_save_data()), month_card: Some(self.month_card.build_save_data()), mc_element: Some(self.mc_element.build_save_data()), + unlocked_skins: Some(self.unlocked_skins.build_save_data()), } } @@ -639,12 +705,11 @@ impl Player { } pub fn build_role_list_notify(&self) -> PbGetRoleListNotify { - // TODO: There is a bug we are investigating with several resonators, this is a workaround PbGetRoleListNotify { role_list: self .role_list - .iter() - .map(|(_, role)| role.to_protobuf()) + .values() + .map(|role| role.to_protobuf()) .collect(), } } diff --git a/wicked-waifus-game-server/src/logic/player/player_inventory.rs b/wicked-waifus-game-server/src/logic/player/player_inventory.rs index c265df2..4348dcd 100644 --- a/wicked-waifus-game-server/src/logic/player/player_inventory.rs +++ b/wicked-waifus-game-server/src/logic/player/player_inventory.rs @@ -1,11 +1,15 @@ use std::collections::HashMap; +use std::sync::atomic::AtomicI32; +use wicked_waifus_protocol::{ArrayIntInt, NormalItem, WeaponItem}; -use wicked_waifus_protocol::NormalItem; - -use wicked_waifus_protocol_internal::PlayerInventoryData; +use crate::config; +use crate::logic::utils::seq_utils::{SequenceGenerator, Sequencer}; +use wicked_waifus_protocol_internal::{PlayerInventoryData, PlayerInventoryWeaponData}; pub struct PlayerInventory { items: HashMap, + weapons_seq: SequenceGenerator, + weapons: HashMap, } pub struct ItemUsage { @@ -33,13 +37,16 @@ impl PlayerInventory { pub fn load_from_save(data: PlayerInventoryData) -> Self { Self { + weapons_seq: SequenceGenerator::from_data(&data.weapons), items: data.items.clone(), + weapons: data.weapons.clone(), } } pub fn build_save_data(&self) -> PlayerInventoryData { PlayerInventoryData { items: self.items.clone(), + weapons: self.weapons.clone(), } } @@ -52,10 +59,19 @@ impl PlayerInventory { } pub fn consume_item(&mut self, id: i32, quantity: i32) -> Result { - Ok(*self.consume_items(&[ItemUsage { id, quantity: -quantity }])?.get(&id).unwrap()) + Ok(*self + .consume_items(&[ItemUsage { + id, + quantity: -quantity, + }])? + .get(&id) + .unwrap()) } - pub fn consume_items(&mut self, usages: &[ItemUsage]) -> Result, InventoryError> { + pub fn consume_items( + &mut self, + usages: &[ItemUsage], + ) -> Result, InventoryError> { if !self.has_enough_items(usages) { return Err(InventoryError::ItemsNotEnough()); } @@ -68,6 +84,11 @@ impl PlayerInventory { self.items.get(&Self::UNION_EXP_ID).copied().unwrap_or(0) } + #[inline(always)] + pub fn add_shell_credits(&mut self, count: i32) -> i32 { + self.add_internal(Self::SHELL_CREDIT_ID, count) + } + #[inline(always)] pub fn get_shell_credits(&self) -> i32 { self.items.get(&Self::SHELL_CREDIT_ID).copied().unwrap_or(0) @@ -97,35 +118,113 @@ impl PlayerInventory { // TODO: Check if this is item or not #[inline(always)] pub fn get_waveplate_crystal(&self) -> i32 { - self.items.get(&Self::WAVEPLATE_CRYSTAL_ID).copied().unwrap_or(0) + self.items + .get(&Self::WAVEPLATE_CRYSTAL_ID) + .copied() + .unwrap_or(0) } pub fn to_normal_item_list(&self) -> Vec { - self.items.iter() + self.items + .iter() .filter(|(&id, _)| Self::WAVEPLATE_ID != id && Self::WAVEPLATE_CRYSTAL_ID != id) // TODO: Implement expiration - .map(|(&id, &count)| NormalItem { id, count, expire_time: 0 }) + .map(|(&id, &count)| NormalItem { + id, + count, + expire_time: 0, + }) .collect::>() } - pub fn to_normal_item_list_filtered(&self, ids: Vec) -> Vec { - self.items.iter() + pub fn to_normal_item_list_filtered(&self, ids: &[i32]) -> Vec { + self.items + .iter() .filter(|(&id, _)| ids.contains(&id)) // TODO: Implement expiration - .map(|(&id, &count)| NormalItem { id, count, expire_time: 0 }) + .map(|(&id, &count)| NormalItem { + id, + count, + expire_time: 0, + }) .collect::>() } + pub fn to_array_int_int_filtered(&self, ids: &[i32]) -> Vec { + ids.iter() + .map(|id| ArrayIntInt { + key: *id, + value: self.items.get(id).copied().unwrap_or(0), + }) + .collect::>() + } + + pub fn add_weapon( + &mut self, + id: i32, + func: i32, + level: i32, + exp: i32, + breach: i32, + reson: i32, + role: i32, + ) -> Result { + let inc_id = self.weapons_seq.take_id(); + self.weapons.insert( + inc_id, + PlayerInventoryWeaponData { + id, + func_value: func, + level, + exp, + breach, + reson_level: reson, + role_id: role, + }, + ); + Ok(inc_id) + } + + pub fn remove_weapon(&mut self, id: i32) { + self.weapons.remove(&id); + self.weapons_seq.give_id(id); + } + + pub fn to_weapon_item_list(&self) -> Vec { + self.weapons + .iter() + .map(|(&inc_id, data)| WeaponItem { + id: data.id, + incr_id: inc_id, + func_value: data.func_value, + weapon_level: data.level, + weapon_exp: data.exp, + weapon_breach: data.breach, + weapon_reson_level: data.reson_level, + role_id: data.role_id, + }) + .collect() + } + + pub fn get_weapon_equip_info(&self, inc_id: i32) -> Option<(i32, i32)> { + self.weapons + .get(&inc_id) + .map(|weapon_data| (weapon_data.id, weapon_data.breach)) + } + #[inline(always)] fn add_internal(&mut self, id: i32, quantity: i32) -> i32 { - *self.items.entry(id) + *self + .items + .entry(id) .and_modify(|count| *count += quantity) .or_insert(quantity) } #[inline(always)] fn add_many_internal(&mut self, usages: &[ItemUsage]) -> HashMap { - usages.iter() + usages + .iter() .filter(|usage| usage.quantity != 0) .map(|delta| (delta.id, self.add_internal(delta.id, delta.quantity))) .collect::>() @@ -138,17 +237,80 @@ impl PlayerInventory { #[inline(always)] fn has_enough_items(&self, items_delta: &[ItemUsage]) -> bool { - items_delta.iter() - .fold(true, |is_enough, delta| { - is_enough && self.has_enough_item(delta.id, -delta.quantity) - }) + items_delta + .iter() + .all(|delta| self.has_enough_item(delta.id, -delta.quantity)) } } impl Default for PlayerInventory { fn default() -> Self { + let mut weapons_seq = SequenceGenerator::new(); + let default_unlocks = &config::get_config().default_unlocks; + let weapons: HashMap = + match default_unlocks.unlock_all_weapons { + true => wicked_waifus_data::weapon_conf_data::iter() + .map(|data| { + let (level, breach) = if default_unlocks.unlock_all_weapons_max_level { + ( + wicked_waifus_data::weapon_level_data::iter() + .filter(|level_data| level_data.level_id == data.level_id) + .map(|level_data| level_data.level) + .max() + .unwrap_or(1), + wicked_waifus_data::weapon_breach_data::iter() + .filter(|level_data| level_data.breach_id == data.breach_id) + .map(|level_data| level_data.level) + .max() + .unwrap_or(0), + ) + } else { + ( + wicked_waifus_data::weapon_level_data::iter() + .filter(|level_data| level_data.level_id == data.level_id) + .map(|level_data| level_data.level) + .min() + .unwrap_or(1), + wicked_waifus_data::weapon_breach_data::iter() + .filter(|level_data| level_data.breach_id == data.breach_id) + .map(|level_data| level_data.level) + .min() + .unwrap_or(0), + ) + }; + let reson_level = if default_unlocks.unlock_all_weapons_all_reson { + wicked_waifus_data::weapon_reson_data::iter() + .filter(|level_data| level_data.reson_id == data.reson_id) + .map(|level_data| level_data.level) + .max() + .unwrap_or(0) + } else { + wicked_waifus_data::weapon_reson_data::iter() + .filter(|level_data| level_data.reson_id == data.reson_id) + .map(|level_data| level_data.level) + .min() + .unwrap_or(0) + }; + ( + weapons_seq.take_id(), + PlayerInventoryWeaponData { + id: data.item_id, + func_value: 0, + level, + exp: 0, + breach, + reson_level, + role_id: 0, + }, + ) + }) + .collect::>(), + false => Default::default(), + }; Self { items: HashMap::new(), + weapons_seq, + weapons, } } -} \ No newline at end of file +} diff --git a/wicked-waifus-game-server/src/logic/player/player_unlocked_skins.rs b/wicked-waifus-game-server/src/logic/player/player_unlocked_skins.rs new file mode 100644 index 0000000..a2630b9 --- /dev/null +++ b/wicked-waifus-game-server/src/logic/player/player_unlocked_skins.rs @@ -0,0 +1,72 @@ +use crate::config; +use std::collections::HashSet; +use wicked_waifus_protocol_internal::PlayerUnlockedSkinsData; + +pub struct PlayerUnlockedSkins { + pub role_skins: HashSet, + pub weapon_skins: HashSet, + pub fly_skins: HashSet, + pub wing_skins: HashSet, +} + +impl PlayerUnlockedSkins { + pub fn load_from_save(data: PlayerUnlockedSkinsData) -> Self { + Self { + role_skins: data.role_skins.iter().cloned().collect(), + weapon_skins: data.weapon_skins.iter().cloned().collect(), + fly_skins: data.fly_skins.iter().cloned().collect(), + wing_skins: data.wing_skins.iter().cloned().collect(), + } + } + + pub fn build_save_data(&self) -> PlayerUnlockedSkinsData { + PlayerUnlockedSkinsData { + role_skins: self.role_skins.iter().cloned().collect(), + weapon_skins: self.weapon_skins.iter().cloned().collect(), + fly_skins: self.fly_skins.iter().cloned().collect(), + wing_skins: self.wing_skins.iter().cloned().collect(), + } + } +} + +impl Default for PlayerUnlockedSkins { + fn default() -> Self { + let unlocks = &config::get_config().default_unlocks; + + Self { + role_skins: if unlocks.unlock_all_role_skins { + wicked_waifus_data::role_skin_data::iter() + .map(|skin| skin.id) + .collect() + } else { + HashSet::new() + }, + + weapon_skins: if unlocks.unlock_all_weapon_skins { + wicked_waifus_data::weapon_skin_data::iter() + .map(|skin| skin.id) + .collect() + } else { + HashSet::new() + }, + + fly_skins: if unlocks.unlock_all_fly_skins { + wicked_waifus_data::fly_skin_config_data::iter() + .filter(|skin| skin.skin_type == 0) + .map(|skin| skin.id) + .collect() + } else { + HashSet::new() + }, + + wing_skins: if unlocks.unlock_all_wing_skins { + wicked_waifus_data::fly_skin_config_data::iter() + .filter(|skin| skin.skin_type == 1) + .map(|skin| skin.id) + .collect() + } else { + HashSet::new() + }, + } + } +} diff --git a/wicked-waifus-game-server/src/logic/role/mod.rs b/wicked-waifus-game-server/src/logic/role/mod.rs index 09c674a..268b552 100644 --- a/wicked-waifus-game-server/src/logic/role/mod.rs +++ b/wicked-waifus-game-server/src/logic/role/mod.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use wicked_waifus_protocol::{ArrayIntInt, RoleInfo}; +use wicked_waifus_protocol::{ArrayIntInt, FormationRoleInfo, RoleInfo}; use crate::config; use crate::logic::utils::growth_utils::get_role_props_by_level; @@ -50,6 +50,9 @@ pub struct Role { pub element_energy: i32, pub favor_level: i32, pub favor_exp: i32, + pub wing_skin_id: i32, + pub fly_skin_id: i32, + pub weapon_skin_id: i32, } impl Role { @@ -119,21 +122,44 @@ impl Role { let default_unlocks = &config::get_config().default_unlocks; let (level, breakthrough) = if default_unlocks.unlock_all_roles_max_level { - (data.max_level, 6) + ( + data.max_level, + wicked_waifus_data::role_breach_data::iter() + .filter(|level_data| level_data.breach_group_id == data.breach_id) + .map(|level_data| level_data.breach_level) + .max() + .unwrap_or(0), + ) } else { - (1, 0) + ( + 1, + wicked_waifus_data::role_breach_data::iter() + .filter(|level_data| level_data.breach_group_id == data.breach_id) + .map(|level_data| level_data.breach_level) + .min() + .unwrap_or(0), + ) }; let resonant_chain_group_index = if default_unlocks.unlock_all_roles_all_sequences { - 6 + wicked_waifus_data::resonant_chain_data::iter() + .filter(|level_data| level_data.group_id == data.resonant_chain_group_id) + .map(|level_data| level_data.group_index) + .max() + .unwrap_or(0) } else { - 0 + wicked_waifus_data::resonant_chain_data::iter() + .filter(|level_data| level_data.group_id == data.resonant_chain_group_id) + .map(|level_data| level_data.group_index) + .min() + .unwrap_or(0) }; + // TODO: add weapon and echo stats let base_stats = &get_role_props_by_level(role_id, level, breakthrough); Self { role_id, name: String::with_capacity(0), level, - exp: 0, // TODO: Compute based on level?? + exp: 0, breakthrough, skill_map: HashMap::new(), // TODO! star: 0, @@ -151,12 +177,16 @@ impl Role { element_energy: base_stats.element_energy, favor_level: 0, favor_exp: 0, + wing_skin_id: 0, + fly_skin_id: 0, + weapon_skin_id: 0, } } pub fn get_base_properties(&self) -> BasePropertyData { // Overwrite dynamic attributes with stores values let mut base_stats = get_role_props_by_level(self.role_id, self.level, self.breakthrough); + // TODO: add weapon and echo stats // TODO: Integrity check, value has to be between 0 and max base_stats.life = self.hp; base_stats.energy = self.energy; @@ -169,6 +199,7 @@ impl Role { } pub fn to_protobuf(&self) -> RoleInfo { + // TODO: add weapon and echo stats let base_prop: HashMap = load_key_value(&self.get_base_properties()); RoleInfo { role_id: self.role_id, @@ -192,6 +223,17 @@ impl Role { } } + pub fn to_formation_protobuf(&self) -> FormationRoleInfo { + let base_stats = get_role_props_by_level(self.role_id, self.level, self.breakthrough); + FormationRoleInfo { + role_id: self.role_id, + max_hp: base_stats.life_max, + cur_hp: base_stats.life, + level: self.level, + role_skin_id: self.skin_id, + } + } + pub fn load_from_save(data: RoleData) -> (i32, Self) { ( data.role_id, @@ -217,6 +259,9 @@ impl Role { element_energy: data.stats.unwrap().element_energy, favor_level: data.favor_level, favor_exp: data.favor_exp, + wing_skin_id: data.wing_skin_id, + fly_skin_id: data.fly_skin_id, + weapon_skin_id: data.weapon_skin_id, }, ) } @@ -250,6 +295,9 @@ impl Role { }), favor_level: self.favor_level, favor_exp: self.favor_exp, + wing_skin_id: self.wing_skin_id, + fly_skin_id: self.fly_skin_id, + weapon_skin_id: self.weapon_skin_id, ..Default::default() } } diff --git a/wicked-waifus-game-server/src/logic/utils/action_utils.rs b/wicked-waifus-game-server/src/logic/utils/action_utils.rs index 309951c..7fd494e 100644 --- a/wicked-waifus-game-server/src/logic/utils/action_utils.rs +++ b/wicked-waifus-game-server/src/logic/utils/action_utils.rs @@ -229,7 +229,7 @@ fn collect_action(player: &mut Player, .collect::>(); let updated_items = player.inventory.add_items(&usages); let normal_item_list = player.inventory.to_normal_item_list_filtered( - updated_items.keys().cloned().collect::>() + &updated_items.keys().cloned().collect::>() ); player.notify(NormalItemUpdateNotify { normal_item_list, no_tips: false }); // UpdateHandBookActiveStateMapNotify diff --git a/wicked-waifus-game-server/src/logic/utils/mod.rs b/wicked-waifus-game-server/src/logic/utils/mod.rs index c7eec47..99d59d8 100644 --- a/wicked-waifus-game-server/src/logic/utils/mod.rs +++ b/wicked-waifus-game-server/src/logic/utils/mod.rs @@ -1,8 +1,9 @@ pub mod action_utils; pub mod condition_utils; pub mod entity_serializer; -pub mod load_role_info; -pub mod world_util; -pub mod quadrant_util; pub mod growth_utils; +pub mod load_role_info; +pub mod quadrant_util; +pub mod seq_utils; pub mod tag_utils; +pub mod world_util; diff --git a/wicked-waifus-game-server/src/logic/utils/quadrant_util.rs b/wicked-waifus-game-server/src/logic/utils/quadrant_util.rs index f21db46..30470de 100644 --- a/wicked-waifus-game-server/src/logic/utils/quadrant_util.rs +++ b/wicked-waifus-game-server/src/logic/utils/quadrant_util.rs @@ -3,7 +3,7 @@ use std::sync::OnceLock; use wicked_waifus_data::LevelEntityConfigData; -struct StaticConfig { +pub(crate) struct StaticConfig { edge_size: f32, edge_check: f32, } diff --git a/wicked-waifus-game-server/src/logic/utils/seq_utils.rs b/wicked-waifus-game-server/src/logic/utils/seq_utils.rs new file mode 100644 index 0000000..de48064 --- /dev/null +++ b/wicked-waifus-game-server/src/logic/utils/seq_utils.rs @@ -0,0 +1,55 @@ +use std::collections::{HashMap, VecDeque}; + +pub trait Sequencer { + fn new() -> Self; + fn from_data(data: &HashMap) -> Self; + fn take_id(&mut self) -> T; + fn give_id(&mut self, id: T); +} + +pub struct SequenceGenerator { + recycled_ids: VecDeque, + next_id: A, +} + +macro_rules! sequence_trait_impl { + ($t:ty, $a:ty) => ( + impl Sequencer<$t> for SequenceGenerator<$t, $a> { + fn new() -> Self { + Self { + recycled_ids: Default::default(), + next_id: <$a>::new(1), + } + } + + fn from_data(data: &HashMap<$t, V>) -> Self { + let max_id = data.keys().max().copied().unwrap_or(1); + let next_id = <$a>::new(max_id); + + let mut recycled_ids = VecDeque::new(); + for i in 1..max_id { + if !data.contains_key(&i) { + recycled_ids.push_back(i); + } + } + Self { + recycled_ids, + next_id, + } + } + + fn take_id(&mut self) -> $t { + self.recycled_ids + .pop_front() + .unwrap_or_else(|| self.next_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed)) + } + + fn give_id(&mut self, id: $t) { + self.recycled_ids.push_back(id); + } + } + ) +} + +sequence_trait_impl!(i32, std::sync::atomic::AtomicI32); +sequence_trait_impl!(i64, std::sync::atomic::AtomicI64); \ No newline at end of file diff --git a/wicked-waifus-game-server/src/logic/utils/world_util.rs b/wicked-waifus-game-server/src/logic/utils/world_util.rs index 4a0634e..db0edb8 100644 --- a/wicked-waifus-game-server/src/logic/utils/world_util.rs +++ b/wicked-waifus-game-server/src/logic/utils/world_util.rs @@ -9,7 +9,7 @@ use wicked_waifus_data::{ blueprint_config_data, template_config_data, EntityLogic, EntityType, LevelEntityConfigData, }; -use crate::logic::components::{Autonomous, Fsm, Interact, MonsterAi, SoarWingSkin, StateTag, Tag}; +use crate::logic::components::{Autonomous, Fsm, Interact, MonsterAi, ParaglidingSkin, SoarWingSkin, StateTag, Tag, WeaponSkin}; use crate::logic::ecs::entity::EntityBuilder; use crate::logic::ecs::world::World; use crate::logic::math::Transform; @@ -87,7 +87,13 @@ macro_rules! create_player_entity_pb { skin_id: role.skin_id, })) .with(ComponentContainer::SoarWingSkin(SoarWingSkin { - skin_id: 84000001, + skin_id: role.fly_skin_id, + })) + .with(ComponentContainer::ParaglidingSkin(ParaglidingSkin { + skin_id: role.wing_skin_id, + })) + .with(ComponentContainer::WeaponSkin(WeaponSkin { + skin_id: role.weapon_skin_id, })) .with(ComponentContainer::FightBuff(buf_manager)) .build(); @@ -181,7 +187,13 @@ pub fn add_player_entities(player: &Player) { skin_id: role.skin_id, })) .with(ComponentContainer::SoarWingSkin(SoarWingSkin { - skin_id: 84000001, + skin_id: role.fly_skin_id, + })) + .with(ComponentContainer::ParaglidingSkin(ParaglidingSkin { + skin_id: role.wing_skin_id, + })) + .with(ComponentContainer::WeaponSkin(WeaponSkin { + skin_id: role.weapon_skin_id, // TODO: Is this kept on weapon change })) .with(ComponentContainer::FightBuff(buf_manager)) .build(); @@ -243,11 +255,10 @@ fn build_player_info_list(world: &World) -> Vec { world.get_world_entity(), PlayerOwnedEntityMarker, OwnerPlayer, - EntityConfig, - RoleSkin + EntityConfig ) .into_iter() - .filter(|(_, _, owner, _, _)| owner.0 == sp.player_id); + .filter(|(_, _, owner, _)| owner.0 == sp.player_id); ScenePlayerInformation { cur_role: cur_role_id, @@ -264,7 +275,7 @@ fn build_player_info_list(world: &World) -> Vec { cur_role: cur_role_id, // is_retain: true, fight_role_infos: active_characters - .map(|(id, _, _, conf, role_skin)| FightRoleInfo { + .map(|(id, _, _, conf)| FightRoleInfo { entity_id: id.into(), role_id: conf.config_id, on_stage_without_control: false, diff --git a/wicked-waifus-protocol-internal/proto/data.proto b/wicked-waifus-protocol-internal/proto/data.proto index 18f057c..4e1a2b8 100644 --- a/wicked-waifus-protocol-internal/proto/data.proto +++ b/wicked-waifus-protocol-internal/proto/data.proto @@ -60,6 +60,9 @@ message RoleData { RoleStats stats = 17; int32 favor_level = 18; int32 favor_exp = 19; + int32 wing_skin_id = 20; + int32 fly_skin_id = 21; + int32 weapon_skin_id = 22; } message RoleFormationData { @@ -135,8 +138,19 @@ message PlayerAdventureStatusData { repeated PlayerAdventureGlobalStatusData status = 1; } +message PlayerInventoryWeaponData { + int32 id = 1; + int32 func_value = 2; + int32 level = 3; + int32 exp = 4; + int32 breach = 5; + int32 reson_level = 6; + int32 role_id = 7; +} + message PlayerInventoryData { map items = 1; + map weapons = 2; } message PlayerTeleportData { @@ -182,6 +196,13 @@ message PlayerMcElementData { PlayerMcElementType current_element = 2; } +message PlayerUnlockedSkinsData { + repeated int32 role_skins = 1; + repeated int32 weapon_skins = 2; + repeated int32 fly_skins = 3; + repeated int32 wing_skins = 4; +} + message PlayerSaveData { PlayerBasicData basic_data = 1; PlayerRoleData role_data = 2; @@ -198,4 +219,5 @@ message PlayerSaveData { PlayerMapTraceData map_trace = 13; PlayerMonthCardData month_card = 14; PlayerMcElementData mc_element = 15; + PlayerUnlockedSkinsData unlocked_skins = 16; }