wicked-waifus-rs/wicked-waifus-game-server/src/logic/utils/world_util.rs
2025-05-10 20:16:21 +03:00

656 lines
26 KiB
Rust

use wicked_waifus_protocol::{
EEntityType, ERemoveEntityType, EntityAddNotify, EntityConfigType, EntityPb, EntityRemoveInfo,
EntityRemoveNotify, EntityState, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation,
SceneMode, ScenePlayerInformation, SceneTimeInfo,
};
use wicked_waifus_data::pb_components::ComponentsData;
use wicked_waifus_data::{
blueprint_config_data, template_config_data, EntityLogic, EntityType, LevelEntityConfigData,
};
use crate::logic::components::{Autonomous, Concomitant, Fsm, Interact, MonsterAi, SoarWingSkin, StateTag, Summoner, Tag};
use crate::logic::ecs::entity::EntityBuilder;
use crate::logic::ecs::world::World;
use crate::logic::math::Transform;
use crate::logic::player::Player;
use crate::logic::utils::{entity_serializer, tag_utils};
use crate::logic::utils::growth_utils::get_monster_props_by_level;
use crate::logic::{
components::{
Attribute, EntityConfig, Equip, FightBuff, Movement, OwnerPlayer, PlayerOwnedEntityMarker,
Position, RoleSkin, Visibility, VisionSkill,
},
ecs::component::ComponentContainer,
};
use crate::query_with;
#[macro_export]
macro_rules! create_player_entity_pb {
($role_list:expr, $cur_map_id:expr, $player:expr, $player_id:expr, $position:expr, $explore_tools:expr) => {{
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 cur_role_id = current_formation.cur_role;
let mut pbs = Vec::new();
for role in $role_list {
let entity = world.create_entity(
role.role_id,
EEntityType::Player.into(),
$cur_map_id,
);
// Once per character buffs are implemented, add a mut on role_buffs
let fight_buff_infos = world.generate_role_permanent_buffs(entity.entity_id as i64);
let buf_manager = FightBuff {
fight_buff_infos,
list_buff_effect_cd: vec![],
};
let entity = world.create_builder(entity)
.with(ComponentContainer::PlayerOwnedEntityMarker(PlayerOwnedEntityMarker {
entity_type: EEntityType::Player,
}))
.with(ComponentContainer::EntityConfig(EntityConfig {
camp: 0,
config_id: role.role_id,
config_type: EntityConfigType::Character,
entity_type: EEntityType::Player.into(),
entity_state: EntityState::Default,
}))
.with(ComponentContainer::OwnerPlayer(OwnerPlayer($player_id)))
.with(ComponentContainer::Position(Position($position)))
.with(ComponentContainer::Visibility(Visibility{
is_visible: role.role_id == cur_role_id,
is_actor_visible: true,
}))
// TODO: Check if role has hardness or rage_mode
// TODO: Support AddProp from Equipment(Echo, weapon, buffs??), weapon base state goes to base_prop too.
.with(ComponentContainer::Attribute(
Attribute::from_data(
&role.get_base_properties(),
None,
None,
)
))
.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,
}))
.with(ComponentContainer::RoleSkin(RoleSkin {
skin_id: role.skin_id,
}))
.with(ComponentContainer::SoarWingSkin(SoarWingSkin {
skin_id: 84000001,
}))
.with(ComponentContainer::FightBuff(buf_manager))
.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,
remove_tag_ids: 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,
);
// Once per character buffs are implemented, add a mut on role_buffs
let fight_buff_infos = world.generate_role_permanent_buffs(entity.entity_id as i64);
let buf_manager = FightBuff {
fight_buff_infos,
list_buff_effect_cd: vec![],
};
let entity = world
.create_builder(entity)
.with(ComponentContainer::PlayerOwnedEntityMarker(
PlayerOwnedEntityMarker {
entity_type: EEntityType::Player,
},
))
.with(ComponentContainer::EntityConfig(EntityConfig {
camp: 0,
config_id: role.role_id,
config_type: EntityConfigType::Character,
entity_type: EEntityType::Player.into(),
entity_state: EntityState::Default,
}))
.with(ComponentContainer::OwnerPlayer(OwnerPlayer(
player.basic_info.id,
)))
.with(ComponentContainer::Position(Position(
player.location.position.clone(),
)))
.with(ComponentContainer::Visibility(Visibility {
is_visible: role.role_id == cur_role_id,
is_actor_visible: true,
}))
// TODO: from role
// TODO: Check if role has hardness or rage_mode
// TODO: Support AddProp from Equipment(Echo, weapon, buffs??), weapon base state goes to base_prop too.
.with(ComponentContainer::Attribute(Attribute::from_data(
&role.get_base_properties(),
None,
None,
)))
.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,
}))
.with(ComponentContainer::RoleSkin(RoleSkin {
skin_id: role.skin_id,
}))
.with(ComponentContainer::SoarWingSkin(SoarWingSkin {
skin_id: 84000001,
}))
.with(ComponentContainer::FightBuff(buf_manager))
.build();
tracing::debug!(
"created player entity, id: {}, role_id: {}",
entity.entity_id,
role.role_id
);
}
}
}
pub fn build_scene_information(player: &Player) -> SceneInformation {
SceneInformation {
scene_id: String::new(),
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(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: player.basic_info.id as i64,
..Default::default()
}
}
fn build_player_info_list(world: &World) -> Vec<ScenePlayerInformation> {
world
.players()
.map(|sp| {
let (cur_role_id, transform, _equip) = query_with!(
world.get_world_entity(),
PlayerOwnedEntityMarker,
OwnerPlayer,
Visibility,
EntityConfig,
Position,
Equip
)
.into_iter()
.find_map(|(_, _, owner, visibility, conf, pos, equip)| {
(sp.player_id == owner.0 && visibility.is_visible).then_some((
conf.config_id,
pos.0.clone(),
equip.weapon_id,
))
})
.unwrap_or_default();
let active_characters = query_with!(
world.get_world_entity(),
PlayerOwnedEntityMarker,
OwnerPlayer,
EntityConfig,
RoleSkin
)
.into_iter()
.filter(|(_, _, owner, _, _)| owner.0 == sp.player_id);
ScenePlayerInformation {
cur_role: cur_role_id,
group_type: sp.group_type,
player_id: sp.player_id,
player_icon: sp.player_icon,
player_name: sp.player_name.clone(),
level: sp.level,
location: Some(transform.get_position_protobuf()),
rotation: Some(transform.get_rotation_protobuf()),
fight_role_infos: Vec::from([FightRoleInfos {
group_type: sp.group_type,
living_status: LivingStatus::Alive.into(),
cur_role: cur_role_id,
// is_retain: true,
fight_role_infos: active_characters
.map(|(id, _, _, conf, role_skin)| FightRoleInfo {
entity_id: id.into(),
role_id: conf.config_id,
on_stage_without_control: false,
})
.collect(),
..Default::default()
}]),
// vehicle_player_data: Vec::ne(),
..Default::default()
}
})
.collect()
}
pub fn remove_entity(player: &Player, entity_id: i64, remove_type: ERemoveEntityType) {
let mut world_ref = player.world.borrow_mut();
let world = world_ref.get_mut_world_entity();
if world.remove_entity(entity_id as i32) {
player.notify(EntityRemoveNotify {
remove_infos: vec![EntityRemoveInfo {
entity_id,
r#type: remove_type.into(),
}],
is_remove: true,
});
}
}
pub fn remove_entities(player: &Player, entities: &[&LevelEntityConfigData]) {
let mut removed_entities = Vec::with_capacity(entities.len());
// Enclose to drop borrow mut ASAP
{
let mut world_ref = player.world.borrow_mut();
let world = world_ref.get_mut_world_entity();
for entity in entities {
let entity_id = entity.entity_id as i32; // TODO: Should be i64
if world.remove_entity(entity_id) {
removed_entities.push(world.get_entity_id(entity_id));
}
}
}
for entity_id in removed_entities {
player.notify(EntityRemoveNotify {
remove_infos: vec![EntityRemoveInfo {
entity_id,
r#type: 0,
}],
is_remove: true,
});
}
}
const CONCOM_ROLE_ID: &[(i32, i32)] = &[
(38, 1407),
(36, 1105),
(35, 1506),
];
fn get_role_id_from_concom(key: i32) -> Option<i32> {
CONCOM_ROLE_ID.iter().find(|&&(k, _)| k == key).map(|&(_, v)| v)
}
fn extract_concom_number(s: String) -> Option<i32> {
let prefix = "Player0";
if !s.starts_with(prefix) {
return None;
}
let rest = &s[prefix.len()..]; // Skip "Player0"
let underscore_index = rest.find('_')?;
let number_str = &rest[..underscore_index];
number_str.parse::<i32>().ok()
}
pub fn add_entities(player: &Player, entities: &[&LevelEntityConfigData], external_awake: bool) {
let mut added_entities = Vec::with_capacity(entities.len());
// Enclose to drop borrow mut ASAP
{
let mut world_ref = player.world.borrow_mut();
let world = world_ref.get_mut_world_entity();
let current_formation = player.formation_list.get(&player.cur_formation_id).unwrap();
let cur_role_id = current_formation.cur_role;
for (_, blueprint_config) in wicked_waifus_data::blueprint_config_data::iter().filter(|(_, bc)| {
bc.blueprint_type.starts_with("Player0") && bc.entity_type == EntityType::Monster
}) {
let blueprint_role_id = get_role_id_from_concom(extract_concom_number(blueprint_config.blueprint_type.clone()).unwrap());
if blueprint_role_id.is_none() || current_formation.role_ids.contains(&blueprint_role_id.unwrap()) {continue}
let (_, template_config) = wicked_waifus_data::template_config_data::iter().find(|(_, tc)| tc.blueprint_type == blueprint_config.blueprint_type).unwrap();
tracing::debug!(
"getting summoner cfg, blueprint_type: {}, template_config_id: {}",
template_config.blueprint_type,
template_config.id
);
let (_, summoner_cfg) = wicked_waifus_data::summon_cfg_data::iter().find(|(_, sc)| sc.blueprint_type == blueprint_config.blueprint_type).unwrap();
let concomitant= world.create_entity(template_config.id, EEntityType::Monster.into(), player.basic_info.cur_map_id);
let fight_buff_infos = world.generate_concom_buffs(summoner_cfg.born_buff_id.clone(), concomitant.entity_id as i64);
let buf_manager = FightBuff {
fight_buff_infos,
list_buff_effect_cd: vec![],
};
added_entities.push(world
.create_builder(concomitant)
.with(ComponentContainer::EntityConfig(EntityConfig {
camp: 0,
config_id: template_config.id,
config_type: EntityConfigType::Template,
entity_type: EEntityType::Monster.into(),
entity_state: EntityState::Born
}))
.with(ComponentContainer::Summoner(Summoner { summon_cfg_id: summoner_cfg.id, summon_skill_id: 1, summon_type: 2 }))
.with(ComponentContainer::FightBuff(buf_manager))
.with(ComponentContainer::Autonomous(Autonomous { autonomous_id: player.basic_info.id }))
.with(ComponentContainer::Visibility(Visibility { is_visible: false, is_actor_visible: true }))
.with(ComponentContainer::Position(Position(player.location.position.clone())))
.with(ComponentContainer::Concomitant(Concomitant {
vision_entity_id: 0,
custom_entity_ids: vec![concomitant.entity_id as i64],
phantom_role_id: 0,
}))
// .with(ComponentContainer::Attribute(Attribute { attr_map: template_config.components_data.attribute_component.unwrap(), hardness_mode_id: (), rage_mode_id: () }))
// .with(ComponentContainer::Fsm(Fsm { hash_code: (), common_hash_code: (), state_list: (), node_list: () }))
.build());
tracing::debug!(
"created concom entity, id: {}, role_id: {}",
template_config.id,
cur_role_id
);
}
for entity in entities {
// Skip hidden entities
if entity.is_hidden {
tracing::debug!("Hidden entity with config id: {}", entity.entity_id);
continue;
}
if entity.in_sleep && !external_awake {
tracing::debug!(
"Sleep entity with config id not spawned: {}",
entity.entity_id
);
continue;
}
let blueprint_config = blueprint_config_data::get(&entity.blueprint_type);
let template_config = template_config_data::get(&entity.blueprint_type);
if blueprint_config.is_none() || template_config.is_none() {
continue;
}
let entity_logic: EntityLogic = blueprint_config.unwrap().entity_logic;
let (config_type, entity_type, mut entity_state) = match entity_logic {
EntityLogic::Item => (
EntityConfigType::Level,
EEntityType::SceneItem,
EntityState::Default,
),
EntityLogic::Animal => (
EntityConfigType::Level,
EEntityType::Animal,
EntityState::Default,
),
EntityLogic::Monster => (
EntityConfigType::Level,
EEntityType::Monster,
EntityState::Born,
),
EntityLogic::Vehicle => (
EntityConfigType::Level,
EEntityType::Vehicle,
EntityState::Default,
),
EntityLogic::Npc => (
EntityConfigType::Level,
EEntityType::Npc,
EntityState::Default,
),
EntityLogic::Vision => (
EntityConfigType::Level,
EEntityType::Vision,
EntityState::Default,
),
EntityLogic::ClientOnly => (
EntityConfigType::Level,
EEntityType::ClientOnly,
EntityState::Default,
),
EntityLogic::ServerOnly => {
tracing::debug!("Unhandled entity to be added of logic: {:?} with blueprint_type {} and id: {}", entity_logic, entity.blueprint_type, entity.entity_id);
continue;
}
EntityLogic::Custom => (
EntityConfigType::Level,
EEntityType::Custom,
EntityState::Default,
),
};
if entity.in_sleep {
entity_state = EntityState::Sleep;
}
let config_id = entity.entity_id as i32; // TODO: i64????
let map_id = entity.map_id;
let components: ComponentsData = entity
.components_data
.merge_with_template(&template_config.unwrap().components_data);
let tmp_entity = world.create_entity(config_id, config_type.into(), map_id);
let mut builder = world.create_builder(tmp_entity);
builder
.with(ComponentContainer::EntityConfig(EntityConfig {
camp: components
.base_info_component
.as_ref()
.and_then(|b| b.camp)
.unwrap_or(0),
config_id,
config_type,
entity_type,
entity_state,
}))
.with(ComponentContainer::Position(Position(Transform::from(
&entity.transform[..],
))))
.with(ComponentContainer::Visibility(Visibility {
is_visible: true,
is_actor_visible: true,
}))
// Some entities may not actually have movement, but it's okay since we won't
// receive move package push for them
.with(ComponentContainer::Movement(Movement::default()));
build_autonomous_component(&mut builder, player.basic_info.id, entity_logic);
build_interact_component(&mut builder, &components);
build_tags_components(&mut builder, &components, player, blueprint_config.unwrap().entity_type, config_id as i64);
build_attribute_component(&mut builder, &components, player.location.instance_id);
build_ai_components(&mut builder, &components);
added_entities.push(builder.build());
}
}
let world_ref = player.world.borrow();
let world = world_ref.get_world_entity();
// Since kuro has issues, we can only send one
for entity in added_entities {
let mut pb = EntityPb {
id: entity.entity_id as i64, // TODO: Should be i64
..Default::default()
};
world
.get_entity_components(entity.entity_id)
.into_iter()
.for_each(|comp| comp.set_pb_data(&mut pb));
player.notify(EntityAddNotify {
entity_pbs: vec![pb],
remove_tag_ids: true,
});
}
}
#[inline(always)]
fn build_autonomous_component(builder: &mut EntityBuilder, id: i32, logic: EntityLogic) {
// TODO: Review if other types have autonomous
match logic {
EntityLogic::Item => {
builder.with(ComponentContainer::Autonomous(Autonomous {
autonomous_id: id,
}));
}
EntityLogic::Animal => {}
EntityLogic::Monster => {}
EntityLogic::Vehicle => {}
EntityLogic::Npc => {}
EntityLogic::Vision => {}
EntityLogic::ClientOnly => {}
EntityLogic::ServerOnly => {}
EntityLogic::Custom => {}
};
}
#[inline(always)]
fn build_interact_component(builder: &mut EntityBuilder, components: &ComponentsData) {
if components.interact_component.is_some() {
builder.with(ComponentContainer::Interact(Interact {}));
}
}
#[inline(always)]
fn build_tags_components(
builder: &mut EntityBuilder,
components: &ComponentsData,
player: &Player,
entity_type: EntityType,
config_id: i64,
) {
if let Some(entity_state_component) = &components.entity_state_component {
let state = match entity_type {
EntityType::Teleporter | EntityType::TemporaryTeleporter => {
let result = player.teleports.teleports_data.iter()
.find(|teleporter| teleporter.entity_config_id == config_id);
match result.is_some() {
true => tag_utils::get_tag_id_by_name("关卡.Common.状态.激活"),
false => tag_utils::get_tag_id_by_name("关卡.Common.状态.常态"),
}
}
_ => {
// TODO: how to get states???
let unlocked = false;
match unlocked {
true => tag_utils::get_tag_id_by_name("关卡.Common.状态.激活"),
false => tag_utils::get_tag_id_by_name("关卡.Common.状态.常态"),
}
}
};
builder
.with(ComponentContainer::StateTag(StateTag {
state_tag_id: state,
}))
.with(ComponentContainer::Tag(Tag {
// TODO:
gameplay_tags: vec![],
entity_common_tags: vec![state],
// TODO:
init_gameplay_tag: false,
}));
}
}
#[inline(always)]
fn build_attribute_component(
builder: &mut EntityBuilder,
components: &ComponentsData,
instance_id: i32,
) {
if let Some(attribute_component) = &components.attribute_component {
if attribute_component.disabled.unwrap_or_default() {
return;
}
if let Some(property_id) = attribute_component.property_id {
let inst_data = wicked_waifus_data::instance_dungeon_data::iter()
.find(|d| d.id == instance_id)
.unwrap();
let mut level = inst_data.entity_level;
if level == 0 {
// TODO:
// - iterate area_data
// - find player.basic_info.area_id
// - get player world level
// - get area.world_monster_level_max
level = 90;
}
builder.with(ComponentContainer::Attribute(Attribute::from_data(
&get_monster_props_by_level(property_id, level),
attribute_component.hardness_mode_id,
attribute_component.rage_mode_id,
)));
}
}
}
#[inline(always)]
fn build_ai_components(builder: &mut EntityBuilder, components: &ComponentsData) {
if let Some(ai_component) = &components.ai_component {
builder.with(ComponentContainer::MonsterAi(MonsterAi {
weapon_id: ai_component
.weapon_id
.as_deref()
.and_then(|id| id.parse().ok())
.unwrap_or(0),
hatred_group_id: 0, // TODO:
ai_team_init_id: 100, // TODO:
combat_message_id: 0, // TODO:
}));
if let Some(ai_id) = ai_component.ai_id {
builder.with(ComponentContainer::Fsm(Fsm::from_ai_id(ai_id)));
}
}
}