[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
This commit is contained in:
xavo95 2024-10-24 13:15:50 +02:00
parent 85fbc933ba
commit c6b66eacb4
Signed by: xavo95
GPG key ID: CBF8ADED6DEBB783
23 changed files with 1298 additions and 543 deletions

View file

@ -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<EAttributeType, (i32, i32)>,
}
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 })
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,
}
}
}

View file

@ -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<RefCell<ComponentContainer>>);
#[derive(Default)]
pub struct EntityManager {
entity_id_counter: i64,
active_entity_set: HashSet<Entity>,
active_entity_set: HashMap<i32, Vec<Entity>>,
next_id: AtomicI32,
recycled_ids: HashMap<i32, VecDeque<i32>>,
}
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<Entity> {
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<i32> {
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<i32> {
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<Entity> 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,
}
}
}

View file

@ -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::<Vec<_>>()
};
}
// 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);
}
});
};
}

View file

@ -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<i32, Vec<RefCell<ComponentContainer>>>,
entity_manager: EntityManager,
}
pub struct World {
components: HashMap<Entity, Vec<RefCell<ComponentContainer>>>,
entity_manager: EntityManager,
in_world_players: HashMap<i32, InWorldPlayer>, // joined players metadata
pub player_cur_map_id: i32,
pub world_entitys: HashMap<i32, WorldEntity>, // i32 -> map_id
pub in_world_players: HashMap<i32, InWorldPlayer>, // 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<Entity, Vec<RefCell<ComponentContainer>>> {
&self.components
}
pub fn get_entity_components(&self, entity: Entity) -> Vec<RefMut<ComponentContainer>> {
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<i32, Vec<RefCell<ComponentContainer>>> {
&self.components
}
pub fn get_entity_components(&self, entity_id: i32) -> Vec<RefMut<ComponentContainer>> {
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(),
}
}
}

View file

@ -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();
}

View file

@ -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.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,
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,
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

View file

@ -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

View file

@ -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: {}",

View file

@ -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;

View file

@ -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<i32> = 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<i64> = 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<Role> = 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();
}

View file

@ -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

View file

@ -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 {

View file

@ -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<i32>,
}
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(),
}
}
}

View file

@ -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<Arc<Session>>,
// Persistent
pub basic_info: PlayerBasicInfo,
pub role_list: Vec<Role>,
pub formation_list: Vec<RoleFormation>,
pub role_list: HashMap<i32, Role>,
pub formation_list: HashMap<i32, RoleFormation>,
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<i32> = 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 {
self.formation_list.insert(
1,
RoleFormation {
id: 1,
cur_role: role.role_id,
role_id_set: HashSet::from([role.role_id]),
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<Role>,
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
pub fn build_player_entity_remove_notify(
&self,
entities: Vec<i64>,
remove_type: ERemoveEntityType,
) -> EntityRemoveNotify {
EntityRemoveNotify {
remove_infos: entities
.iter()
.find(|rf| rf.is_current)
.unwrap()
.cur_role
.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<i32, (i32, i32)> = 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::<HashMap<i32, Role>>(),
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()

View file

@ -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<i32>,
pub role_ids: Vec<i32>,
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,
}
}

View file

@ -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<i32, i32> = 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,11 +62,17 @@ 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 {
pub fn load_from_save(data: RoleData) -> (i32, Self) {
(
data.role_id,
Self {
role_id: data.role_id,
name: data.name,
@ -71,7 +84,8 @@ impl Role {
favor: data.favor,
create_time: data.create_time,
equip_weapon: data.equip_weapon,
}
},
)
}
pub fn build_save_data(&self) -> RoleData {

View file

@ -15,8 +15,9 @@ pub(super) struct MovementSystem;
impl System for MovementSystem {
fn tick(&self, world: &mut World, players: &mut [RefMut<Player>]) {
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();
}

View file

@ -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();
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()),
});

View file

@ -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::<Vec<_>>();
.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::<HashSet<(EEntityType, i32)>>();
let mut aoi_data = PlayerSceneAoiData::default();
for entity in entities {
let mut pb = EntityPb { id: entity.into(), ..Default::default() };
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
};
}
);
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)
.get_entity_components(entity_id)
.into_iter()
.for_each(|comp| comp.set_pb_data(&mut pb));
aoi_data.entities.push(pb);
}
}
_ => {}
};
});
aoi_data
}

View file

@ -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<i32, i32> {
HashMap::from([$(
::paste::paste!((EAttributeType::[<$name:camel>] as i32, base_property.$name as i32)),
)*])
}
pub fn attribute_from_data(base_property: &BasePropertyData) -> HashMap<EAttributeType, (i32, i32)> {
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
);

View file

@ -1,2 +1,3 @@
pub mod entity_serializer;
pub mod load_role_info;
pub mod world_util;

View file

@ -1,28 +1,96 @@
use shorekeeper_data::base_property_data;
use shorekeeper_protocol::{
EntityConfigType, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation, SceneMode,
ScenePlayerInformation, SceneTimeInfo,
};
use super::entity_serializer;
use crate::{
logic::{
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, world::World},
player::Player,
},
query_with,
ecs::component::ComponentContainer,
};
use crate::query_with;
use shorekeeper_data::base_property_data;
use shorekeeper_protocol::{
EEntityType, EntityConfigType, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation,
SceneMode, ScenePlayerInformation, SceneTimeInfo,
};
pub fn add_player_entities(world: &mut World, player: &Player) {
let cur_role_id = player.get_cur_role_id();
#[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();
for role in player.get_current_formation_role_list() {
let id = world
.create_entity()
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");
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();
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::<Vec<_>>();
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,
@ -54,29 +122,30 @@ pub fn add_player_entities(world: &mut World, player: &Player) {
tracing::debug!(
"created player entity, id: {}, role_id: {}",
i64::from(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,22 +154,31 @@ fn build_player_info_list(world: &World) -> Vec<ScenePlayerInformation> {
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)
let active_characters = query_with!(
world.get_world_entity(),
PlayerEntityMarker,
OwnerPlayer,
EntityConfig
)
.into_iter()
.filter(|(_, _, owner, _)| owner.0 == sp.player_id);

View file

@ -20,6 +20,8 @@ message PlayerBasicData {
int32 exp = 5;
int32 head_photo = 6;
int32 head_frame = 7;
int32 cur_map_id = 8;
repeated int32 role_show_list = 9;
}
message RoleSkillNodeData {
@ -55,7 +57,8 @@ message RoleFormationData {
message PlayerRoleData {
repeated RoleData role_list = 1;
repeated RoleFormationData role_formation_list = 2;
map<int32, RoleFormationData> role_formation_list = 2;
int32 cur_formation_id = 3;
}
message PlayerLocationData {