diff --git a/CHANGELOG.md b/CHANGELOG.md index c7b1bfa438..ea621f30eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Recipe for shovel, which is used to dig in mud and graves - Recipe for a new leather pack - Keybinds for zooming the camera (Defaults: ']' for zooming in and '[' for zooming out) +- Added the ability to make pets sit, they wont follow nor defend you in this state ### Changed diff --git a/assets/voxygen/i18n/en/gameinput.ftl b/assets/voxygen/i18n/en/gameinput.ftl index 485c46c36b..55eade661a 100644 --- a/assets/voxygen/i18n/en/gameinput.ftl +++ b/assets/voxygen/i18n/en/gameinput.ftl @@ -33,6 +33,7 @@ gameinput-climbdown = Climb Down gameinput-wallleap = Wall Leap gameinput-togglelantern = Toggle Lantern gameinput-mount = Mount +gameinput-stay = Stay/Follow gameinput-chat = Chat gameinput-command = Command gameinput-escape = Escape diff --git a/assets/voxygen/i18n/en/hud/misc.ftl b/assets/voxygen/i18n/en/hud/misc.ftl index 6b832607d8..1115e37759 100644 --- a/assets/voxygen/i18n/en/hud/misc.ftl +++ b/assets/voxygen/i18n/en/hud/misc.ftl @@ -54,5 +54,7 @@ hud-mine-needs_unhandled_case = Needs ??? hud-talk = Talk hud-trade = Trade hud-mount = Mount +hud-follow = Follow +hud-stay= Stay hud-sit = Sit hud-steer = Steer diff --git a/client/src/lib.rs b/client/src/lib.rs index c19d73529b..cd07e42870 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1477,6 +1477,14 @@ impl Client { pub fn unmount(&mut self) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Unmount)); } + pub fn set_pet_stay(&mut self, entity: EcsEntity, stay: bool) { + if let Some(uid) = self.state.read_component_copied(entity) { + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::SetPetStay( + uid, stay, + ))); + } + } + pub fn respawn(&mut self) { if self .state diff --git a/common/net/src/synced_components.rs b/common/net/src/synced_components.rs old mode 100644 new mode 100755 index f83f9c6e15..d8d5bf858b --- a/common/net/src/synced_components.rs +++ b/common/net/src/synced_components.rs @@ -75,7 +75,6 @@ macro_rules! reexport_comps { pub use common::comp::*; use common::link::Is; use common::mounting::{Mount, Rider, VolumeRider}; - // We alias these because the identifier used for the // component's type is reused as an enum variant name // in the macro's that we pass to `synced_components!`. @@ -105,8 +104,6 @@ synced_components!(reexport_comps); use crate::sync::{NetSync, SyncFrom}; -// These are synced from any entity within range. - impl NetSync for Body { const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; } diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 3f75505641..8985d5eb88 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -595,6 +595,7 @@ pub struct Agent { /// required and reset each time the flee timer is reset. pub flee_from_pos: Option, pub awareness: Awareness, + pub stay_pos: Option, /// Inputs sent up to rtsim pub rtsim_outbox: Option>, } @@ -700,6 +701,7 @@ impl Agent { sounds_heard: Vec::new(), position_pid_controller: None, flee_from_pos: None, + stay_pos: None, awareness: Awareness::new(0.0), rtsim_outbox: None, } diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 8cafa1b659..48482d123b 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -1004,6 +1004,9 @@ pub struct CharacterActivity { /// `None` means that the look direction should be derived from the /// orientation pub look_dir: Option, + /// If true, the owner has set this pet to stay at a fixed location and + /// to not engage in combat + pub is_pet_staying: bool, } impl Component for CharacterActivity { diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index f26396f176..c0e1e82159 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -146,6 +146,7 @@ pub enum ControlEvent { Mount(Uid), MountVolume(VolumePos), Unmount, + SetPetStay(Uid, bool), InventoryEvent(InventoryEvent), GroupManip(GroupManip), RemoveBuff(BuffKind), diff --git a/common/src/comp/pet.rs b/common/src/comp/pet.rs old mode 100644 new mode 100755 diff --git a/common/src/event.rs b/common/src/event.rs index cb2f31d864..006607afd5 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -200,6 +200,7 @@ pub enum ServerEvent { Mount(EcsEntity, EcsEntity), MountVolume(EcsEntity, VolumePos), Unmount(EcsEntity), + SetPetStay(EcsEntity, EcsEntity, bool), Possess(Uid, Uid), /// Inserts default components for a character when loading into the game InitCharacterData { diff --git a/common/systems/src/controller.rs b/common/systems/src/controller.rs index 6539c62c63..9da0652d13 100644 --- a/common/systems/src/controller.rs +++ b/common/systems/src/controller.rs @@ -63,6 +63,11 @@ impl<'a> System<'a> for Sys { } } }, + ControlEvent::SetPetStay(pet_uid, stay) => { + if let Some(pet_entity) = read_data.id_maps.uid_entity(pet_uid) { + server_emitter.emit(ServerEvent::SetPetStay(entity, pet_entity, stay)); + } + }, ControlEvent::RemoveBuff(buff_id) => { server_emitter.emit(ServerEvent::Buff { entity, diff --git a/server/agent/src/consts.rs b/server/agent/src/consts.rs index fb1c828280..e7a81835ff 100644 --- a/server/agent/src/consts.rs +++ b/server/agent/src/consts.rs @@ -3,6 +3,9 @@ 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; +/// If the pet is any further than this value from its stay position, it will +/// start walking back there +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 old mode 100644 new mode 100755 diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs old mode 100644 new mode 100755 index 1a4661ee94..9741c958af --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -17,7 +17,7 @@ use common::{ consts::{MAX_MOUNT_RANGE, MAX_SPRITE_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME}, event::EventBus, link::Is, - mounting::{Mounting, Rider, VolumeMounting, VolumePos, VolumeRider}, + mounting::{Mount, Mounting, Rider, VolumeMounting, VolumePos, VolumeRider}, outcome::Outcome, rtsim::RtSimVehicle, terrain::{Block, SpriteKind}, @@ -132,7 +132,15 @@ pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) { is_mountable(mount_body, state.ecs().read_storage().get(rider)) }); - if (is_pet_of(mount, rider_uid) || is_pet_of(rider, mount_uid)) && can_ride { + let is_stay = state + .ecs() + .read_storage::() + .get(mount) + .and_then(|x| x.stay_pos) + .is_some(); + + if (is_pet_of(mount, rider_uid) || is_pet_of(rider, mount_uid)) && can_ride && !is_stay + { drop(uids); let _ = state.link(Mounting { mount: mount_uid, @@ -202,6 +210,46 @@ pub fn handle_unmount(server: &mut Server, rider: EcsEntity) { state.ecs().write_storage::>().remove(rider); } +pub fn handle_set_pet_stay( + server: &mut Server, + command_giver: EcsEntity, + pet: EcsEntity, + stay: bool, +) { + let state = server.state_mut(); + let positions = state.ecs().read_storage::(); + let is_owner = state + .ecs() + .uid_from_entity(command_giver) + .map_or(false, |owner_uid| { + matches!( + state + .ecs() + .read_storage::() + .get(pet), + Some(comp::Alignment::Owned(pet_owner)) if *pet_owner == owner_uid, + ) + }); + + let current_pet_position = positions.get(pet).copied(); + let stay = stay && current_pet_position.is_some(); + if is_owner + && within_mounting_range(positions.get(command_giver), positions.get(pet)) + && state.ecs().read_storage::>().get(pet).is_none() + { + state + .ecs() + .write_storage::() + .get_mut(pet) + .map(|mut activity| activity.is_pet_staying = stay); + state + .ecs() + .write_storage::() + .get_mut(pet) + .map(|s| s.stay_pos = current_pet_position); + } +} + fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<&Pos>) -> bool { match (player_position, mount_position) { (Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_MOUNT_RANGE.powi(2), diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 125d904daa..1173f9267b 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -23,7 +23,7 @@ use group_manip::handle_group; use information::handle_site_info; use interaction::{ handle_create_sprite, handle_lantern, handle_mine_block, handle_mount, handle_npc_interaction, - handle_sound, handle_unmount, + handle_set_pet_stay, handle_sound, handle_unmount, }; use inventory_manip::handle_inventory; use invite::{handle_invite, handle_invite_response}; @@ -144,6 +144,9 @@ impl Server { handle_mount_volume(self, mounter, volume) }, ServerEvent::Unmount(mounter) => handle_unmount(self, mounter), + ServerEvent::SetPetStay(command_giver, pet, stay) => { + handle_set_pet_stay(self, command_giver, pet, stay) + }, ServerEvent::Possess(possessor_uid, possesse_uid) => { handle_possess(self, possessor_uid, possesse_uid) }, diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs old mode 100644 new mode 100755 index 35b879eb01..8747f9ba9f --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -68,9 +68,11 @@ impl<'a> System<'a> for Sys { read_data.light_emitter.maybe(), read_data.groups.maybe(), read_data.rtsim_entities.maybe(), - !&read_data.is_mounts, - read_data.is_riders.maybe(), - read_data.is_volume_riders.maybe(), + ( + !&read_data.is_mounts, + read_data.is_riders.maybe(), + read_data.is_volume_riders.maybe(), + ), ) .par_join() .for_each_init( @@ -93,9 +95,7 @@ impl<'a> System<'a> for Sys { light_emitter, group, rtsim_entity, - _, - is_rider, - is_volume_rider, + (_, is_rider, is_volume_rider), )| { let mut event_emitter = event_bus.emitter(); let mut rng = thread_rng(); diff --git a/server/src/sys/agent/behavior_tree.rs b/server/src/sys/agent/behavior_tree.rs old mode 100644 new mode 100755 index d826fa1889..36c01966dc --- 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,30 @@ 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); - - 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; + if let Some(stay_pos) = bdata.agent.stay_pos { + let distance_from_stay = stay_pos.0.distance_squared(bdata.agent_data.pos.0); + bdata.controller.push_action(ControlAction::Sit); + 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 { + bdata.controller.push_action(ControlAction::Stand); + 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; + } } } } @@ -431,8 +448,8 @@ fn attack_if_owner_hurt(bdata: &mut BehaviorData) -> bool { } else { false }; - - if owner_recently_attacked { + let stay = bdata.agent.stay_pos.is_some(); + if owner_recently_attacked && !stay { bdata.agent_data.attack_target_attacker( bdata.agent, bdata.read_data, diff --git a/server/src/sys/pets.rs b/server/src/sys/pets.rs old mode 100644 new mode 100755 index d444b1b73c..c7c49fa2fa --- a/server/src/sys/pets.rs +++ b/server/src/sys/pets.rs @@ -1,5 +1,5 @@ use common::{ - comp::{Alignment, Pet, PhysicsState, Pos}, + comp::{Agent, Alignment, Pet, PhysicsState, Pos}, terrain::TerrainGrid, uid::IdMaps, }; @@ -16,6 +16,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Pos>, ReadStorage<'a, Alignment>, ReadStorage<'a, Pet>, + ReadStorage<'a, Agent>, ReadStorage<'a, PhysicsState>, Read<'a, IdMaps>, ); @@ -26,7 +27,7 @@ impl<'a> System<'a> for Sys { fn run( _job: &mut Job, - (entities, terrain, mut positions, alignments, pets, physics, id_maps): Self::SystemData, + (entities, terrain, mut positions, alignments, pets, agn, physics, id_maps): Self::SystemData, ) { const LOST_PET_DISTANCE_THRESHOLD: f32 = 200.0; @@ -57,7 +58,8 @@ impl<'a> System<'a> for Sys { .collect(); for (pet_entity, owner_pos) in lost_pets.iter() { - if let Some(mut pet_pos) = positions.get_mut(*pet_entity) { + let stay = agn.get(*pet_entity).and_then(|x| x.stay_pos).is_some(); + if let Some(mut pet_pos) = positions.get_mut(*pet_entity) && !stay{ // Move the pets to their owner's position // TODO: Create a teleportation event to handle this instead of // processing the entity position move here diff --git a/voxygen/src/game_input.rs b/voxygen/src/game_input.rs index 4e5f9f5554..e84a93f88c 100644 --- a/voxygen/src/game_input.rs +++ b/voxygen/src/game_input.rs @@ -81,6 +81,8 @@ pub enum GameInput { ToggleLantern, #[strum(serialize = "gameinput-mount")] Mount, + #[strum(serialize = "gameinput-stayfollow")] + StayFollow, #[strum(serialize = "gameinput-chat")] Chat, #[strum(serialize = "gameinput-command")] diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs old mode 100644 new mode 100755 index e6e3ff6e81..30277e58c3 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1505,6 +1505,7 @@ impl Hud { let is_mounts = ecs.read_storage::>(); let is_riders = ecs.read_storage::>(); let stances = ecs.read_storage::(); + let char_activities = ecs.read_storage::(); let time = ecs.read_resource::