diff --git a/Cargo.lock b/Cargo.lock index 1377ae0..2689f5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,7 +402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -416,7 +416,7 @@ checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -724,13 +724,19 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hashlink" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -890,12 +896,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -1927,7 +1933,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown", + "hashbrown 0.14.5", "hashlink", "hex", "indexmap", diff --git a/game-server/src/logic/components/entity_config.rs b/game-server/src/logic/components/entity_config.rs index e9a6174..03f99aa 100644 --- a/game-server/src/logic/components/entity_config.rs +++ b/game-server/src/logic/components/entity_config.rs @@ -1,15 +1,19 @@ -use shorekeeper_protocol::EntityConfigType; +use shorekeeper_protocol::{EEntityType, EntityConfigType, EntityState}; use crate::logic::ecs::component::Component; pub struct EntityConfig { pub config_id: i32, pub config_type: EntityConfigType, + pub entity_type: EEntityType, + pub entity_state: EntityState } impl Component for EntityConfig { fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) { pb.config_id = self.config_id; pb.config_type = self.config_type.into(); + pb.entity_type = self.entity_type.into(); + pb.entity_state = self.entity_state.into(); } } diff --git a/game-server/src/logic/components/fsm.rs b/game-server/src/logic/components/fsm.rs new file mode 100644 index 0000000..bbd0ed3 --- /dev/null +++ b/game-server/src/logic/components/fsm.rs @@ -0,0 +1,25 @@ +use crate::logic::ecs::component::Component; +use shorekeeper_protocol::entity_component_pb::ComponentPb; +use shorekeeper_protocol::{DFsm, DFsmBlackBoard, EntityComponentPb, EntityFsmComponentPb, FsmCustomBlackboardDatas}; + +pub struct Fsm { + pub fsms: Vec, + pub hash_code: i32, + pub common_hash_code: i32, + pub black_board: Vec, + pub fsm_custom_blackboard_datas: Option, +} + +impl Component for Fsm { + fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) { + pb.component_pbs.push(EntityComponentPb { + component_pb: Some(ComponentPb::EntityFsmComponentPb(EntityFsmComponentPb { + fsms: self.fsms.clone(), + hash_code: self.hash_code, + common_hash_code: self.common_hash_code, + black_board: self.black_board.clone(), + fsm_custom_blackboard_datas: self.fsm_custom_blackboard_datas.clone(), + })), + }) + } +} diff --git a/game-server/src/logic/components/mod.rs b/game-server/src/logic/components/mod.rs index 910823c..87d4661 100644 --- a/game-server/src/logic/components/mod.rs +++ b/game-server/src/logic/components/mod.rs @@ -7,6 +7,8 @@ mod player_entity_marker; mod position; mod visibility; mod vision_skill; +mod monster_ai; +mod fsm; pub use attribute::Attribute; pub use entity_config::EntityConfig; @@ -17,3 +19,5 @@ pub use player_entity_marker::PlayerEntityMarker; pub use position::Position; pub use visibility::Visibility; pub use vision_skill::VisionSkill; +pub use monster_ai::MonsterAi; +pub use fsm::Fsm; diff --git a/game-server/src/logic/components/monster_ai.rs b/game-server/src/logic/components/monster_ai.rs new file mode 100644 index 0000000..7a6f113 --- /dev/null +++ b/game-server/src/logic/components/monster_ai.rs @@ -0,0 +1,23 @@ +use crate::logic::ecs::component::Component; +use shorekeeper_protocol::entity_component_pb::ComponentPb; +use shorekeeper_protocol::{EntityComponentPb, MonsterAiComponentPb}; + +pub struct MonsterAi { + pub weapon_id: i32, + pub hatred_group_id: i64, + pub ai_team_init_id: i32, + pub combat_message_id: i64, +} + +impl Component for MonsterAi { + fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) { + pb.component_pbs.push(EntityComponentPb { + component_pb: Some(ComponentPb::MonsterAiComponentPb(MonsterAiComponentPb { + weapon_id: self.weapon_id, + hatred_group_id: self.hatred_group_id, + ai_team_init_id: self.ai_team_init_id, + combat_message_id: self.combat_message_id, + })), + }) + } +} diff --git a/game-server/src/logic/ecs/component.rs b/game-server/src/logic/ecs/component.rs index 40491dc..efe27cf 100644 --- a/game-server/src/logic/ecs/component.rs +++ b/game-server/src/logic/ecs/component.rs @@ -32,6 +32,8 @@ impl_component_container! { Movement; Equip; VisionSkill; + MonsterAi; + Fsm; } pub trait Component { diff --git a/game-server/src/logic/handler/entity.rs b/game-server/src/logic/handler/entity.rs index ff18a46..c877bf2 100644 --- a/game-server/src/logic/handler/entity.rs +++ b/game-server/src/logic/handler/entity.rs @@ -1,10 +1,9 @@ -use crate::{logic::ecs::component::ComponentContainer, logic::player::Player, query_components}; -use shorekeeper_protocol::entity_component_pb::ComponentPb; -use shorekeeper_protocol::{ - EntityActiveRequest, EntityActiveResponse, EntityComponentPb, EntityLoadCompleteRequest, - EntityLoadCompleteResponse, EntityOnLandedRequest, EntityOnLandedResponse, - EntityPositionRequest, EntityPositionResponse, ErrorCode, MovePackagePush, -}; +use shorekeeper_protocol::{EntityActiveRequest, EntityActiveResponse, EntityLoadCompleteRequest, + EntityLoadCompleteResponse, EntityOnLandedRequest, + EntityOnLandedResponse, EntityPb, EntityPositionRequest, + EntityPositionResponse, ErrorCode, MovePackagePush}; + +use crate::{logic, logic::ecs::component::ComponentContainer, logic::player::Player, query_components}; pub fn on_entity_active_request( player: &Player, @@ -23,18 +22,27 @@ pub fn on_entity_active_request( return; }; - if let (Some(position), Some(attribute)) = + let component_pbs = { + let mut pb = EntityPb { + id: request.entity_id, + ..Default::default() + }; + + world.get_entity_components(request.entity_id as i32) + .into_iter() + .for_each(|comp| comp.set_pb_data(&mut pb)); + pb.component_pbs + }; + + // TODO: Remove attribute + if let (Some(position), Some(_attribute)) = query_components!(world, request.entity_id, Position, Attribute) { response.is_visible = true; response.pos = Some(position.0.get_position_protobuf()); response.rot = Some(position.0.get_rotation_protobuf()); - response.component_pbs.push(EntityComponentPb { - component_pb: Some(ComponentPb::AttributeComponent( - attribute.build_entity_attribute(), - )), - }); + response.component_pbs.extend_from_slice(&component_pbs); response.error_code = ErrorCode::Success.into(); } else { @@ -85,29 +93,47 @@ pub fn on_entity_load_complete_request( } pub fn on_move_package_push(player: &mut Player, push: MovePackagePush) { - let world_ref = player.world.borrow(); - let world = world_ref.get_world_entity(); - for moving_entity in push.moving_entities { - if !world.is_in_all_world_map(moving_entity.entity_id as i32) { - tracing::debug!( + // Query components borrows world component so lets wrap it + { + let world_ref = player.world.borrow(); + let world = world_ref.get_world_entity(); + + if !world.is_in_all_world_map(moving_entity.entity_id as i32) { + tracing::debug!( "MovePackage: entity with id {} doesn't exist", moving_entity.entity_id ); - continue; - } + continue; + } - let Some(mut movement) = query_components!(world, moving_entity.entity_id, Movement).0 - else { - tracing::warn!( + let Some(mut movement) = query_components!(world, moving_entity.entity_id, Movement).0 + else { + tracing::warn!( "MovePackage: entity {} doesn't have movement component", moving_entity.entity_id ); - continue; - }; + continue; + }; - movement - .pending_movement_vec - .extend(moving_entity.move_infos); + movement + .pending_movement_vec + .extend(moving_entity.move_infos); + } + + // TODO: review instance id vs map id in world + let map = logic::utils::quadrant_util::get_map(player.location.instance_id); + let quadrant_id = map.get_quadrant_id( + player.location.position.position.x * 100.0, + player.location.position.position.y * 100.0, + ); + + // TODO: This may require some changes for Co-Op + if quadrant_id != player.quadrant_id { + let (entities_to_remove, entities_to_add) = map.get_update_entities(player.quadrant_id, quadrant_id); + player.quadrant_id = quadrant_id; + logic::utils::world_util::remove_entities(player, &entities_to_remove); + logic::utils::world_util::add_entities(player, &entities_to_add); + } } } diff --git a/game-server/src/logic/math/transform.rs b/game-server/src/logic/math/transform.rs index 6b6de1a..497bdb7 100644 --- a/game-server/src/logic/math/transform.rs +++ b/game-server/src/logic/math/transform.rs @@ -1,3 +1,4 @@ +use shorekeeper_data::RawVectorData; use shorekeeper_protocol::{Rotator, TransformData}; use super::Vector3f; @@ -47,3 +48,12 @@ impl Transform { } } } + +impl From<&[RawVectorData]> for Transform { + fn from(transform: &[RawVectorData]) -> Self { + Self { + position: Vector3f::from(&transform[0]), + ..Default::default() + } + } +} \ No newline at end of file diff --git a/game-server/src/logic/math/vector.rs b/game-server/src/logic/math/vector.rs index 81035a6..0bcab3d 100644 --- a/game-server/src/logic/math/vector.rs +++ b/game-server/src/logic/math/vector.rs @@ -1,3 +1,4 @@ +use shorekeeper_data::RawVectorData; use shorekeeper_protocol::{Vector, VectorData}; #[derive(Default, Clone, PartialEq, Debug)] @@ -40,3 +41,13 @@ impl Vector3f { } } } + +impl From<&RawVectorData> for Vector3f { + fn from(transform: &RawVectorData) -> Self { + Self { + x: transform.x / 100.0, + y: transform.y / 100.0, + z: transform.z / 100.0, + } + } +} \ No newline at end of file diff --git a/game-server/src/logic/player/mod.rs b/game-server/src/logic/player/mod.rs index 3f230b1..94a025c 100644 --- a/game-server/src/logic/player/mod.rs +++ b/game-server/src/logic/player/mod.rs @@ -1,6 +1,6 @@ use common::time_util; use shorekeeper_protocol::{ - EEntityType, ERemoveEntityType, EntityAddNotify, EntityConfigType, EntityPb, EntityRemoveInfo, + EEntityType, EntityState, ERemoveEntityType, EntityAddNotify, EntityConfigType, EntityPb, EntityRemoveInfo, EntityRemoveNotify, FightFormationNotifyInfo, FightRoleInfo, FightRoleInfos, FormationRoleInfo, GroupFormation, ItemPkgOpenNotify, LivingStatus, PbGetRoleListNotify, PlayerBasicData, PlayerFightFormations, PlayerRoleData, PlayerSaveData, ProtocolUnit, UpdateFormationNotify, @@ -55,6 +55,7 @@ pub struct Player { // Runtime pub world: Rc>, pub last_save_time: u64, + pub quadrant_id: u64, } impl Player { @@ -312,6 +313,7 @@ impl Player { .unwrap_or_default(), world: Rc::new(RefCell::new(World::new())), last_save_time: time_util::unix_timestamp(), + quadrant_id: 0, } } diff --git a/game-server/src/logic/thread_mgr.rs b/game-server/src/logic/thread_mgr.rs index 4151c63..cbf29cc 100644 --- a/game-server/src/logic/thread_mgr.rs +++ b/game-server/src/logic/thread_mgr.rs @@ -19,10 +19,7 @@ use std::{ use super::{ecs::world::World, player::Player, utils::world_util}; use crate::logic::ecs::world::WorldEntity; -use crate::{ - player_save_task::{self, PlayerSaveReason}, - session::Session, -}; +use crate::{logic, player_save_task::{self, PlayerSaveReason}, session::Session}; const WATER_MASK: &str = include_str!("../../watermask-rr.js"); const UID_FIX: &str = include_str!("../../uidfix.js"); @@ -200,6 +197,16 @@ fn handle_logic_input(state: &mut LogicState, input: LogicInput) { content: CENSORSHIP_FIX.to_string(), }); + let map = logic::utils::quadrant_util::get_map(player.location.instance_id); + let quadrant_id = map.get_quadrant_id( + player.location.position.position.x * 100.0, + player.location.position.position.y * 100.0, + ); + player.quadrant_id = quadrant_id; + + let entities = map.get_initial_entities(quadrant_id); + world_util::add_entities(&player, &entities); + drop(player); state @@ -224,6 +231,7 @@ fn handle_logic_input(state: &mut LogicState, input: LogicInput) { let _ = state.worlds.remove(&player_id); // TODO: kick co-op players from removed world + // TODO: Remove all entitie player_save_task::push( player_id, diff --git a/game-server/src/logic/utils/mod.rs b/game-server/src/logic/utils/mod.rs index aab01f6..919f7dc 100644 --- a/game-server/src/logic/utils/mod.rs +++ b/game-server/src/logic/utils/mod.rs @@ -1,3 +1,4 @@ pub mod entity_serializer; pub mod load_role_info; pub mod world_util; +pub mod quadrant_util; diff --git a/game-server/src/logic/utils/quadrant_util.rs b/game-server/src/logic/utils/quadrant_util.rs new file mode 100644 index 0000000..4da32f8 --- /dev/null +++ b/game-server/src/logic/utils/quadrant_util.rs @@ -0,0 +1,230 @@ +use std::collections::HashMap; +use std::sync::OnceLock; + +use shorekeeper_data::LevelEntityConfigData; + +#[derive(Clone)] +struct MapBounds { + x_max: f32, + x_min: f32, + x_translate: f32, + y_max: f32, + y_min: f32, + y_translate: f32, +} + +#[derive(Default)] +struct Quadrant { + entities: HashMap, +} + +pub struct Map { + bounds: MapBounds, + width: u64, + height: u64, + quadrants: HashMap, +} + +// TODO: Make it configurable? +const EDGE_SIZE: f32 = 1000000f32; +const EDGE_CHECK: f32 = EDGE_SIZE * 3.0f32; + +pub(crate) static MAP_TABLE: OnceLock> = OnceLock::new(); + +impl MapBounds { + fn find_max_min(slice: &[&LevelEntityConfigData]) -> (Self, bool) { + let mut x_max = 0f32; + let mut x_min = 0f32; + + let mut y_max = 0f32; + let mut y_min = 0f32; + + // Find max and min coordinates + for entity in slice.iter() { + if entity.transform[0].x < x_min { x_min = entity.transform[0].x } + if entity.transform[0].x > x_max { x_max = entity.transform[0].x } + + if entity.transform[0].y < y_min { y_min = entity.transform[0].y } + if entity.transform[0].y > y_max { y_max = entity.transform[0].y } + } + + if (f32::abs(x_max - x_min) < EDGE_CHECK) || (f32::abs(y_max - y_min) < EDGE_CHECK) { + // TODO: Handle this special case, since all entities fit, no need for quadrant + + // Move everything to positive coordinates to prevent corner cases + let (x_max, x_min, x_translate) = recenter_map(x_max, x_min); + let (y_max, y_min, y_translate) = recenter_map(y_max, y_min); + + (MapBounds { x_max, x_min, x_translate, y_max, y_min, y_translate }, false) + } else { + // Round to edge + x_max = round_max_coordinate(x_max, EDGE_SIZE); + x_min = round_min_coordinate(x_min, EDGE_SIZE); + y_max = round_max_coordinate(y_max, EDGE_SIZE); + y_min = round_min_coordinate(y_min, EDGE_SIZE); + + // Adding bounds to prevent OOB when moving + x_max += EDGE_SIZE; + x_min -= EDGE_SIZE; + y_max += EDGE_SIZE; + y_min -= EDGE_SIZE; + + // Move everything to positive coordinates to prevent corner cases + let (x_max, x_min, x_translate) = recenter_map(x_max, x_min); + let (y_max, y_min, y_translate) = recenter_map(y_max, y_min); + + (MapBounds { x_max, x_min, x_translate, y_max, y_min, y_translate }, true) + } + } +} + +impl Quadrant { + fn insert_entity(&mut self, entity_id: i64, entity: &'static LevelEntityConfigData) { + self.entities.insert(entity_id, entity); + } + + fn get_entities(&self) -> Vec<&LevelEntityConfigData> { + self.entities + .iter() + .map(|(_, v)| *v) + .collect() + } +} + +impl Map { + fn insert_entity(&mut self, entity: &'static LevelEntityConfigData) { + let index = self.get_quadrant_id(entity.transform[0].x, entity.transform[0].y); + self.quadrants.entry(index).or_default().insert_entity(entity.entity_id, entity) + } + + fn get_neighbour_cells(&self, quadrant_id: u64) -> [u64; 9] { + let x = quadrant_id % self.width; + let y = (quadrant_id - x) / self.width; + return [ + (self.width * (y - 1)) + (x - 1), + (self.width * (y - 1)) + (x), + (self.width * (y - 1)) + (x + 1), + (self.width * (y)) + (x - 1), + (self.width * (y)) + (x), + (self.width * (y)) + (x + 1), + (self.width * (y + 1)) + (x - 1), + (self.width * (y + 1)) + (x), + (self.width * (y + 1)) + (x + 1), + ]; + } + + fn collect_quadrant_differences(&self, discriminant: [u64; 9], discriminator: [u64; 9]) -> Vec<&LevelEntityConfigData> { + let mut output = Vec::new(); + for quadrant in discriminant { + if !discriminator.contains(&quadrant) { + if let Some(quadrant) = &self.quadrants.get(&quadrant) { + output.extend_from_slice(&quadrant.get_entities()) + } + } + } + output + } + + pub fn get_quadrant_id(&self, x: f32, y: f32) -> u64 { + let width: u64 = unsafe { + f32::to_int_unchecked( + f32::trunc( + (self.bounds.x_max + self.bounds.x_translate - x) / EDGE_SIZE + ) + ) + }; + let height: u64 = unsafe { + f32::to_int_unchecked( + f32::trunc( + (self.bounds.y_max + self.bounds.y_translate - y) / EDGE_SIZE + ) + ) + }; + (self.width * height) + width + } + + pub fn get_initial_entities(&self, quadrant_id: u64) -> Vec<&LevelEntityConfigData> { + let quadrants = self.get_neighbour_cells(quadrant_id); + let mut output = Vec::new(); + for quadrant in quadrants { + if let Some(quadrant) = &self.quadrants.get(&quadrant) { + output.extend_from_slice(&quadrant.get_entities()) + } + } + output + } + + pub fn get_update_entities(&self, old_quadrant_id: u64, new_quadrant_id: u64) -> (Vec<&LevelEntityConfigData>, Vec<&LevelEntityConfigData>) { + let old_quadrants = self.get_neighbour_cells(old_quadrant_id); + let new_quadrants = self.get_neighbour_cells(new_quadrant_id); + + let entities_to_remove = self.collect_quadrant_differences(old_quadrants, new_quadrants); + let entities_to_add = self.collect_quadrant_differences(new_quadrants, old_quadrants); + + (entities_to_remove, entities_to_add) + } +} + +pub fn maps_iter() -> std::collections::hash_map::Iter<'static, i32, Map> { + MAP_TABLE.get().unwrap().iter() +} + +pub fn initialize_quadrant_system() { + let mut map_grouped_entities: HashMap> = HashMap::new(); + for (_, entity) in shorekeeper_data::level_entity_config_data::iter() { + map_grouped_entities.entry(entity.map_id).or_default().push(entity); + } + + let mut maps: HashMap = HashMap::new(); + for (map_id, entities) in map_grouped_entities { + let (bounds, _quadrant_enabled) = MapBounds::find_max_min(&entities[..]); + let width = unsafe { f32::to_int_unchecked((bounds.x_max - bounds.x_min) / EDGE_SIZE) }; + let height = unsafe { f32::to_int_unchecked((bounds.y_max - bounds.y_min) / EDGE_SIZE) }; + let map = maps.entry(map_id).or_insert( + Map { + bounds: bounds.clone(), + width, + height, + quadrants: HashMap::new(), + } + ); + + for entity in entities { + map.insert_entity(entity); + } + } + + let _ = MAP_TABLE.set(maps); +} + +pub fn get_map(map_id: i32) -> &'static Map { + // TODO: Error check for map id + MAP_TABLE.get().unwrap().get(&map_id).unwrap() +} + +fn recenter_map(max: f32, min: f32) -> (f32, f32, f32) { + match min < 0.0 { + true => (max + f32::abs(min), 0.0, min), + false => (max, min, 0.0) + } +} + +fn round_max_coordinate(coordinate: f32, round: f32) -> f32 { + let rounded = f32::round(coordinate); + let remainder = rounded % round; + if remainder != 0f32 { + rounded + (if rounded > 0.0 { round } else { 0.0 } - remainder) + } else { + rounded + } +} + +fn round_min_coordinate(coordinate: f32, round: f32) -> f32 { + let rounded = f32::round(coordinate); + let remainder = rounded % round; + if remainder != 0f32 { + rounded + (remainder.signum() * (if rounded > 0.0 { 0.0 } else { round } - f32::abs(remainder))) + } else { + rounded + } +} \ No newline at end of file diff --git a/game-server/src/logic/utils/world_util.rs b/game-server/src/logic/utils/world_util.rs index 5047184..85a833a 100644 --- a/game-server/src/logic/utils/world_util.rs +++ b/game-server/src/logic/utils/world_util.rs @@ -1,6 +1,11 @@ -use crate::logic::ecs::world::World; -use crate::logic::player::Player; -use crate::logic::utils::entity_serializer; +use std::cell::{BorrowMutError, RefMut}; + +use shorekeeper_data::{base_property_data, LevelEntityConfigData}; +use shorekeeper_protocol::{DFsm, EEntityType, EntityAddNotify, EntityConfigType, EntityPb, + EntityRemoveInfo, EntityRemoveNotify, EntityState, FightRoleInfo, + FightRoleInfos, LivingStatus, SceneInformation, SceneMode, + ScenePlayerInformation, SceneTimeInfo}; + use crate::logic::{ components::{ Attribute, EntityConfig, Equip, Movement, OwnerPlayer, PlayerEntityMarker, Position, @@ -8,12 +13,13 @@ use crate::logic::{ }, ecs::component::ComponentContainer, }; +use crate::logic::components::{Fsm, MonsterAi}; +use crate::logic::ecs::entity::Entity; +use crate::logic::ecs::world::{World, WorldEntity}; +use crate::logic::math::Transform; +use crate::logic::player::Player; +use crate::logic::utils::entity_serializer; use crate::query_with; -use shorekeeper_data::base_property_data; -use shorekeeper_protocol::{ - EEntityType, EntityConfigType, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation, - SceneMode, ScenePlayerInformation, SceneTimeInfo, -}; #[macro_export] macro_rules! create_player_entity_pb { @@ -32,6 +38,8 @@ macro_rules! create_player_entity_pb { .with(ComponentContainer::EntityConfig(EntityConfig { config_id: role_id, config_type: EntityConfigType::Character, + entity_type: EEntityType::Player.into(), + entity_state: EntityState::Default })) .with(ComponentContainer::OwnerPlayer(OwnerPlayer($player_id))) .with(ComponentContainer::Position(Position($position))) @@ -95,6 +103,8 @@ pub fn add_player_entities(player: &Player) { .with(ComponentContainer::EntityConfig(EntityConfig { config_id: role.role_id, config_type: EntityConfigType::Character, + entity_type: EEntityType::Player.into(), + entity_state: EntityState::Default, })) .with(ComponentContainer::OwnerPlayer(OwnerPlayer( player.basic_info.id, @@ -163,15 +173,15 @@ fn build_player_info_list(world: &World) -> Vec { Position, Equip ) - .into_iter() - .find_map(|(_, _, owner, visibility, conf, pos, equip)| { - (sp.player_id == owner.0 && visibility.0).then_some(( - conf.config_id, - pos.0.clone(), - equip.weapon_id, - )) - }) - .unwrap_or_default(); + .into_iter() + .find_map(|(_, _, owner, visibility, conf, pos, equip)| { + (sp.player_id == owner.0 && visibility.0).then_some(( + conf.config_id, + pos.0.clone(), + equip.weapon_id, + )) + }) + .unwrap_or_default(); let active_characters = query_with!( world.get_world_entity(), @@ -179,8 +189,8 @@ fn build_player_info_list(world: &World) -> Vec { OwnerPlayer, EntityConfig ) - .into_iter() - .filter(|(_, _, owner, _)| owner.0 == sp.player_id); + .into_iter() + .filter(|(_, _, owner, _)| owner.0 == sp.player_id); ScenePlayerInformation { cur_role: cur_role_id, @@ -209,3 +219,118 @@ fn build_player_info_list(world: &World) -> Vec { }) .collect() } + +pub fn build_monster_entity(world: &mut WorldEntity, config_id: i32, map_id: i32, transform: Transform) -> Entity { + // TODO: Check for more components, AI and so + world.create_entity(config_id, EEntityType::Monster.into(), map_id) + .with(ComponentContainer::EntityConfig(EntityConfig { + config_id, + config_type: EntityConfigType::Level, + entity_type: EEntityType::Monster.into(), + entity_state: EntityState::Born, + })) + .with(ComponentContainer::Position(Position(transform))) + .with(ComponentContainer::Visibility(Visibility(true))) + .with(ComponentContainer::Attribute(Attribute::from_data( + base_property_data::iter() + .find(|d| d.id == 600000100) // TODO: Implement monster stats + .unwrap(), + ))) + .with(ComponentContainer::MonsterAi(MonsterAi { + weapon_id: 0, + hatred_group_id: 0, + ai_team_init_id: 100, + combat_message_id: 0, + })) + .with(ComponentContainer::Fsm(Fsm { + fsms: vec![ + DFsm { + fsm_id: 10007, + current_state: 10013, + flag: 0, + k_ts: 0, + }, + DFsm { + fsm_id: 10007, + current_state: 10015, + flag: 0, + k_ts: 0, + }, + DFsm { + fsm_id: 10007, + current_state: 10012, + flag: 0, + k_ts: 0, + }, + ], + hash_code: 0, + common_hash_code: 0, + black_board: vec![], + fsm_custom_blackboard_datas: None, + })) + .with(ComponentContainer::Movement(Movement::default())) + .build() +} + +pub fn remove_entities(player: &Player, entities: &[&LevelEntityConfigData]) { + let mut removed_entities = Vec::with_capacity(entities.len()); + // Enclose to drop borrow mut ASAP + { + let mut world_ref = player.world.borrow_mut(); + let world = world_ref.get_mut_world_entity(); + + for entity in entities { + let entity_id = entity.entity_id as i32; // TODO: Should be i64 + if world.remove_entity(entity_id) { + removed_entities.push(world.get_entity_id(entity_id)); + } + } + } + for entity_id in removed_entities { + player.notify(EntityRemoveNotify { + remove_infos: vec![EntityRemoveInfo { entity_id, r#type: 0 }], + is_remove: true, + }); + } +} + +pub fn add_entities(player: &Player, entities: &[&LevelEntityConfigData]) { + let mut added_entities = Vec::with_capacity(entities.len()); + // Enclose to drop borrow mut ASAP + { + let mut world_ref = player.world.borrow_mut(); + let world = world_ref.get_mut_world_entity(); + + for entity in entities { + // TODO: review other types + tracing::debug!("Entity to be added of type: {}", entity.blueprint_type); + if entity.blueprint_type.contains("Monster") { + added_entities.push(build_monster_entity( + world, + entity.entity_id as i32, // TODO: Should be i64 + entity.map_id, + Transform::from(&entity.transform[..]), + )); + } + } + } + + let mut world_ref = player.world.borrow(); + let world = world_ref.get_world_entity(); + // Since kuro has issues, we can only send one + for entity in added_entities { + let mut pb = EntityPb { + id: entity.entity_id as i64, // TODO: Should be i64 + ..Default::default() + }; + + world.get_entity_components(entity.entity_id) + .into_iter() + .for_each(|comp| comp.set_pb_data(&mut pb)); + + player.notify(EntityAddNotify { + entity_pbs: vec![pb], + is_add: true, + }); + } +} \ No newline at end of file diff --git a/game-server/src/main.rs b/game-server/src/main.rs index 0e6077c..5886bf0 100644 --- a/game-server/src/main.rs +++ b/game-server/src/main.rs @@ -21,6 +21,7 @@ async fn main() -> Result<()> { ::common::splash::print_splash(); ::common::logging::init(::tracing::Level::DEBUG); shorekeeper_data::load_all_json_data("assets/logic/BinData")?; + logic::utils::quadrant_util::initialize_quadrant_system(); let database = Arc::new(shorekeeper_database::connect_to(&CONFIG.database).await?); shorekeeper_database::run_migrations(database.as_ref()).await?;