From c6b66eacb4988168c04c794cfdfb9174af066522 Mon Sep 17 00:00:00 2001 From: xavo95 Date: Thu, 24 Oct 2024 13:15:50 +0200 Subject: [PATCH] [PATCH] feat: implement formation and switch roles functionality - Add support for multi-character formations and role switching - Refactor entity management system to handle multiple entities per map - Update attribute component to use a map-based approach for properties - Implement formation-related handlers and notifications - Add world entity management per map instance - Update player save/load logic to handle formations - Add visibility controls for active character display --- game-server/src/logic/components/attribute.rs | 203 ++---------- game-server/src/logic/ecs/entity.rs | 136 +++++++- game-server/src/logic/ecs/mod.rs | 88 +++-- game-server/src/logic/ecs/world.rs | 122 +++++-- game-server/src/logic/handler/combat.rs | 71 +++- game-server/src/logic/handler/entity.rs | 73 +++-- game-server/src/logic/handler/guide.rs | 4 +- game-server/src/logic/handler/misc.rs | 8 +- game-server/src/logic/handler/mod.rs | 9 + game-server/src/logic/handler/role.rs | 141 ++++++++ game-server/src/logic/handler/scene.rs | 8 +- game-server/src/logic/handler/skill.rs | 7 +- game-server/src/logic/player/basic_info.rs | 6 + game-server/src/logic/player/mod.rs | 303 +++++++++++++----- game-server/src/logic/role/formation.rs | 8 +- game-server/src/logic/role/mod.rs | 42 ++- game-server/src/logic/systems/movement.rs | 12 +- game-server/src/logic/thread_mgr.rs | 54 ++-- .../src/logic/utils/entity_serializer.rs | 78 ++++- game-server/src/logic/utils/load_role_info.rs | 158 +++++++++ game-server/src/logic/utils/mod.rs | 1 + game-server/src/logic/utils/world_util.rs | 212 ++++++++---- shorekeeper-protocol/proto/data.proto | 97 +++--- 23 files changed, 1298 insertions(+), 543 deletions(-) create mode 100644 game-server/src/logic/handler/role.rs create mode 100644 game-server/src/logic/utils/load_role_info.rs diff --git a/game-server/src/logic/components/attribute.rs b/game-server/src/logic/components/attribute.rs index 6568297..6f54f9c 100644 --- a/game-server/src/logic/components/attribute.rs +++ b/game-server/src/logic/components/attribute.rs @@ -1,49 +1,31 @@ -use std::collections::HashMap; - use shorekeeper_data::BasePropertyData; use shorekeeper_protocol::{ entity_component_pb::ComponentPb, AttrData, AttributeComponentPb, EAttributeType, EntityComponentPb, LivingStatus, }; +use std::collections::HashMap; use crate::logic::ecs::component::Component; +use crate::logic::utils::load_role_info::attribute_from_data; pub struct Attribute { pub attr_map: HashMap, } -macro_rules! impl_from_data { - ($($name:ident),*) => { - pub fn from_data(base_property: &BasePropertyData) -> Self { - Self { - attr_map: HashMap::from([$( - ::paste::paste!((EAttributeType::[<$name:camel>], (base_property.$name, 0))), - )*]), - } - } - }; -} - impl Component for Attribute { fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) { - pb.living_status = (if self.is_alive() { LivingStatus::Alive } else { LivingStatus::Dead }) - .into(); + pb.living_status = (if self.is_alive() { + LivingStatus::Alive + } else { + LivingStatus::Dead + }) + .into(); pb.component_pbs.push(EntityComponentPb { - component_pb: Some(ComponentPb::AttributeComponent(AttributeComponentPb { - attr_data: self - .attr_map - .iter() - .map(|(ty, (base, incr))| AttrData { - attribute_type: (*ty).into(), - base_value: *base, - increment: *incr, - }) - .collect(), - hardness_mode_id: 0, - rage_mode_id: 0, - })), - }) + component_pb: Some(ComponentPb::AttributeComponent( + self.build_entity_attribute(), + )), + }); } } @@ -57,142 +39,27 @@ impl Attribute { > 0 } - impl_from_data!( - lv, - life_max, - life, - sheild, - sheild_damage_change, - sheild_damage_reduce, - atk, - crit, - crit_damage, - def, - energy_efficiency, - cd_reduse, - element_efficiency, - damage_change_normal_skill, - damage_change, - damage_reduce, - damage_change_auto, - damage_change_cast, - damage_change_ultra, - damage_change_qte, - damage_change_phys, - damage_change_element1, - damage_change_element2, - damage_change_element3, - damage_change_element4, - damage_change_element5, - damage_change_element6, - damage_resistance_phys, - damage_resistance_element1, - damage_resistance_element2, - damage_resistance_element3, - damage_resistance_element4, - damage_resistance_element5, - damage_resistance_element6, - heal_change, - healed_change, - damage_reduce_phys, - damage_reduce_element1, - damage_reduce_element2, - damage_reduce_element3, - damage_reduce_element4, - damage_reduce_element5, - damage_reduce_element6, - reaction_change1, - reaction_change2, - reaction_change3, - reaction_change4, - reaction_change5, - reaction_change6, - reaction_change7, - reaction_change8, - reaction_change9, - reaction_change10, - reaction_change11, - reaction_change12, - reaction_change13, - reaction_change14, - reaction_change15, - energy_max, - energy, - special_energy_1_max, - special_energy_1, - special_energy_2_max, - special_energy_2, - special_energy_3_max, - special_energy_3, - special_energy_4_max, - special_energy_4, - strength_max, - strength, - strength_recover, - strength_punish_time, - strength_run, - strength_swim, - strength_fast_swim, - hardness_max, - hardness, - hardness_recover, - hardness_punish_time, - hardness_change, - hardness_reduce, - rage_max, - rage, - rage_recover, - rage_punish_time, - rage_change, - rage_reduce, - tough_max, - tough, - tough_recover, - tough_change, - tough_reduce, - tough_recover_delay_time, - element_power1, - element_power2, - element_power3, - element_power4, - element_power5, - element_power6, - special_damage_change, - strength_fast_climb_cost, - element_property_type, - weak_time, - ignore_def_rate, - ignore_damage_resistance_phys, - ignore_damage_resistance_element1, - ignore_damage_resistance_element2, - ignore_damage_resistance_element3, - ignore_damage_resistance_element4, - ignore_damage_resistance_element5, - ignore_damage_resistance_element6, - skill_tough_ratio, - strength_climb_jump, - strength_gliding, - mass, - braking_friction_factor, - gravity_scale, - speed_ratio, - damage_change_phantom, - auto_attack_speed, - cast_attack_speed, - status_build_up_1_max, - status_build_up_1, - status_build_up_2_max, - status_build_up_2, - status_build_up_3_max, - status_build_up_3, - status_build_up_4_max, - status_build_up_4, - status_build_up_5_max, - status_build_up_5, - paralysis_time_max, - paralysis_time, - paralysis_time_recover, - element_energy_max, - element_energy - ); + #[inline(always)] + pub fn from_data(base_property: &BasePropertyData) -> Self { + Self { + attr_map: attribute_from_data(base_property), + } + } + + #[inline(always)] + pub fn build_entity_attribute(&self) -> AttributeComponentPb { + AttributeComponentPb { + attr_data: self + .attr_map + .iter() + .map(|(ty, (base, incr))| AttrData { + attribute_type: (*ty).into(), + base_value: *base, + increment: *incr, + }) + .collect(), + hardness_mode_id: 0, + rage_mode_id: 0, + } + } } diff --git a/game-server/src/logic/ecs/entity.rs b/game-server/src/logic/ecs/entity.rs index 3bc7314..164cc7e 100644 --- a/game-server/src/logic/ecs/entity.rs +++ b/game-server/src/logic/ecs/entity.rs @@ -1,34 +1,118 @@ -use std::{cell::RefCell, collections::HashSet}; - use super::component::ComponentContainer; +use std::cell::RefCell; +use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::{AtomicI32, Ordering}; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub struct Entity(i64); +pub struct Entity { + pub entity_id: i32, + pub entity_type: i32, + pub map_id: i32, +} pub struct EntityBuilder<'comp>(Entity, &'comp mut Vec>); -#[derive(Default)] pub struct EntityManager { - entity_id_counter: i64, - active_entity_set: HashSet, + active_entity_set: HashMap>, + next_id: AtomicI32, + recycled_ids: HashMap>, } impl EntityManager { - pub fn create(&mut self) -> Entity { - self.entity_id_counter += 1; - let entity = Entity(self.entity_id_counter); + pub fn create(&mut self, config_id: i32, entity_type: i32, map_id: i32) -> Entity { + let entity_id = self + .recycled_ids + .get_mut(&config_id) + .and_then(|ids| ids.pop_front()) + .unwrap_or_else(|| self.next_id.fetch_add(1, Ordering::Relaxed)); + + let entity = Entity { + entity_id, + entity_type, + map_id, + }; + + self.active_entity_set + .entry(config_id) + .or_default() + .push(entity); - self.active_entity_set.insert(entity); entity } - pub fn get(&self, id: i64) -> Option { - self.active_entity_set.get(&Entity(id)).copied() + pub fn get_entity_id(&self, config_id: i32) -> i32 { + self.active_entity_set + .get(&config_id) + .and_then(|entities| entities.first()) + .map(|entity| entity.entity_id) + .unwrap_or_else(|| { + tracing::error!("Entity Configuration ID {} not found.", config_id); + -1 + }) } - #[expect(dead_code)] - pub fn remove(&mut self, entity: Entity) -> bool { - self.active_entity_set.remove(&entity) + pub fn get_config_id(&self, entity_id: i32) -> i32 { + self.active_entity_set + .iter() + .find_map(|(config_id, entities)| { + entities + .iter() + .any(|e| e.entity_id == entity_id) + .then_some(*config_id) + }) + .unwrap_or_else(|| { + tracing::error!("Entity ID {} not found.", entity_id); + -1 + }) + } + + pub fn get(&self, config_id: i32) -> Entity { + self.active_entity_set + .get(&config_id) + .and_then(|entities| entities.first()) + .cloned() + .unwrap_or_else(|| { + tracing::error!("Entity Configuration ID {} not found.", config_id); + Entity::default() + }) + } + + pub fn get_all_entity_id(&self) -> Vec { + self.active_entity_set + .iter() + .flat_map(|(_, entities)| entities.iter().map(|e| e.entity_id)) + .collect() + } + + pub fn active_entity_empty(&self) -> bool { + self.active_entity_set.is_empty() + } + + pub fn get_entity_ids_by_map(&self, map_id: i32) -> Vec { + self.active_entity_set + .iter() + .flat_map(|(_, entities)| { + entities + .iter() + .filter(|e| e.map_id == map_id) + .map(|e| e.entity_id) + }) + .collect() + } + + #[inline(always)] + pub fn remove(&mut self, entity_id: i32) -> bool { + for (config_id, entities) in self.active_entity_set.iter_mut() { + if let Some(index) = entities.iter().position(|e| e.entity_id == entity_id) { + let entity = entities.remove(index); + self.recycled_ids + .entry(*config_id) + .or_default() + .push_back(entity.entity_id); + return true; + } + } + false } } @@ -52,6 +136,26 @@ impl<'comp> EntityBuilder<'comp> { impl From for i64 { fn from(value: Entity) -> Self { - value.0 + value.entity_id as i64 + } +} + +impl Default for EntityManager { + fn default() -> Self { + Self { + active_entity_set: HashMap::new(), + next_id: AtomicI32::new(1), + recycled_ids: HashMap::new(), + } + } +} + +impl Default for Entity { + fn default() -> Self { + Self { + entity_id: -1, + entity_type: -1, + map_id: 8, + } } } diff --git a/game-server/src/logic/ecs/mod.rs b/game-server/src/logic/ecs/mod.rs index 6c51998..2a9b916 100644 --- a/game-server/src/logic/ecs/mod.rs +++ b/game-server/src/logic/ecs/mod.rs @@ -2,28 +2,56 @@ pub mod component; pub mod entity; pub mod world; -// Query specified components from all entities +#[macro_export] +macro_rules! find_component { + ($comps:expr, $comp:ident) => { + $comps.iter().find_map(|comp| { + let r = comp.try_borrow_mut().ok()?; + if matches!(&*r, ComponentContainer::$comp(_)) { + Some(::std::cell::RefMut::map(r, |r| { + let ComponentContainer::$comp(comp_inner) = r else { + unreachable!() + }; + comp_inner + })) + } else { + None + } + }) + }; +} + +// Query specified components from all entities (and) #[macro_export] macro_rules! query_with { - ($world:expr, $($comp:ident),*) => { - $world.components().iter().filter(|(_, comps)| { - $(comps.iter().any(|comp| matches!(&*comp.borrow(), ComponentContainer::$comp(_))) && )* true + ($world_entitys:expr, $($comp:ident),*) => { + $world_entitys.components().iter().filter(|(_, comps)| { + $(comps.iter().any(|comp| matches!(&*comp.borrow(), ComponentContainer::$comp(_))) && ) + * true }) .map(|(e, comps)| { (*e, $( - comps.iter().find_map(|comp| { - let r = comp.try_borrow_mut().ok()?; - if matches!(&*r, ComponentContainer::$comp(_)) { - Some(::std::cell::RefMut::map(r, |r| { - let ComponentContainer::$comp(comp_inner) = r else { unreachable!() }; - comp_inner - })) - } - else { - None - } - }).unwrap(), + $crate::find_component!(comps, $comp).unwrap(), + )* + ) + }) + .collect::>() + }; +} + +// Query specified components from all entities (or) +#[macro_export] +macro_rules! query_hn_with { + ($world_entitys:expr, $($comp:ident),*) => { + $world_entitys.components().iter().filter(|(_, comps)| { + $(comps.iter().any(|comp| matches!(&*comp.borrow(), ComponentContainer::$comp(_))) || ) + * false + }) + .map(|(e, comps)| { + (*e, + $( + $crate::find_component!(comps, $comp), )* ) }) @@ -41,24 +69,24 @@ macro_rules! ident_as_none { // Query components of specified entity #[macro_export] macro_rules! query_components { - ($world:expr, $entity_id:expr, $($comp:ident),*) => { - $world.components().iter().find(|(id, _)| $entity_id == i64::from(**id)) + ($world_entitys:expr, $entity_id:expr, $($comp:ident),*) => { + $world_entitys.components().iter().find(|(id, _)| $entity_id == i64::from(**id)) .map(|(_, comps)| { ($( - comps.iter().find_map(|comp| { - let r = comp.try_borrow_mut().ok()?; - if matches!(&*r, ComponentContainer::$comp(_)) { - Some(::std::cell::RefMut::map(r, |r| { - let ComponentContainer::$comp(comp_inner) = r else { unreachable!() }; - comp_inner - })) - } - else { - None - } - }), + $crate::find_component!(comps, $comp), )*) }) .unwrap_or_else(|| ($( $crate::ident_as_none!($comp), )*)) }; } + +#[macro_export] +macro_rules! modify_component { + ($comps:expr, $comp:ident, $modifier:expr) => { + $comps.iter_mut().for_each(|comp| { + if let ComponentContainer::$comp(ref mut inner_comp) = &mut **comp { + $modifier(inner_comp); + } + }); + }; +} diff --git a/game-server/src/logic/ecs/world.rs b/game-server/src/logic/ecs/world.rs index 28e4291..0ceee0a 100644 --- a/game-server/src/logic/ecs/world.rs +++ b/game-server/src/logic/ecs/world.rs @@ -1,48 +1,30 @@ +use super::component::ComponentContainer; +use super::entity::{Entity, EntityBuilder, EntityManager}; +use crate::logic::player::InWorldPlayer; use std::cell::{RefCell, RefMut}; use std::collections::hash_map::{Keys, Values}; use std::collections::HashMap; -use crate::logic::player::InWorldPlayer; - -use super::component::ComponentContainer; -use super::entity::{Entity, EntityBuilder, EntityManager}; +pub struct WorldEntity { + components: HashMap>>, + entity_manager: EntityManager, +} pub struct World { - components: HashMap>>, - entity_manager: EntityManager, - in_world_players: HashMap, // joined players metadata + pub player_cur_map_id: i32, + pub world_entitys: HashMap, // i32 -> map_id + pub in_world_players: HashMap, // joined players metadata } impl World { pub fn new() -> Self { Self { - components: HashMap::new(), - entity_manager: EntityManager::default(), + player_cur_map_id: 8, + world_entitys: HashMap::new(), in_world_players: HashMap::new(), } } - pub fn create_entity(&mut self) -> EntityBuilder { - let entity = self.entity_manager.create(); - EntityBuilder::builder(entity, self.components.entry(entity).or_default()) - } - - pub fn is_in_world(&self, entity_id: i64) -> bool { - self.entity_manager.get(entity_id).is_some() - } - - pub fn components(&self) -> &HashMap>> { - &self.components - } - - pub fn get_entity_components(&self, entity: Entity) -> Vec> { - let Some(components) = self.components.get(&entity) else { - return Vec::with_capacity(0); - }; - - components.iter().map(|rc| rc.borrow_mut()).collect() - } - pub fn player_ids(&self) -> Keys<'_, i32, InWorldPlayer> { self.in_world_players.keys() } @@ -55,4 +37,84 @@ impl World { self.in_world_players .insert(in_world_player.player_id, in_world_player); } + + pub fn get_mut_world_entity(&mut self) -> &mut WorldEntity { + self.world_entitys + .get_mut(&self.player_cur_map_id) + .unwrap_or_else(|| panic!("Failed to get cur map data: {}", self.player_cur_map_id)) + } + + pub fn get_world_entity(&self) -> &WorldEntity { + self.world_entitys + .get(&self.player_cur_map_id) + .unwrap_or_else(|| panic!("Failed to get cur map data: {}", self.player_cur_map_id)) + } +} + +impl WorldEntity { + pub fn create_entity( + &mut self, + config_id: i32, + entity_type: i32, + map_id: i32, + ) -> EntityBuilder { + let entity = self.entity_manager.create(config_id, entity_type, map_id); + EntityBuilder::builder( + entity, + self.components + .entry(entity.entity_id) + .or_insert(Vec::new()), + ) + } + + pub fn is_in_all_world_map(&self, entity_id: i32) -> bool { + self.entity_manager.get_all_entity_id().contains(&entity_id) + } + + pub fn is_in_world_map(&self, entity_id: i32, map_id: i32) -> bool { + self.entity_manager + .get_entity_ids_by_map(map_id) + .contains(&entity_id) + } + + pub fn get_entity_id(&self, config_id: i32) -> i64 { + self.entity_manager.get_entity_id(config_id) as i64 + } + + pub fn get_config_id(&self, entity_id: i32) -> i32 { + self.entity_manager.get_config_id(entity_id) + } + + pub fn get_entity(&self, config_id: i32) -> Entity { + self.entity_manager.get(config_id) + } + + pub fn components(&self) -> &HashMap>> { + &self.components + } + + pub fn get_entity_components(&self, entity_id: i32) -> Vec> { + if let Some(components) = self.components.get(&entity_id) { + components.iter().map(|rc| rc.borrow_mut()).collect() + } else { + Vec::new() + } + } + + pub fn remove_entity(&mut self, entity_id: i32) -> bool { + self.components.remove(&entity_id).is_some() && self.entity_manager.remove(entity_id) + } + + pub fn active_entity_empty(&self) -> bool { + self.entity_manager.active_entity_empty() + } +} + +impl Default for WorldEntity { + fn default() -> Self { + Self { + components: HashMap::new(), + entity_manager: EntityManager::default(), + } + } } diff --git a/game-server/src/logic/handler/combat.rs b/game-server/src/logic/handler/combat.rs index ef729fe..163c2bd 100644 --- a/game-server/src/logic/handler/combat.rs +++ b/game-server/src/logic/handler/combat.rs @@ -1,14 +1,73 @@ -use shorekeeper_protocol::combat_message::{CombatSendPackRequest, CombatSendPackResponse}; -use shorekeeper_protocol::ErrorCode; - use crate::logic::player::Player; +use shorekeeper_protocol::combat_message::{ + combat_receive_data, combat_request_data, combat_response_data, combat_send_data, + CombatReceiveData, CombatRequestData, CombatResponseData, CombatSendPackRequest, + CombatSendPackResponse, +}; +use shorekeeper_protocol::{ErrorCode, SwitchRoleRequest, SwitchRoleResponse}; + +#[inline(always)] +fn create_combat_response( + combat_request: &CombatRequestData, + message: combat_response_data::Message, +) -> CombatReceiveData { + CombatReceiveData { + message: Some(combat_receive_data::Message::CombatResponseData( + CombatResponseData { + combat_common: combat_request.combat_common, + request_id: combat_request.request_id, + message: Some(message), + }, + )), + } +} pub fn on_combat_message_combat_send_pack_request( - _player: &Player, + player: &mut Player, request: CombatSendPackRequest, response: &mut CombatSendPackResponse, ) { - tracing::debug!("CombatSendPackRequest: for {:?}", request); - // TODO: Implement this + for data in request.data.iter() { + if let Some(combat_send_data::Message::Request(ref request_data)) = data.message { + if let Some(ref request_message) = request_data.message { + match request_message { + combat_request_data::Message::SwitchRoleRequest(ref request) => { + handle_switch_role_request(player, request_data, request, response); + } + _ => {} + } + } + } + } + response.error_code = ErrorCode::Success.into(); +} + +fn handle_switch_role_request( + player: &mut Player, + combat_request: &CombatRequestData, + request: &SwitchRoleRequest, + response: &mut CombatSendPackResponse, +) { + // Find current formation and update current role + if let Some(formation) = player.formation_list.values_mut().find(|f| f.is_current) { + formation.cur_role = request.role_id; + + let receive_pack = response + .receive_pack_notify + .get_or_insert_with(Default::default); + + receive_pack.data.push(create_combat_response( + combat_request, + combat_response_data::Message::SwitchRoleResponse(SwitchRoleResponse { + error_code: ErrorCode::Success.into(), + role_id: request.role_id, + }), + )); + } else { + tracing::error!("Role with id {} not found", request.role_id); + response.error_code = ErrorCode::ErrSwitchRoleEntityNotExist.into(); + return; + } + response.error_code = ErrorCode::Success.into(); } diff --git a/game-server/src/logic/handler/entity.rs b/game-server/src/logic/handler/entity.rs index f8b4f9e..ff18a46 100644 --- a/game-server/src/logic/handler/entity.rs +++ b/game-server/src/logic/handler/entity.rs @@ -1,19 +1,20 @@ -use shorekeeper_protocol::{EntityActiveRequest, EntityActiveResponse, EntityLoadCompleteRequest, - EntityLoadCompleteResponse, EntityOnLandedRequest, - EntityOnLandedResponse, EntityPositionRequest, EntityPositionResponse, - ErrorCode, MovePackagePush, -}; - 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, +}; pub fn on_entity_active_request( player: &Player, request: EntityActiveRequest, response: &mut EntityActiveResponse, ) { - let world = player.world.borrow(); + let world_ref = player.world.borrow(); + let world = world_ref.get_world_entity(); - if !world.is_in_world(request.entity_id) { + if !world.is_in_all_world_map(request.entity_id as i32) { tracing::debug!( "EntityActiveRequest: entity with id {} doesn't exist, player_id: {}", request.entity_id, @@ -22,14 +23,28 @@ pub fn on_entity_active_request( return; }; - if let Some(position) = query_components!(world, request.entity_id, Position).0 { - // TODO: proper entity "activation" logic + 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 = Vec::new(); // not implemented - response.error_code = ErrorCode::Success.into(); + response.component_pbs.push(EntityComponentPb { + component_pb: Some(ComponentPb::AttributeComponent( + attribute.build_entity_attribute(), + )), + }); + + response.error_code = ErrorCode::Success.into(); + } else { + tracing::error!( + "EntityActiveRequest: entity with id {} not found", + request.entity_id + ); + response.error_code = ErrorCode::ErrEntityNotFound.into(); // TODO: replace with appropriate error code + return; + }; } pub fn on_entity_on_landed_request( @@ -38,33 +53,43 @@ pub fn on_entity_on_landed_request( _: &mut EntityOnLandedResponse, ) { // TODO: More implementation? - tracing::debug!("EntityOnLandedRequest: entity with id {} landed", request.entity_id); + tracing::debug!( + "EntityOnLandedRequest: entity with id {} landed", + request.entity_id + ); } -pub fn on_entity_position_request(_: &Player, - request: EntityPositionRequest, - _: &mut EntityPositionResponse, +pub fn on_entity_position_request( + _: &Player, + request: EntityPositionRequest, + _: &mut EntityPositionResponse, ) { // TODO: Implement this tracing::debug!( "EntityPositionRequest: config with id {} for map {} position requested", - request.config_id, request.map_id + request.config_id, + request.map_id ); } -pub fn on_entity_load_complete_request(_: &Player, - request: EntityLoadCompleteRequest, - _: &mut EntityLoadCompleteResponse, +pub fn on_entity_load_complete_request( + _: &Player, + request: EntityLoadCompleteRequest, + _: &mut EntityLoadCompleteResponse, ) { // TODO: Implement this - tracing::debug!("EntityLoadCompleteRequest: for ids {:?}", request.entity_ids); + tracing::debug!( + "EntityLoadCompleteRequest: for ids {:?}", + request.entity_ids + ); } pub fn on_move_package_push(player: &mut Player, push: MovePackagePush) { - let world = player.world.borrow(); + let world_ref = player.world.borrow(); + let world = world_ref.get_world_entity(); for moving_entity in push.moving_entities { - if !world.is_in_world(moving_entity.entity_id) { + 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 diff --git a/game-server/src/logic/handler/guide.rs b/game-server/src/logic/handler/guide.rs index 0e95e01..5f86cf8 100644 --- a/game-server/src/logic/handler/guide.rs +++ b/game-server/src/logic/handler/guide.rs @@ -3,8 +3,8 @@ use shorekeeper_protocol::{GuideInfoRequest, GuideInfoResponse}; use crate::logic::player::Player; pub fn on_guide_info_request( - player: &Player, - request: GuideInfoRequest, + _player: &Player, + _request: GuideInfoRequest, response: &mut GuideInfoResponse, ) { // TODO: Implement this diff --git a/game-server/src/logic/handler/misc.rs b/game-server/src/logic/handler/misc.rs index eabfb9c..a187465 100644 --- a/game-server/src/logic/handler/misc.rs +++ b/game-server/src/logic/handler/misc.rs @@ -1,4 +1,8 @@ -use shorekeeper_protocol::{ErrorCode, Hih, InputSettingRequest, InputSettingResponse, InputSettingUpdateRequest, InputSettingUpdateResponse, LanguageSettingUpdateRequest, LanguageSettingUpdateResponse, ServerPlayStationPlayOnlyStateRequest, ServerPlayStationPlayOnlyStateResponse, VersionInfoPush}; +use shorekeeper_protocol::{ + ErrorCode, Hih, InputSettingRequest, InputSettingResponse, InputSettingUpdateRequest, + InputSettingUpdateResponse, LanguageSettingUpdateRequest, LanguageSettingUpdateResponse, + ServerPlayStationPlayOnlyStateRequest, ServerPlayStationPlayOnlyStateResponse, VersionInfoPush, +}; use crate::logic::player::Player; @@ -34,7 +38,7 @@ pub fn on_server_play_station_play_only_state_request( response.play_station_play_only_state = false; } -pub fn on_version_info_push(player: &Player, push: VersionInfoPush) { +pub fn on_version_info_push(_player: &Player, push: VersionInfoPush) { // TODO: Shall we do safety check and ensure we have compatible versions? tracing::debug!( "Client versions: launcher: {}, app: {}, resources: {}", diff --git a/game-server/src/logic/handler/mod.rs b/game-server/src/logic/handler/mod.rs index ff97710..b403145 100644 --- a/game-server/src/logic/handler/mod.rs +++ b/game-server/src/logic/handler/mod.rs @@ -3,6 +3,7 @@ mod entity; mod guide; mod mail; mod misc; +mod role; mod scene; mod skill; @@ -11,6 +12,7 @@ pub use entity::*; pub use guide::*; pub use mail::*; pub use misc::*; +pub use role::*; pub use scene::*; pub use skill::*; @@ -89,6 +91,13 @@ handle_request! { // Combat CombatSendPack, combat_message; + // Role + RoleShowListUpdate; + ClientCurrentRoleReport; + RoleFavorList; + FormationAttr; + UpdateFormation; + // Entity EntityActive; EntityOnLanded; diff --git a/game-server/src/logic/handler/role.rs b/game-server/src/logic/handler/role.rs new file mode 100644 index 0000000..e2a124f --- /dev/null +++ b/game-server/src/logic/handler/role.rs @@ -0,0 +1,141 @@ +use crate::logic::player::Player; +use crate::logic::role::{Role, RoleFormation}; +use shorekeeper_protocol::{ + ClientCurrentRoleReportRequest, ClientCurrentRoleReportResponse, ERemoveEntityType, ErrorCode, + FormationAttrRequest, FormationAttrResponse, RoleFavorListRequest, RoleFavorListResponse, + RoleShowListUpdateRequest, RoleShowListUpdateResponse, UpdateFormationRequest, + UpdateFormationResponse, +}; +use std::collections::HashSet; + +pub fn on_role_show_list_update_request( + player: &mut Player, + request: RoleShowListUpdateRequest, + response: &mut RoleShowListUpdateResponse, +) { + let role_ids: HashSet = player.role_list.keys().cloned().collect(); + let all_exist = request.role_list.iter().all(|id| role_ids.contains(id)); + + if all_exist { + player.basic_info.role_show_list = request.role_list; + response.error_code = ErrorCode::Success.into(); + } else { + response.error_code = ErrorCode::InvalidRequest.into(); // TODO: replace with appropriate error code + } +} + +pub fn on_client_current_role_report_request( + _player: &Player, + request: ClientCurrentRoleReportRequest, + response: &mut ClientCurrentRoleReportResponse, +) { + response.current_entity_id = request.current_entity_id; + response.player_id = request.player_id; +} + +pub fn on_role_favor_list_request( + _player: &Player, + _request: RoleFavorListRequest, + response: &mut RoleFavorListResponse, +) { + response.favor_list = vec![]; // TODO: add favor + response.error_code = ErrorCode::Success.into(); +} + +pub fn on_formation_attr_request( + _player: &Player, + _request: FormationAttrRequest, + response: &mut FormationAttrResponse, +) { + response.error_code = ErrorCode::Success.into(); +} + +pub fn on_update_formation_request( + player: &mut Player, + request: UpdateFormationRequest, + response: &mut UpdateFormationResponse, +) { + let mut world_ref = player.world.borrow_mut(); + let world = world_ref.get_mut_world_entity(); + + for formation in request.formations { + let formation_id = formation.formation_id; + let cur_role = formation.cur_role; + let is_current = formation.is_current; + + if is_current { + // update player current formation id + player.cur_formation_id = formation_id; + + // search old formation id and set real_formation_id, set is_current to false + let mut real_formation_id = formation_id; + if let Some(rf) = player + .formation_list + .values_mut() + .find(|rf| rf.is_current && rf.id != formation_id) + { + real_formation_id = rf.id; + rf.is_current = false; + } + + if let Some(old_formation) = player.formation_list.get(&real_formation_id) { + let removed_entities: Vec = old_formation + .role_ids + .iter() + .map(|&role_id| world.get_entity_id(role_id)) + .collect(); + removed_entities.iter().for_each(|&entity_id| { + world.remove_entity(entity_id as i32); + }); + player.notify(player.build_player_entity_remove_notify( + removed_entities, + ERemoveEntityType::RemoveTypeNormal, + )); + } + + let added_roles: Vec = formation + .role_ids + .iter() + .map(|&role_id| Role::new(role_id)) + .collect(); + + if !added_roles.is_empty() { + // add new roles + player.notify(player.build_player_entity_add_notify(added_roles, world)); + } + + // send update group formation notify + player.notify(player.build_update_group_formation_notify( + RoleFormation { + id: formation_id, + cur_role, + role_ids: formation.role_ids.clone(), + is_current, + }, + world, + )); + + response.formation = Some(formation.clone()); + } + + // update all formation and check formation_list + player + .formation_list + .entry(formation_id) + .and_modify(|r| { + r.cur_role = formation.cur_role; + r.role_ids = formation.role_ids.clone(); + r.is_current = is_current; + }) + .or_insert(RoleFormation { + id: formation_id, + cur_role: formation.cur_role, + role_ids: formation.role_ids, + is_current, + }); + } + + player.notify(player.build_update_formation_notify()); + + response.error_code = ErrorCode::Success.into(); +} diff --git a/game-server/src/logic/handler/scene.rs b/game-server/src/logic/handler/scene.rs index 1e3d68b..e82cefd 100644 --- a/game-server/src/logic/handler/scene.rs +++ b/game-server/src/logic/handler/scene.rs @@ -1,6 +1,6 @@ -use shorekeeper_protocol::{ErrorCode, SceneLoadingFinishRequest, SceneLoadingFinishResponse, - SceneTraceRequest, SceneTraceResponse, UpdateSceneDateRequest, - UpdateSceneDateResponse, +use shorekeeper_protocol::{ + ErrorCode, SceneLoadingFinishRequest, SceneLoadingFinishResponse, SceneTraceRequest, + SceneTraceResponse, UpdateSceneDateRequest, UpdateSceneDateResponse, }; use crate::logic::player::Player; @@ -15,7 +15,7 @@ pub fn on_scene_trace_request( pub fn on_scene_loading_finish_request( _player: &Player, - request: SceneLoadingFinishRequest, + _request: SceneLoadingFinishRequest, response: &mut SceneLoadingFinishResponse, ) { // TODO: Implement this if needed diff --git a/game-server/src/logic/handler/skill.rs b/game-server/src/logic/handler/skill.rs index a7c0689..0a7314d 100644 --- a/game-server/src/logic/handler/skill.rs +++ b/game-server/src/logic/handler/skill.rs @@ -13,8 +13,11 @@ pub fn on_vision_explore_skill_set_request( ) { player.explore_tools.active_explore_skill = request.skill_id; - let world = player.world.borrow(); - for (entity, owner, mut vision_skill) in query_with!(world, OwnerPlayer, VisionSkill) { + for (entity, owner, mut vision_skill) in query_with!( + player.world.borrow().get_world_entity(), + OwnerPlayer, + VisionSkill + ) { if owner.0 == player.basic_info.id { vision_skill.skill_id = request.skill_id; player.notify(VisionSkillChangeNotify { diff --git a/game-server/src/logic/player/basic_info.rs b/game-server/src/logic/player/basic_info.rs index 961182c..45cd84c 100644 --- a/game-server/src/logic/player/basic_info.rs +++ b/game-server/src/logic/player/basic_info.rs @@ -10,6 +10,8 @@ pub struct PlayerBasicInfo { pub exp: i32, pub head_photo: i32, pub head_frame: i32, + pub cur_map_id: i32, + pub role_show_list: Vec, } impl PlayerBasicInfo { @@ -37,6 +39,8 @@ impl PlayerBasicInfo { exp: data.exp, head_photo: data.head_photo, head_frame: data.head_frame, + cur_map_id: data.cur_map_id, + role_show_list: data.role_show_list, } } @@ -49,6 +53,8 @@ impl PlayerBasicInfo { exp: self.exp, head_photo: self.head_photo, head_frame: self.head_frame, + cur_map_id: self.cur_map_id, + role_show_list: self.role_show_list.clone(), } } } diff --git a/game-server/src/logic/player/mod.rs b/game-server/src/logic/player/mod.rs index 3f642b3..3f230b1 100644 --- a/game-server/src/logic/player/mod.rs +++ b/game-server/src/logic/player/mod.rs @@ -1,15 +1,23 @@ -use std::{cell::RefCell, collections::HashSet, rc::Rc, sync::Arc}; - -use basic_info::PlayerBasicInfo; use common::time_util; -use explore_tools::ExploreTools; -use location::PlayerLocation; -use player_func::PlayerFunc; use shorekeeper_protocol::{ - message::Message, ItemPkgOpenNotify, PbGetRoleListNotify, PlayerBasicData, PlayerRoleData, - PlayerSaveData, ProtocolUnit, + EEntityType, ERemoveEntityType, EntityAddNotify, EntityConfigType, EntityPb, EntityRemoveInfo, + EntityRemoveNotify, FightFormationNotifyInfo, FightRoleInfo, FightRoleInfos, FormationRoleInfo, + GroupFormation, ItemPkgOpenNotify, LivingStatus, PbGetRoleListNotify, PlayerBasicData, + PlayerFightFormations, PlayerRoleData, PlayerSaveData, ProtocolUnit, UpdateFormationNotify, + UpdateGroupFormationNotify, }; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use std::sync::Arc; +use crate::logic::{ + components::{ + Attribute, EntityConfig, Equip, Movement, OwnerPlayer, PlayerEntityMarker, Position, + Visibility, VisionSkill, + }, + ecs::component::ComponentContainer, +}; use crate::session::Session; use super::{ @@ -23,14 +31,24 @@ mod in_world_player; mod location; mod player_func; +use crate::create_player_entity_pb; +use crate::logic::ecs::world::WorldEntity; +use crate::logic::player::basic_info::PlayerBasicInfo; +use crate::logic::player::explore_tools::ExploreTools; +use crate::logic::player::location::PlayerLocation; +use crate::logic::player::player_func::PlayerFunc; pub use in_world_player::InWorldPlayer; +use shorekeeper_data::base_property_data; +use shorekeeper_data::role_info_data; +use shorekeeper_protocol::message::Message; pub struct Player { session: Option>, // Persistent pub basic_info: PlayerBasicInfo, - pub role_list: Vec, - pub formation_list: Vec, + pub role_list: HashMap, + pub formation_list: HashMap, + pub cur_formation_id: i32, pub location: PlayerLocation, pub func: PlayerFunc, pub explore_tools: ExploreTools, @@ -41,28 +59,10 @@ pub struct Player { impl Player { pub fn init(&mut self) { - if self.role_list.is_empty() { - self.on_first_enter(); + if self.role_list.is_empty() || self.formation_list.is_empty() { + self.init_role_and_formation(); } - // we need shorekeeper - // TODO: remove this part after implementing team switch - if !self.role_list.iter().any(|r| r.role_id == 1603) { - let mut camellya = Role::new(1603); - camellya.equip_weapon = 21020026; - self.role_list.push(camellya); - } - - self.formation_list.clear(); - self.formation_list.push(RoleFormation { - id: 1, - cur_role: 1603, - role_id_set: HashSet::from([1603]), - is_current: true, - }); - // End shorekeeper hardcode part - - self.ensure_current_formation(); self.ensure_basic_unlock_func(); } @@ -76,24 +76,52 @@ impl Player { self.notify(ItemPkgOpenNotify { open_pkg: (0..8).collect(), }); + + self.notify(self.build_update_formation_notify()); } - fn on_first_enter(&mut self) { - self.role_list.push(Self::create_main_character_role( - self.basic_info.name.clone(), - self.basic_info.sex, - )); + fn init_role_and_formation(&mut self) { + self.role_list.clear(); + let mut role = match self.basic_info.sex { + 0 => Role::new(Role::MAIN_CHARACTER_FEMALE_ID), + 1 => Role::new(Role::MAIN_CHARACTER_MALE_ID), + _ => unreachable!(), + }; - let role = &self.role_list[0]; + role.name = self.basic_info.name.clone(); - self.formation_list.push(RoleFormation { - id: 1, - cur_role: role.role_id, - role_id_set: HashSet::from([role.role_id]), - is_current: true, + self.role_list.insert(role.role_id, role); + + let required_role_ids: Vec = role_info_data::iter() + .filter(|role_info| role_info.role_type == 1) + .map(|role_info| role_info.id) + .collect(); + let formation = vec![1603, 1504, 1505]; + + required_role_ids.iter().for_each(|&role_id| { + if !self.role_list.keys().any(|&k| k == role_id) { + self.role_list.insert(role_id, Role::new(role_id)); + } }); - self.location = PlayerLocation::default(); + self.formation_list.insert( + 1, + RoleFormation { + id: 1, + cur_role: *formation.iter().next().unwrap(), + role_ids: formation, + is_current: true, + }, + ); + self.cur_formation_id = 1; + + self.formation_list.values_mut().for_each(|formation| { + if formation.is_current && formation.id != 1 { + formation.is_current = false; + } + }); + + self.ensure_current_formation(); } // Ensure basic functionality is unlocked @@ -104,28 +132,38 @@ impl Player { } fn ensure_current_formation(&mut self) { + // If the list off formation is empty, add a default formation if self.formation_list.is_empty() { - let role = &self.role_list[0]; + let mut role_list_clone = self.role_list.iter().clone(); - self.formation_list.push(RoleFormation { - id: 1, - cur_role: role.role_id, - role_id_set: HashSet::from([role.role_id]), - is_current: true, - }); + self.formation_list.insert( + 1, + RoleFormation { + id: 1, + cur_role: role_list_clone.next().unwrap().1.role_id, + role_ids: role_list_clone + .take(3) + .map(|(&role_id, _)| role_id) + .collect(), + is_current: true, + }, + ); } - if !self.formation_list.iter().any(|rf| rf.is_current) { - self.formation_list[0].is_current = true; + // If there is no current formation, set the first formation as the current formation + if !self.formation_list.values().any(|rf| rf.is_current) { + self.formation_list.get_mut(&1).unwrap().is_current = true; } - if let Some(rf) = self.formation_list.iter_mut().find(|rf| rf.is_current) { - if rf.role_id_set.is_empty() { - rf.role_id_set.insert(self.role_list[0].role_id); + // Ensure that the set of character IDs for the current formation is not empty and that the current character ID is in the set + if let Some(rf) = self.formation_list.values_mut().find(|rf| rf.is_current) { + if rf.role_ids.is_empty() { + rf.role_ids + .push(self.role_list.iter().next().unwrap().1.role_id); } - if !rf.role_id_set.contains(&rf.cur_role) { - rf.cur_role = *rf.role_id_set.iter().nth(0).unwrap(); + if !rf.role_ids.contains(&rf.cur_role) { + rf.cur_role = *rf.role_ids.iter().nth(0).unwrap(); } } } @@ -140,23 +178,105 @@ impl Player { } } - pub fn get_current_formation_role_list(&self) -> Vec<&Role> { - self.formation_list - .iter() - .find(|rf| rf.is_current) - .unwrap() - .role_id_set - .iter() - .flat_map(|id| self.role_list.iter().find(|r| r.role_id == *id)) - .collect() + pub fn build_player_entity_add_notify( + &self, + role_list: Vec, + world: &mut WorldEntity, + ) -> EntityAddNotify { + create_player_entity_pb!( + role_list, + self.basic_info.cur_map_id, + world, + self.basic_info.id, + self.location.position.clone(), + self.explore_tools + ) } - pub fn get_cur_role_id(&self) -> i32 { - self.formation_list - .iter() - .find(|rf| rf.is_current) - .unwrap() - .cur_role + pub fn build_player_entity_remove_notify( + &self, + entities: Vec, + remove_type: ERemoveEntityType, + ) -> EntityRemoveNotify { + EntityRemoveNotify { + remove_infos: entities + .iter() + .map(|&entity_id| EntityRemoveInfo { + entity_id, + r#type: remove_type.into(), + }) + .collect(), + is_remove: true, + } + } + + pub fn build_update_group_formation_notify( + &self, + cur_formation: RoleFormation, + world: &mut WorldEntity, + ) -> UpdateGroupFormationNotify { + let group_type = 1; + UpdateGroupFormationNotify { + group_formation: vec![GroupFormation { + player_id: self.basic_info.id, + fight_role_infos: vec![FightRoleInfos { + group_type, + fight_role_infos: cur_formation + .role_ids + .iter() + .map(|&role_id| FightRoleInfo { + role_id, + entity_id: world.get_entity_id(role_id), + }) + .collect(), + cur_role: cur_formation.cur_role, + is_retain: false, + living_status: LivingStatus::Alive.into(), + is_fixed_location: false, + }], + current_group_type: group_type, + }], + } + } + + pub fn build_update_formation_notify(&self) -> UpdateFormationNotify { + let role_map: HashMap = self + .role_list + .values() + .map(|role| (role.role_id, (role.role_id, role.level))) + .collect(); + + UpdateFormationNotify { + players_formations: vec![PlayerFightFormations { + player_id: self.basic_info.id, + formations: self + .formation_list + .iter() + .map(|(&formation_id, formation)| FightFormationNotifyInfo { + formation_id, + cur_role: formation.cur_role, + role_infos: formation + .role_ids + .iter() + .map(|role_id| { + if !role_map.contains_key(role_id) { + tracing::warn!("Role {} not found in use role list", role_id); + return Default::default(); + } + let &(role_id, level) = role_map.get(&role_id).unwrap(); + FormationRoleInfo { + role_id, + max_hp: 0, + cur_hp: 0, + level, + } + }) + .collect(), + is_current: formation.is_current, + }) + .collect(), + }], + } } pub fn load_from_save(save_data: PlayerSaveData) -> Self { @@ -164,17 +284,20 @@ impl Player { Self { session: None, - basic_info: PlayerBasicInfo::load_from_save(save_data.basic_data.unwrap_or_default()), + basic_info: PlayerBasicInfo::load_from_save( + save_data.basic_data.clone().unwrap_or_default(), + ), role_list: role_data .role_list .into_iter() .map(Role::load_from_save) - .collect(), + .collect::>(), formation_list: role_data .role_formation_list .into_iter() - .map(RoleFormation::load_from_save) + .map(|(k, v)| (k, RoleFormation::load_from_save(v))) .collect(), + cur_formation_id: role_data.cur_formation_id, location: save_data .location_data .map(PlayerLocation::load_from_save) @@ -196,12 +319,17 @@ impl Player { PlayerSaveData { basic_data: Some(self.basic_info.build_save_data()), role_data: Some(PlayerRoleData { - role_list: self.role_list.iter().map(|r| r.build_save_data()).collect(), + role_list: self + .role_list + .iter() + .map(|(_, role)| role.build_save_data()) + .collect(), role_formation_list: self .formation_list .iter() - .map(|rf| rf.build_save_data()) + .map(|(&k, v)| (k, v.build_save_data())) .collect(), + cur_formation_id: self.cur_formation_id, }), location_data: Some(self.location.build_save_data()), func_data: Some(self.func.build_save_data()), @@ -215,7 +343,11 @@ impl Player { pub fn build_role_list_notify(&self) -> PbGetRoleListNotify { PbGetRoleListNotify { - role_list: self.role_list.iter().map(|r| r.to_protobuf()).collect(), + role_list: self + .role_list + .iter() + .map(|(_, role)| role.to_protobuf()) + .collect(), } } @@ -240,18 +372,13 @@ impl Player { } } - fn create_main_character_role(name: String, sex: i32) -> Role { - let mut role = match sex { - 0 => Role::new(Role::MAIN_CHARACTER_FEMALE_ID), - 1 => Role::new(Role::MAIN_CHARACTER_MALE_ID), - _ => unreachable!(), + pub fn create_default_save_data(id: i32, name: String, sex: i32) -> PlayerSaveData { + let role_id = match sex { + 0 => Role::MAIN_CHARACTER_FEMALE_ID, // 1502 + 1 => Role::MAIN_CHARACTER_MALE_ID, // 1501 + _ => Role::MAIN_CHARACTER_MALE_ID, // Default to male }; - role.name = name; - role - } - - pub fn create_default_save_data(id: i32, name: String, sex: i32) -> PlayerSaveData { PlayerSaveData { basic_data: Some(PlayerBasicData { id, @@ -260,6 +387,8 @@ impl Player { level: 1, head_photo: 1603, head_frame: 80060009, + cur_map_id: 8, + role_show_list: vec![role_id], ..Default::default() }), ..Default::default() diff --git a/game-server/src/logic/role/formation.rs b/game-server/src/logic/role/formation.rs index bbd83f7..fa37c35 100644 --- a/game-server/src/logic/role/formation.rs +++ b/game-server/src/logic/role/formation.rs @@ -1,11 +1,9 @@ -use std::collections::HashSet; - use shorekeeper_protocol::RoleFormationData; pub struct RoleFormation { pub id: i32, pub cur_role: i32, - pub role_id_set: HashSet, + pub role_ids: Vec, pub is_current: bool, } @@ -14,7 +12,7 @@ impl RoleFormation { Self { id: data.formation_id, cur_role: data.cur_role, - role_id_set: data.role_id_list.into_iter().collect(), + role_ids: data.role_id_list, is_current: data.is_current, } } @@ -23,7 +21,7 @@ impl RoleFormation { RoleFormationData { formation_id: self.id, cur_role: self.cur_role, - role_id_list: self.role_id_set.iter().cloned().collect(), + role_id_list: self.role_ids.iter().map(|&role_id| role_id).collect(), is_current: self.is_current, } } diff --git a/game-server/src/logic/role/mod.rs b/game-server/src/logic/role/mod.rs index 6cf2069..246ff03 100644 --- a/game-server/src/logic/role/mod.rs +++ b/game-server/src/logic/role/mod.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; +use crate::logic::utils::load_role_info::load_key_value; use common::time_util; pub use formation::RoleFormation; -use shorekeeper_data::role_info_data; +use shorekeeper_data::{base_property_data, role_info_data}; use shorekeeper_protocol::{ArrayIntInt, RoleData, RoleInfo}; mod formation; @@ -41,6 +42,12 @@ impl Role { } pub fn to_protobuf(&self) -> RoleInfo { + let base_prop: HashMap = load_key_value( + base_property_data::iter() + .find(|d| d.id == self.role_id) + .unwrap(), + ); + RoleInfo { role_id: self.role_id, name: self.name.clone(), @@ -55,23 +62,30 @@ impl Role { .collect(), star: self.star, favor: self.favor, + base_prop: base_prop + .iter() + .map(|(&k, &v)| ArrayIntInt { key: k, value: v }) + .collect(), ..Default::default() } } - pub fn load_from_save(data: RoleData) -> Self { - Self { - role_id: data.role_id, - name: data.name, - level: data.level, - exp: data.exp, - breakthrough: data.breakthrough, - skill_map: data.skill_map, - star: data.star, - favor: data.favor, - create_time: data.create_time, - equip_weapon: data.equip_weapon, - } + pub fn load_from_save(data: RoleData) -> (i32, Self) { + ( + data.role_id, + Self { + role_id: data.role_id, + name: data.name, + level: data.level, + exp: data.exp, + breakthrough: data.breakthrough, + skill_map: data.skill_map, + star: data.star, + favor: data.favor, + create_time: data.create_time, + equip_weapon: data.equip_weapon, + }, + ) } pub fn build_save_data(&self) -> RoleData { diff --git a/game-server/src/logic/systems/movement.rs b/game-server/src/logic/systems/movement.rs index 7e4dd73..0986d76 100644 --- a/game-server/src/logic/systems/movement.rs +++ b/game-server/src/logic/systems/movement.rs @@ -15,8 +15,9 @@ pub(super) struct MovementSystem; impl System for MovementSystem { fn tick(&self, world: &mut World, players: &mut [RefMut]) { let mut notify = MovePackageNotify::default(); + let world_entity = world.get_world_entity(); - for (entity, mut movement, mut position) in query_with!(world, Movement, Position) { + for (entity, mut movement, mut position) in query_with!(world_entity, Movement, Position) { if movement.pending_movement_vec.is_empty() { continue; } @@ -46,9 +47,12 @@ impl System for MovementSystem { notify.moving_entities.push(moving_entity_data); - if let (Some(_), Some(owner)) = - query_components!(world, i64::from(entity), PlayerEntityMarker, OwnerPlayer) - { + if let (Some(_), Some(owner)) = query_components!( + world_entity, + i64::from(entity), + PlayerEntityMarker, + OwnerPlayer + ) { if let Some(player) = players.iter_mut().find(|pl| pl.basic_info.id == owner.0) { player.location.position = position.0.clone(); } diff --git a/game-server/src/logic/thread_mgr.rs b/game-server/src/logic/thread_mgr.rs index a6a3a69..4151c63 100644 --- a/game-server/src/logic/thread_mgr.rs +++ b/game-server/src/logic/thread_mgr.rs @@ -1,3 +1,10 @@ +use common::time_util; +use shorekeeper_protocol::PlayerSaveData; +use shorekeeper_protocol::{ + message::Message, AfterJoinSceneNotify, EnterGameResponse, JoinSceneNotify, JsPatchNotify, + TransitionOptionPb, +}; +use std::collections::hash_map::Entry::Vacant; use std::{ cell::RefCell, collections::{HashMap, VecDeque}, @@ -10,20 +17,13 @@ use std::{ time::Duration, }; -use common::time_util; -use shorekeeper_protocol::PlayerSaveData; -use shorekeeper_protocol::{ - message::Message, AfterJoinSceneNotify, EnterGameResponse, JoinSceneNotify, JsPatchNotify, - TransitionOptionPb, -}; - +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 super::{ecs::world::World, player::Player, utils::world_util}; - const WATER_MASK: &str = include_str!("../../watermask-rr.js"); const UID_FIX: &str = include_str!("../../uidfix.js"); const CENSORSHIP_FIX: &str = include_str!("../../censorshipfix.js"); @@ -141,13 +141,29 @@ fn handle_logic_input(state: &mut LogicState, input: LogicInput) { session, player_save_data, } => { - let player = state - .players - .entry(player_id) - .or_insert(RefCell::new(Player::load_from_save(player_save_data))); + let (player, is_player) = if let Vacant(e) = state.players.entry(player_id) { + ( + e.insert(RefCell::new(Player::load_from_save(player_save_data))), + true, + ) + } else { + if let Some(player) = state.players.get_mut(&player_id) { + (player, false) + } else { + tracing::warn!("logic_thread: get player requested, but player {player_id} with data doesn't exist"); + return; + } + }; let mut player = player.borrow_mut(); - state.worlds.insert(player_id, player.world.clone()); + if is_player { + player + .world + .borrow_mut() + .world_entitys + .insert(player.basic_info.cur_map_id, WorldEntity::default()); + state.worlds.insert(player_id, player.world.clone()); + } player.init(); player.set_session(session); @@ -159,16 +175,12 @@ fn handle_logic_input(state: &mut LogicState, input: LogicInput) { .borrow_mut() .set_in_world_player_data(player.build_in_world_player()); - world_util::add_player_entities(&mut player.world.borrow_mut(), &player); - let scene_info = world_util::build_scene_information( - &player.world.borrow(), - player.location.instance_id, - player.basic_info.id, - ); + world_util::add_player_entities(&player); + let scene_info = world_util::build_scene_information(&player); player.notify(JoinSceneNotify { - max_entity_id: i64::MAX, scene_info: Some(scene_info), + max_entity_id: i64::MAX, transition_option: Some(TransitionOptionPb::default()), }); diff --git a/game-server/src/logic/utils/entity_serializer.rs b/game-server/src/logic/utils/entity_serializer.rs index c3d32b8..9c9c0f1 100644 --- a/game-server/src/logic/utils/entity_serializer.rs +++ b/game-server/src/logic/utils/entity_serializer.rs @@ -1,25 +1,75 @@ use crate::logic::ecs::component::ComponentContainer; -use shorekeeper_protocol::{EntityPb, PlayerSceneAoiData}; +use shorekeeper_protocol::{EEntityType, EntityPb, PlayerSceneAoiData}; +use std::collections::HashSet; -use crate::{logic::ecs::world::World, query_with}; +use crate::logic::components::Visibility; +use crate::logic::player::Player; +use crate::{modify_component, query_hn_with}; -pub fn build_scene_add_on_init_data(world: &World) -> PlayerSceneAoiData { - let entities = query_with!(world, PlayerEntityMarker) +pub fn build_scene_add_on_init_data(player: &Player) -> PlayerSceneAoiData { + let mut world_ref = player.world.borrow_mut(); + let world = world_ref.get_mut_world_entity(); + + let entities = query_hn_with!(world, PlayerEntityMarker) .into_iter() - .map(|(e, _)| e) - .collect::>(); + .map(|(entity_id, _)| { + let res_map: (EEntityType, i32); + match EEntityType::try_from( + world.get_entity(world.get_config_id(entity_id)).entity_type, + ) { + Ok(EEntityType::Player) => { + res_map = (EEntityType::Player, entity_id); + } + _ => { + res_map = (EEntityType::default(), -1); + } + } + res_map + }) + .collect::>(); let mut aoi_data = PlayerSceneAoiData::default(); - for entity in entities { - let mut pb = EntityPb { id: entity.into(), ..Default::default() }; - world - .get_entity_components(entity) - .into_iter() - .for_each(|comp| comp.set_pb_data(&mut pb)); + entities + .iter() + .filter(|&&(_, entity_id)| entity_id != -1) + .for_each(|&(entity_type, entity_id)| { + match entity_type { + EEntityType::Player => { + let config_id = world.get_config_id(entity_id); + modify_component!( + world.get_entity_components(entity_id), + Visibility, + |vis: &mut Visibility| { + let cur_role_id = player + .formation_list + .get(&player.cur_formation_id) + .unwrap() + .cur_role; + vis.0 = if config_id == cur_role_id { + true + } else { + false + }; + } + ); - aoi_data.entities.push(pb); - } + if world.get_entity(config_id).entity_type == EEntityType::Player as i32 { + let mut pb = EntityPb { + id: entity_id as i64, + ..Default::default() + }; + world + .get_entity_components(entity_id) + .into_iter() + .for_each(|comp| comp.set_pb_data(&mut pb)); + + aoi_data.entities.push(pb); + } + } + _ => {} + }; + }); aoi_data } diff --git a/game-server/src/logic/utils/load_role_info.rs b/game-server/src/logic/utils/load_role_info.rs new file mode 100644 index 0000000..d638dff --- /dev/null +++ b/game-server/src/logic/utils/load_role_info.rs @@ -0,0 +1,158 @@ +use shorekeeper_data::BasePropertyData; +use shorekeeper_protocol::EAttributeType; +use std::collections::HashMap; + +#[macro_export] +macro_rules! impl_base_prop { + ($($name:ident),*) => { + pub fn load_key_value(base_property: &BasePropertyData) -> HashMap { + HashMap::from([$( + ::paste::paste!((EAttributeType::[<$name:camel>] as i32, base_property.$name as i32)), + )*]) + } + pub fn attribute_from_data(base_property: &BasePropertyData) -> HashMap { + HashMap::from([$( + ::paste::paste!((EAttributeType::[<$name:camel>], (base_property.$name, 0))), + )*]) + } + }; +} + +impl_base_prop!( + lv, + life_max, + life, + sheild, + sheild_damage_change, + sheild_damage_reduce, + atk, + crit, + crit_damage, + def, + energy_efficiency, + cd_reduse, + element_efficiency, + damage_change_normal_skill, + damage_change, + damage_reduce, + damage_change_auto, + damage_change_cast, + damage_change_ultra, + damage_change_qte, + damage_change_phys, + damage_change_element1, + damage_change_element2, + damage_change_element3, + damage_change_element4, + damage_change_element5, + damage_change_element6, + damage_resistance_phys, + damage_resistance_element1, + damage_resistance_element2, + damage_resistance_element3, + damage_resistance_element4, + damage_resistance_element5, + damage_resistance_element6, + heal_change, + healed_change, + damage_reduce_phys, + damage_reduce_element1, + damage_reduce_element2, + damage_reduce_element3, + damage_reduce_element4, + damage_reduce_element5, + damage_reduce_element6, + reaction_change1, + reaction_change2, + reaction_change3, + reaction_change4, + reaction_change5, + reaction_change6, + reaction_change7, + reaction_change8, + reaction_change9, + reaction_change10, + reaction_change11, + reaction_change12, + reaction_change13, + reaction_change14, + reaction_change15, + energy_max, + energy, + special_energy_1_max, + special_energy_1, + special_energy_2_max, + special_energy_2, + special_energy_3_max, + special_energy_3, + special_energy_4_max, + special_energy_4, + strength_max, + strength, + strength_recover, + strength_punish_time, + strength_run, + strength_swim, + strength_fast_swim, + hardness_max, + hardness, + hardness_recover, + hardness_punish_time, + hardness_change, + hardness_reduce, + rage_max, + rage, + rage_recover, + rage_punish_time, + rage_change, + rage_reduce, + tough_max, + tough, + tough_recover, + tough_change, + tough_reduce, + tough_recover_delay_time, + element_power1, + element_power2, + element_power3, + element_power4, + element_power5, + element_power6, + special_damage_change, + strength_fast_climb_cost, + element_property_type, + weak_time, + ignore_def_rate, + ignore_damage_resistance_phys, + ignore_damage_resistance_element1, + ignore_damage_resistance_element2, + ignore_damage_resistance_element3, + ignore_damage_resistance_element4, + ignore_damage_resistance_element5, + ignore_damage_resistance_element6, + skill_tough_ratio, + strength_climb_jump, + strength_gliding, + mass, + braking_friction_factor, + gravity_scale, + speed_ratio, + damage_change_phantom, + auto_attack_speed, + cast_attack_speed, + status_build_up_1_max, + status_build_up_1, + status_build_up_2_max, + status_build_up_2, + status_build_up_3_max, + status_build_up_3, + status_build_up_4_max, + status_build_up_4, + status_build_up_5_max, + status_build_up_5, + paralysis_time_max, + paralysis_time, + paralysis_time_recover, + element_energy_max, + element_energy +); diff --git a/game-server/src/logic/utils/mod.rs b/game-server/src/logic/utils/mod.rs index 76ca4a3..aab01f6 100644 --- a/game-server/src/logic/utils/mod.rs +++ b/game-server/src/logic/utils/mod.rs @@ -1,2 +1,3 @@ pub mod entity_serializer; +pub mod load_role_info; pub mod world_util; diff --git a/game-server/src/logic/utils/world_util.rs b/game-server/src/logic/utils/world_util.rs index 0f1cc0e..5047184 100644 --- a/game-server/src/logic/utils/world_util.rs +++ b/game-server/src/logic/utils/world_util.rs @@ -1,82 +1,151 @@ +use crate::logic::ecs::world::World; +use crate::logic::player::Player; +use crate::logic::utils::entity_serializer; +use crate::logic::{ + components::{ + Attribute, EntityConfig, Equip, Movement, OwnerPlayer, PlayerEntityMarker, Position, + Visibility, VisionSkill, + }, + ecs::component::ComponentContainer, +}; +use crate::query_with; use shorekeeper_data::base_property_data; use shorekeeper_protocol::{ - EntityConfigType, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation, SceneMode, - ScenePlayerInformation, SceneTimeInfo, + EEntityType, EntityConfigType, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation, + SceneMode, ScenePlayerInformation, SceneTimeInfo, }; -use super::entity_serializer; -use crate::{ - logic::{ - components::{ - Attribute, EntityConfig, Equip, Movement, OwnerPlayer, PlayerEntityMarker, Position, - Visibility, VisionSkill, - }, - ecs::{component::ComponentContainer, world::World}, - player::Player, - }, - query_with, -}; +#[macro_export] +macro_rules! create_player_entity_pb { + ($role_list:expr, $cur_map_id:expr, $world:expr, $player_id:expr, $position:expr, $explore_tools:expr) => {{ + let mut pbs = Vec::new(); -pub fn add_player_entities(world: &mut World, player: &Player) { - let cur_role_id = player.get_cur_role_id(); + for role in $role_list { + let role_id: i32 = role.role_id; + let base_property = base_property_data::iter() + .find(|d| d.id == role_id) + .expect("macro create_role_entity_pb: Base property data not found"); - for role in player.get_current_formation_role_list() { - let id = world - .create_entity() - .with(ComponentContainer::PlayerEntityMarker(PlayerEntityMarker)) - .with(ComponentContainer::EntityConfig(EntityConfig { - config_id: role.role_id, - config_type: EntityConfigType::Character, - })) - .with(ComponentContainer::OwnerPlayer(OwnerPlayer( - player.basic_info.id, - ))) - .with(ComponentContainer::Position(Position( - player.location.position.clone(), - ))) - .with(ComponentContainer::Visibility(Visibility( - role.role_id == cur_role_id, - ))) - .with(ComponentContainer::Attribute(Attribute::from_data( - base_property_data::iter() - .find(|d| d.id == role.role_id) - .unwrap(), - ))) - .with(ComponentContainer::Movement(Movement::default())) - .with(ComponentContainer::Equip(Equip { - weapon_id: role.equip_weapon, - weapon_breach_level: 0, // TODO: store this too - })) - .with(ComponentContainer::VisionSkill(VisionSkill { - skill_id: player.explore_tools.active_explore_skill, - })) - .build(); + let entity = $world + .create_entity(role_id, EEntityType::Player.into(), $cur_map_id) + .with(ComponentContainer::PlayerEntityMarker(PlayerEntityMarker)) + .with(ComponentContainer::EntityConfig(EntityConfig { + config_id: role_id, + config_type: EntityConfigType::Character, + })) + .with(ComponentContainer::OwnerPlayer(OwnerPlayer($player_id))) + .with(ComponentContainer::Position(Position($position))) + .with(ComponentContainer::Visibility(Visibility( + role_id == role_id, + ))) + .with(ComponentContainer::Attribute(Attribute::from_data( + base_property, + ))) + .with(ComponentContainer::Movement(Movement::default())) + .with(ComponentContainer::Equip(Equip { + weapon_id: role.equip_weapon, + weapon_breach_level: 90, // TODO: store this too + })) + .with(ComponentContainer::VisionSkill(VisionSkill { + skill_id: $explore_tools.active_explore_skill, + })) + .build(); - tracing::debug!( - "created player entity, id: {}, role_id: {}", - i64::from(id), - role.role_id - ); + let mut pb = EntityPb { + id: entity.entity_id as i64, + ..Default::default() + }; + + $world + .get_entity_components(entity.entity_id) + .into_iter() + .for_each(|comp| comp.set_pb_data(&mut pb)); + pbs.push(pb); + } + + EntityAddNotify { + entity_pbs: pbs, + is_add: true, + } + }}; +} + +pub fn add_player_entities(player: &Player) { + let mut world_ref = player.world.borrow_mut(); + let world = world_ref.get_mut_world_entity(); + + let current_formation = player.formation_list.get(&player.cur_formation_id).unwrap(); + + let role_vec = current_formation + .role_ids + .iter() + .map(|role_id| player.role_list.get(&role_id).unwrap()) + .collect::>(); + let cur_role_id = current_formation.cur_role; + + if world.active_entity_empty() { + for role in role_vec { + let entity = world + .create_entity( + role.role_id, + EEntityType::Player.into(), + player.basic_info.cur_map_id, + ) + .with(ComponentContainer::PlayerEntityMarker(PlayerEntityMarker)) + .with(ComponentContainer::EntityConfig(EntityConfig { + config_id: role.role_id, + config_type: EntityConfigType::Character, + })) + .with(ComponentContainer::OwnerPlayer(OwnerPlayer( + player.basic_info.id, + ))) + .with(ComponentContainer::Position(Position( + player.location.position.clone(), + ))) + .with(ComponentContainer::Visibility(Visibility( + role.role_id == cur_role_id, + ))) + .with(ComponentContainer::Attribute(Attribute::from_data( + base_property_data::iter() + .find(|d| d.id == role.role_id) + .unwrap(), + ))) + .with(ComponentContainer::Movement(Movement::default())) + .with(ComponentContainer::Equip(Equip { + weapon_id: role.equip_weapon, + weapon_breach_level: 0, // TODO: store this too + })) + .with(ComponentContainer::VisionSkill(VisionSkill { + skill_id: player.explore_tools.active_explore_skill, + })) + .build(); + + tracing::debug!( + "created player entity, id: {}, role_id: {}", + entity.entity_id, + role.role_id + ); + } } } -pub fn build_scene_information(world: &World, instance_id: i32, owner_id: i32) -> SceneInformation { +pub fn build_scene_information(player: &Player) -> SceneInformation { SceneInformation { scene_id: String::new(), - instance_id, - owner_id, + instance_id: player.location.instance_id, + owner_id: player.basic_info.id, dynamic_entity_list: Vec::new(), blackboard_params: Vec::new(), end_time: 0, - aoi_data: Some(entity_serializer::build_scene_add_on_init_data(world)), - player_infos: build_player_info_list(world), + aoi_data: Some(entity_serializer::build_scene_add_on_init_data(player)), + player_infos: build_player_info_list(&player.world.borrow_mut()), mode: SceneMode::Single.into(), time_info: Some(SceneTimeInfo { owner_time_clock_time_span: 0, hour: 8, minute: 0, }), - cur_context_id: owner_id as i64, + cur_context_id: player.basic_info.id as i64, ..Default::default() } } @@ -85,24 +154,33 @@ fn build_player_info_list(world: &World) -> Vec { world .players() .map(|sp| { - let (cur_role_id, transform) = query_with!( - world, + let (cur_role_id, transform, _equip) = query_with!( + world.get_world_entity(), PlayerEntityMarker, OwnerPlayer, Visibility, EntityConfig, - Position + Position, + Equip ) .into_iter() - .find_map(|(_, _, owner, visibility, conf, pos)| { - (sp.player_id == owner.0 && visibility.0).then_some((conf.config_id, pos.0.clone())) + .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, PlayerEntityMarker, OwnerPlayer, EntityConfig) - .into_iter() - .filter(|(_, _, owner, _)| owner.0 == sp.player_id); + let active_characters = query_with!( + world.get_world_entity(), + PlayerEntityMarker, + OwnerPlayer, + EntityConfig + ) + .into_iter() + .filter(|(_, _, owner, _)| owner.0 == sp.player_id); ScenePlayerInformation { cur_role: cur_role_id, diff --git a/shorekeeper-protocol/proto/data.proto b/shorekeeper-protocol/proto/data.proto index f11cd85..c635e20 100644 --- a/shorekeeper-protocol/proto/data.proto +++ b/shorekeeper-protocol/proto/data.proto @@ -2,81 +2,84 @@ syntax = "proto3"; package data; message VectorData { - float x = 1; - float y = 2; - float z = 3; + float x = 1; + float y = 2; + float z = 3; } message TransformData { - VectorData position = 1; - VectorData rotation = 2; + VectorData position = 1; + VectorData rotation = 2; } message PlayerBasicData { - int32 id = 1; - string name = 2; - int32 sex = 3; - int32 level = 4; - int32 exp = 5; - int32 head_photo = 6; - int32 head_frame = 7; + int32 id = 1; + string name = 2; + int32 sex = 3; + int32 level = 4; + int32 exp = 5; + int32 head_photo = 6; + int32 head_frame = 7; + int32 cur_map_id = 8; + repeated int32 role_show_list = 9; } message RoleSkillNodeData { - int32 node_id = 1; - bool is_active = 2; - int32 skill_id = 3; + int32 node_id = 1; + bool is_active = 2; + int32 skill_id = 3; } message RoleData { - int32 role_id = 1; - string name = 2; - int32 level = 3; - int32 exp = 4; - int32 breakthrough = 5; - map skill_map = 6; - map phantom_map = 7; - int32 star = 8; - int32 favor = 9; - uint32 create_time = 10; - int32 cur_model = 11; - repeated int32 models = 12; - repeated RoleSkillNodeData skill_node_state = 13; - int32 resonant_chain_group_index = 14; - int32 equip_weapon = 15; + int32 role_id = 1; + string name = 2; + int32 level = 3; + int32 exp = 4; + int32 breakthrough = 5; + map skill_map = 6; + map phantom_map = 7; + int32 star = 8; + int32 favor = 9; + uint32 create_time = 10; + int32 cur_model = 11; + repeated int32 models = 12; + repeated RoleSkillNodeData skill_node_state = 13; + int32 resonant_chain_group_index = 14; + int32 equip_weapon = 15; } message RoleFormationData { - int32 formation_id = 1; - int32 cur_role = 2; - repeated int32 role_id_list = 3; - bool is_current = 4; + int32 formation_id = 1; + int32 cur_role = 2; + repeated int32 role_id_list = 3; + bool is_current = 4; } message PlayerRoleData { - repeated RoleData role_list = 1; - repeated RoleFormationData role_formation_list = 2; + repeated RoleData role_list = 1; + map role_formation_list = 2; + int32 cur_formation_id = 3; } message PlayerLocationData { - int32 instance_id = 1; - TransformData position = 2; + int32 instance_id = 1; + TransformData position = 2; } message PlayerFuncData { - map func_map = 1; + map func_map = 1; } message PlayerExploreToolsData { - repeated int32 unlocked_skill_list = 1; - int32 active_skill_id = 2; - repeated int32 roulette = 3; + repeated int32 unlocked_skill_list = 1; + int32 active_skill_id = 2; + repeated int32 roulette = 3; } message PlayerSaveData { - PlayerBasicData basic_data = 1; - PlayerRoleData role_data = 2; - PlayerLocationData location_data = 3; - PlayerFuncData func_data = 4; - PlayerExploreToolsData explore_tools_data = 5; + PlayerBasicData basic_data = 1; + PlayerRoleData role_data = 2; + PlayerLocationData location_data = 3; + PlayerFuncData func_data = 4; + PlayerExploreToolsData explore_tools_data = 5; }