diff --git a/common/src/comp/pet.rs b/common/src/comp/pet.rs index 7943be6b15..a250232023 100644 --- a/common/src/comp/pet.rs +++ b/common/src/comp/pet.rs @@ -1,4 +1,4 @@ -use crate::comp::{body::Body, phys::Mass, quadruped_medium, quadruped_small}; +use crate::comp::{body::Body, phys::Mass, quadruped_medium, quadruped_small, Pos}; use crossbeam_utils::atomic::AtomicCell; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage}; @@ -109,15 +109,10 @@ impl Component for Pet { type Storage = specs::VecStorage; } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum StayFollow { - Stay, - Follow, -} - #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)] pub struct PetState { pub stay: bool, + pub stay_pos: Option, } impl Component for PetState { diff --git a/server/agent/src/consts.rs b/server/agent/src/consts.rs index fb1c828280..fe587aa7bf 100644 --- a/server/agent/src/consts.rs +++ b/server/agent/src/consts.rs @@ -3,6 +3,7 @@ pub const FLEE_DURATION: f32 = 3.0; pub const NPC_PICKUP_RANGE: f32 = 2.5; pub const MAX_PATROL_DIST: f32 = 50.0; pub const MAX_PATH_DIST: f32 = 170.0; +pub const MAX_STAY_DISTANCE: f32 = 10.0; pub const PARTIAL_PATH_DIST: f32 = 50.0; pub const SEPARATION_DIST: f32 = 10.0; pub const SEPARATION_BIAS: f32 = 0.8; diff --git a/server/agent/src/data.rs b/server/agent/src/data.rs index 298927f506..667b9e38dc 100644 --- a/server/agent/src/data.rs +++ b/server/agent/src/data.rs @@ -54,6 +54,7 @@ pub struct AgentData<'a> { pub glider_equipped: bool, pub is_gliding: bool, pub is_stay: bool, + pub stay_pos: Option, pub health: Option<&'a Health>, pub char_state: &'a CharacterState, pub active_abilities: &'a ActiveAbilities, diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 0c7af57610..de65be28a0 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -213,12 +213,19 @@ pub fn handle_toggle_stay(server: &mut Server, pet: EcsEntity) { let _ = state .ecs() .write_storage::() - .insert(pet, PetState { stay: false }); + .insert(pet, PetState { + stay: false, + stay_pos: None, + }); } else { + let current_pos = state.ecs().read_storage::().get(pet).copied(); let _ = state .ecs() .write_storage::() - .insert(pet, PetState { stay: true }); + .insert(pet, PetState { + stay: true, + stay_pos: current_pos, + }); } } diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 1b30497877..2ca2a3520b 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -160,8 +160,11 @@ impl<'a> System<'a> for Sys { .map_or(false, |item| { matches!(&*item.kind(), comp::item::ItemKind::Glider) }); + let is_stay = pet_state.map_or(false, |s| s.stay); + let stay_pos = pet_state.and_then(|s| s.stay_pos); + let is_gliding = matches!( read_data.char_states.get(entity), Some(CharacterState::GlideWield(_) | CharacterState::Glide(_)) @@ -233,6 +236,7 @@ impl<'a> System<'a> for Sys { glider_equipped, is_gliding, is_stay, + stay_pos, health: read_data.healths.get(entity), char_state, active_abilities, diff --git a/server/src/sys/agent/behavior_tree.rs b/server/src/sys/agent/behavior_tree.rs index fe235adf8f..273b50a7c3 100644 --- a/server/src/sys/agent/behavior_tree.rs +++ b/server/src/sys/agent/behavior_tree.rs @@ -25,7 +25,7 @@ use self::interaction::{ use super::{ consts::{ DAMAGE_MEMORY_DURATION, FLEE_DURATION, HEALING_ITEM_THRESHOLD, MAX_PATROL_DIST, - NORMAL_FLEE_DIR_DIST, NPC_PICKUP_RANGE, RETARGETING_THRESHOLD_SECONDS, + MAX_STAY_DISTANCE, NORMAL_FLEE_DIR_DIST, NPC_PICKUP_RANGE, RETARGETING_THRESHOLD_SECONDS, STD_AWARENESS_DECAY_RATE, }, data::{AgentData, ReadData, TargetData}, @@ -406,13 +406,25 @@ fn do_pickup_loot(bdata: &mut BehaviorData) -> bool { fn follow_if_far_away(bdata: &mut BehaviorData) -> bool { if let Some(Target { target, .. }) = bdata.agent.target { if let Some(tgt_pos) = bdata.read_data.positions.get(target) { - let dist_sqrd = bdata.agent_data.pos.0.distance_squared(tgt_pos.0); let stay = bdata.agent_data.is_stay; - if dist_sqrd > (MAX_PATROL_DIST * bdata.agent.psyche.idle_wander_factor).powi(2) && !stay { - bdata + if stay { + let stay_pos = bdata.agent_data.stay_pos.map_or(Pos(Vec3::zero()), |v| v); + let distance_from_stay = stay_pos.0.distance_squared(bdata.agent_data.pos.0); + + if distance_from_stay > (MAX_STAY_DISTANCE).powi(2) { + bdata + .agent_data + .follow(bdata.agent, bdata.controller, bdata.read_data, &stay_pos); + return true; + } + } else { + let dist_sqrd = bdata.agent_data.pos.0.distance_squared(tgt_pos.0); + if dist_sqrd > (MAX_PATROL_DIST * bdata.agent.psyche.idle_wander_factor).powi(2) { + bdata .agent_data .follow(bdata.agent, bdata.controller, bdata.read_data, tgt_pos); - return true; + return true; + } } } } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index be988286a2..51e8805b02 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -2432,21 +2432,21 @@ impl Hud { i18n.get_msg("hud-mount").to_string(), )); } - } - let pet_stay = is_stay.get(entity).map(|st| st.stay); - match pet_stay { - Some(false) => options.push(( - GameInput::StayFollow, - i18n.get_msg("hud-stay").to_string(), - )), - Some(true) => options.push(( - GameInput::StayFollow, - i18n.get_msg("hud-follow").to_string(), - )), - None => options.push(( - GameInput::StayFollow, - i18n.get_msg("hud-stay").to_string(), - )), + let pet_stay = is_stay.get(entity).map(|st| st.stay); + match pet_stay { + Some(false) => options.push(( + GameInput::StayFollow, + i18n.get_msg("hud-stay").to_string(), + )), + Some(true) => options.push(( + GameInput::StayFollow, + i18n.get_msg("hud-follow").to_string(), + )), + None => options.push(( + GameInput::StayFollow, + i18n.get_msg("hud-stay").to_string(), + )), + } } options }, diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index b8e1608186..5f56fb2eb5 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -963,7 +963,7 @@ impl PlayState for SessionState { *dist_sqr < MAX_MOUNT_RANGE.powi(2) }) .min_by_key(|(_, dist_sqr)| OrderedFloat(*dist_sqr)); - if let Some((pet_entity, _)) = closest_pet { + if let Some((pet_entity, _)) = closest_pet && client.state().read_storage::>().get(pet_entity).is_none() { client.toggle_stay(pet_entity); } }