From e611d695b1fbfedc761e83b44d69425d872b9239 Mon Sep 17 00:00:00 2001 From: TelepathicWalrus Date: Sun, 30 Apr 2023 16:45:21 +0100 Subject: [PATCH] Add stay/follow option for pets --- assets/voxygen/i18n/en/gameinput.ftl | 1 + assets/voxygen/i18n/en/hud/misc.ftl | 2 ++ client/src/lib.rs | 6 +++++ common/net/src/synced_components.rs | 8 ++++++- common/src/comp/controller.rs | 1 + common/src/comp/pet.rs | 33 ++++++++++++++++++++++++++- common/src/event.rs | 1 + common/state/src/state.rs | 1 + common/systems/src/controller.rs | 7 ++++++ server/agent/src/data.rs | 4 +++- server/src/events/interaction.rs | 14 +++++++++++- server/src/events/mod.rs | 3 ++- server/src/sys/agent.rs | 5 ++++ server/src/sys/agent/behavior_tree.rs | 8 +++---- server/src/sys/pets.rs | 12 ++++++---- voxygen/src/game_input.rs | 2 ++ voxygen/src/hud/mod.rs | 31 +++++++++++++++++++++++-- voxygen/src/session/mod.rs | 32 ++++++++++++++++++++++++++ voxygen/src/settings/control.rs | 1 + voxygen/src/settings/gamepad.rs | 2 ++ 20 files changed, 158 insertions(+), 16 deletions(-) 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..4148083bb9 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-stay = Stay +hud-follow = Follow hud-sit = Sit hud-steer = Steer diff --git a/client/src/lib.rs b/client/src/lib.rs index c19d73529b..6e49c52436 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1477,6 +1477,12 @@ impl Client { pub fn unmount(&mut self) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Unmount)); } + pub fn toggle_stay(&mut self, entity: EcsEntity){ + if let Some(uid) = self.state.read_component_copied(entity) { + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::ToggleStay(uid))); + } + } + pub fn respawn(&mut self) { if self .state diff --git a/common/net/src/synced_components.rs b/common/net/src/synced_components.rs index f83f9c6e15..0661a96f23 100644 --- a/common/net/src/synced_components.rs +++ b/common/net/src/synced_components.rs @@ -46,6 +46,7 @@ macro_rules! synced_components { beam_segment: BeamSegment, alignment: Alignment, stance: Stance, + pet_state: PetStates, // TODO: change this to `SyncFrom::ClientEntity` and sync the bare minimum // from other entities (e.g. just keys needed to show appearance // based on their loadout). Also, it looks like this actually has @@ -75,7 +76,7 @@ macro_rules! reexport_comps { pub use common::comp::*; use common::link::Is; use common::mounting::{Mount, Rider, VolumeRider}; - + use common::comp::pet::PetState; // 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!`. @@ -85,6 +86,7 @@ macro_rules! reexport_comps { pub type IsMount = Is; pub type IsRider = Is; pub type IsVolumeRider = Is; + pub type PetStates = PetState; } // Re-export all the component types. So that uses of `synced_components!` outside this @@ -105,7 +107,11 @@ synced_components!(reexport_comps); use crate::sync::{NetSync, SyncFrom}; + // These are synced from any entity within range. +impl NetSync for PetStates { + const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; +} impl NetSync for Body { const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index f26396f176..4d738f992a 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, + ToggleStay(Uid), InventoryEvent(InventoryEvent), GroupManip(GroupManip), RemoveBuff(BuffKind), diff --git a/common/src/comp/pet.rs b/common/src/comp/pet.rs index e954eff273..f2a701393b 100644 --- a/common/src/comp/pet.rs +++ b/common/src/comp/pet.rs @@ -1,7 +1,8 @@ use crate::comp::{body::Body, phys::Mass, quadruped_medium, quadruped_small}; use crossbeam_utils::atomic::AtomicCell; -use specs::Component; +use specs::{Component, DerefFlaggedStorage}; use std::{num::NonZeroU64, sync::Arc}; +use serde::{Deserialize, Serialize}; pub type PetId = AtomicCell>; @@ -107,3 +108,33 @@ impl Component for Pet { // isn't worth using `DenseVecStorage` here. type Storage = specs::VecStorage; } + + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum StayFollow{ + Stay, + Follow, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PetState{ + pub stay: bool, +} + +impl Default for PetState { + fn default() -> Self { + Self { + stay: false, + } + } +} + +impl PetState { + pub fn get_state(&self) -> bool{ + self.stay + } +} + +impl Component for PetState { + type Storage = DerefFlaggedStorage; +} diff --git a/common/src/event.rs b/common/src/event.rs index cb2f31d864..9af01e45e2 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), + ToggleStay(EcsEntity), Possess(Uid, Uid), /// Inserts default components for a character when loading into the game InitCharacterData { diff --git a/common/state/src/state.rs b/common/state/src/state.rs index 7771d54f00..39668f0b71 100644 --- a/common/state/src/state.rs +++ b/common/state/src/state.rs @@ -203,6 +203,7 @@ impl State { ecs.register::>(); ecs.register::>(); ecs.register::>(); + ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/common/systems/src/controller.rs b/common/systems/src/controller.rs index 6539c62c63..5ca0a70f07 100644 --- a/common/systems/src/controller.rs +++ b/common/systems/src/controller.rs @@ -63,6 +63,13 @@ impl<'a> System<'a> for Sys { } } }, + ControlEvent::ToggleStay(pet_uid) => { + if let Some(pet_entity) = read_data + .uid_allocator + .retrieve_entity_internal(pet_uid.id()){ + server_emitter.emit(ServerEvent::ToggleStay(pet_entity)); + } + } ControlEvent::RemoveBuff(buff_id) => { server_emitter.emit(ServerEvent::Buff { entity, diff --git a/server/agent/src/data.rs b/server/agent/src/data.rs index a5537684e9..f43f6a6c4c 100644 --- a/server/agent/src/data.rs +++ b/server/agent/src/data.rs @@ -14,7 +14,7 @@ use common::{ }, ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory, LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Presence, Scale, SkillSet, Stance, - Stats, Vel, + Stats, Vel, pet::PetState, }, consts::GRAVITY, link::Is, @@ -52,6 +52,7 @@ pub struct AgentData<'a> { pub light_emitter: Option<&'a LightEmitter>, pub glider_equipped: bool, pub is_gliding: bool, + pub is_stay: bool, pub health: Option<&'a Health>, pub char_state: &'a CharacterState, pub active_abilities: &'a ActiveAbilities, @@ -347,6 +348,7 @@ pub struct ReadData<'a> { pub is_mounts: ReadStorage<'a, Is>, pub is_riders: ReadStorage<'a, Is>, pub is_volume_riders: ReadStorage<'a, Is>, + pub pet_states: ReadStorage<'a, PetState>, pub time_of_day: Read<'a, TimeOfDay>, pub light_emitter: ReadStorage<'a, LightEmitter>, #[cfg(feature = "worldgen")] diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 1a4661ee94..395520360f 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -10,7 +10,7 @@ use common::{ inventory::slot::EquipSlot, item::{flatten_counted_items, MaterialStatManifest}, loot_owner::LootOwnerKind, - pet::is_mountable, + pet::{is_mountable, PetState}, tool::{AbilityMap, ToolKind}, Inventory, LootOwner, Pos, SkillGroupKind, }, @@ -202,6 +202,18 @@ pub fn handle_unmount(server: &mut Server, rider: EcsEntity) { state.ecs().write_storage::>().remove(rider); } +pub fn handle_toggle_stay(server: &mut Server, pet: EcsEntity){ + let state = server.state_mut(); + if state.ecs() + .read_storage::() + .get(pet) + .map_or(false, |s| s.stay){ + let _ = state.ecs().write_storage::().insert(pet, PetState { stay: false }); + } else { + let _ = state.ecs().write_storage::().insert(pet, PetState { stay: true }); + } +} + 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..1e3834a825 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_sound, handle_unmount, handle_toggle_stay, }; use inventory_manip::handle_inventory; use invite::{handle_invite, handle_invite_response}; @@ -144,6 +144,7 @@ impl Server { handle_mount_volume(self, mounter, volume) }, ServerEvent::Unmount(mounter) => handle_unmount(self, mounter), + ServerEvent::ToggleStay(pet) => handle_toggle_stay(self, pet), 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 index 35b879eb01..d7ba718d92 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -71,6 +71,7 @@ impl<'a> System<'a> for Sys { !&read_data.is_mounts, read_data.is_riders.maybe(), read_data.is_volume_riders.maybe(), + read_data.pet_states.maybe(), ) .par_join() .for_each_init( @@ -96,6 +97,7 @@ impl<'a> System<'a> for Sys { _, is_rider, is_volume_rider, + pet_state, )| { let mut event_emitter = event_bus.emitter(); let mut rng = thread_rng(); @@ -158,6 +160,8 @@ 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 is_gliding = matches!( read_data.char_states.get(entity), @@ -229,6 +233,7 @@ impl<'a> System<'a> for Sys { light_emitter, glider_equipped, is_gliding, + is_stay, 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 d826fa1889..470c7b6987 100644 --- a/server/src/sys/agent/behavior_tree.rs +++ b/server/src/sys/agent/behavior_tree.rs @@ -407,8 +407,8 @@ 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) { + let stay = bdata.agent_data.is_stay; + if dist_sqrd > (MAX_PATROL_DIST * bdata.agent.psyche.idle_wander_factor).powi(2) && !stay{ bdata .agent_data .follow(bdata.agent, bdata.controller, bdata.read_data, tgt_pos); @@ -431,8 +431,8 @@ fn attack_if_owner_hurt(bdata: &mut BehaviorData) -> bool { } else { false }; - - if owner_recently_attacked { + let stay = bdata.agent_data.is_stay; + 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 index d444b1b73c..f0663beae0 100644 --- a/server/src/sys/pets.rs +++ b/server/src/sys/pets.rs @@ -1,5 +1,5 @@ use common::{ - comp::{Alignment, Pet, PhysicsState, Pos}, + comp::{Alignment, Pet, PhysicsState, Pos, pet::PetState}, 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, PetState>, ReadStorage<'a, PhysicsState>, Read<'a, IdMaps>, ); @@ -26,10 +27,10 @@ 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, pet_state, physics, id_maps): Self::SystemData, ) { const LOST_PET_DISTANCE_THRESHOLD: f32 = 200.0; - + // Find pets that are too far away from their owner let lost_pets: Vec<(Entity, Pos)> = (&entities, &positions, &alignments, &pets) .join() @@ -55,9 +56,10 @@ impl<'a> System<'a> for Sys { }) .map(|(entity, owner_pos, _, _)| (entity, *owner_pos)) .collect(); - + for (pet_entity, owner_pos) in lost_pets.iter() { - if let Some(mut pet_pos) = positions.get_mut(*pet_entity) { + let stay = pet_state.get(*pet_entity).map_or(false, |f| f.stay); + 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 index e6e3ff6e81..d6f379c229 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1504,6 +1504,7 @@ impl Hud { let alignments = ecs.read_storage::(); let is_mounts = ecs.read_storage::>(); let is_riders = ecs.read_storage::>(); + let is_stay = ecs.read_storage::(); let stances = ecs.read_storage::(); let time = ecs.read_resource::