Merge branch 'newclarityex/petting-animals' into 'master'

newclarityex/petting animals

See merge request veloren/veloren!4408
This commit is contained in:
crabman 2024-04-07 15:05:19 +00:00
commit 4ee31ab020
22 changed files with 364 additions and 40 deletions

View File

@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Petting animals tamed by you or someone else!
### Changed ### Changed
- Fireworks and bombs are (again) available from chests (Sahagin and above). - Fireworks and bombs are (again) available from chests (Sahagin and above).

View File

@ -54,6 +54,7 @@ hud-mine-needs_pickaxe = Needs Pickaxe
hud-mine-needs_shovel = Needs Shovel hud-mine-needs_shovel = Needs Shovel
hud-mine-needs_unhandled_case = Needs ??? hud-mine-needs_unhandled_case = Needs ???
hud-talk = Talk hud-talk = Talk
hud-pet = Pet
hud-trade = Trade hud-trade = Trade
hud-mount = Mount hud-mount = Mount
hud-follow = Follow hud-follow = Follow

View File

@ -1419,6 +1419,16 @@ impl Client {
} }
} }
pub fn do_pet(&mut self, target_entity: EcsEntity) {
if self.is_dead() {
return;
}
if let Some(target_uid) = self.state.read_component_copied(target_entity) {
self.control_action(ControlAction::Pet { target_uid });
}
}
pub fn npc_interact(&mut self, npc_entity: EcsEntity, subject: Subject) { pub fn npc_interact(&mut self, npc_entity: EcsEntity, subject: Subject) {
// If we're dead, exit before sending message // If we're dead, exit before sending message
if self.is_dead() { if self.is_dead() {

View File

@ -717,6 +717,7 @@ impl From<&CharacterState> for CharacterAbilityType {
| CharacterState::Climb(_) | CharacterState::Climb(_)
| CharacterState::Sit | CharacterState::Sit
| CharacterState::Dance | CharacterState::Dance
| CharacterState::Pet(_)
| CharacterState::Talk | CharacterState::Talk
| CharacterState::Glide(_) | CharacterState::Glide(_)
| CharacterState::GlideWield(_) | CharacterState::GlideWield(_)

View File

@ -102,6 +102,7 @@ pub enum CharacterState {
Sit, Sit,
Dance, Dance,
Talk, Talk,
Pet(pet::Data),
Glide(glide::Data), Glide(glide::Data),
GlideWield(glide_wield::Data), GlideWield(glide_wield::Data),
/// A stunned state /// A stunned state
@ -315,6 +316,7 @@ impl CharacterState {
CharacterState::Climb(_) CharacterState::Climb(_)
| CharacterState::Equipping(_) | CharacterState::Equipping(_)
| CharacterState::Dance | CharacterState::Dance
| CharacterState::Pet(_)
| CharacterState::Glide(_) | CharacterState::Glide(_)
| CharacterState::GlideWield(_) | CharacterState::GlideWield(_)
| CharacterState::Talk | CharacterState::Talk
@ -508,6 +510,7 @@ impl CharacterState {
CharacterState::Stunned(data) => data.behavior(j, output_events), CharacterState::Stunned(data) => data.behavior(j, output_events),
CharacterState::Sit => sit::Data::behavior(&sit::Data, j, output_events), CharacterState::Sit => sit::Data::behavior(&sit::Data, j, output_events),
CharacterState::Dance => dance::Data::behavior(&dance::Data, j, output_events), CharacterState::Dance => dance::Data::behavior(&dance::Data, j, output_events),
CharacterState::Pet(data) => data.behavior(j, output_events),
CharacterState::BasicBlock(data) => data.behavior(j, output_events), CharacterState::BasicBlock(data) => data.behavior(j, output_events),
CharacterState::Roll(data) => data.behavior(j, output_events), CharacterState::Roll(data) => data.behavior(j, output_events),
CharacterState::Wielding(data) => data.behavior(j, output_events), CharacterState::Wielding(data) => data.behavior(j, output_events),
@ -562,6 +565,7 @@ impl CharacterState {
CharacterState::Dance => { CharacterState::Dance => {
states::dance::Data::handle_event(&dance::Data, j, output_events, action) states::dance::Data::handle_event(&dance::Data, j, output_events, action)
}, },
CharacterState::Pet(data) => data.handle_event(j, output_events, action),
CharacterState::BasicBlock(data) => data.handle_event(j, output_events, action), CharacterState::BasicBlock(data) => data.handle_event(j, output_events, action),
CharacterState::Roll(data) => data.handle_event(j, output_events, action), CharacterState::Roll(data) => data.handle_event(j, output_events, action),
CharacterState::Wielding(data) => data.handle_event(j, output_events, action), CharacterState::Wielding(data) => data.handle_event(j, output_events, action),
@ -618,6 +622,7 @@ impl CharacterState {
CharacterState::Stunned(_) => None, CharacterState::Stunned(_) => None,
CharacterState::Sit => None, CharacterState::Sit => None,
CharacterState::Dance => None, CharacterState::Dance => None,
CharacterState::Pet(_) => None,
CharacterState::BasicBlock(data) => Some(data.static_data.ability_info), CharacterState::BasicBlock(data) => Some(data.static_data.ability_info),
CharacterState::Roll(data) => Some(data.static_data.ability_info), CharacterState::Roll(data) => Some(data.static_data.ability_info),
CharacterState::Wielding(_) => None, CharacterState::Wielding(_) => None,
@ -663,6 +668,7 @@ impl CharacterState {
CharacterState::Stunned(data) => Some(data.stage_section), CharacterState::Stunned(data) => Some(data.stage_section),
CharacterState::Sit => None, CharacterState::Sit => None,
CharacterState::Dance => None, CharacterState::Dance => None,
CharacterState::Pet(_) => None,
CharacterState::BasicBlock(data) => Some(data.stage_section), CharacterState::BasicBlock(data) => Some(data.stage_section),
CharacterState::Roll(data) => Some(data.stage_section), CharacterState::Roll(data) => Some(data.stage_section),
CharacterState::Equipping(_) => Some(StageSection::Buildup), CharacterState::Equipping(_) => Some(StageSection::Buildup),
@ -712,6 +718,7 @@ impl CharacterState {
}), }),
CharacterState::Sit => None, CharacterState::Sit => None,
CharacterState::Dance => None, CharacterState::Dance => None,
CharacterState::Pet(_) => None,
CharacterState::BasicBlock(data) => Some(DurationsInfo { CharacterState::BasicBlock(data) => Some(DurationsInfo {
buildup: Some(data.static_data.buildup_duration), buildup: Some(data.static_data.buildup_duration),
recover: Some(data.static_data.recover_duration), recover: Some(data.static_data.recover_duration),
@ -901,6 +908,7 @@ impl CharacterState {
CharacterState::Stunned(data) => Some(data.timer), CharacterState::Stunned(data) => Some(data.timer),
CharacterState::Sit => None, CharacterState::Sit => None,
CharacterState::Dance => None, CharacterState::Dance => None,
CharacterState::Pet(_) => None,
CharacterState::BasicBlock(data) => Some(data.timer), CharacterState::BasicBlock(data) => Some(data.timer),
CharacterState::Roll(data) => Some(data.timer), CharacterState::Roll(data) => Some(data.timer),
CharacterState::Wielding(_) => None, CharacterState::Wielding(_) => None,
@ -946,6 +954,7 @@ impl CharacterState {
CharacterState::Stunned(_) => None, CharacterState::Stunned(_) => None,
CharacterState::Sit => None, CharacterState::Sit => None,
CharacterState::Dance => None, CharacterState::Dance => None,
CharacterState::Pet(_) => None,
CharacterState::BasicBlock(_) => None, CharacterState::BasicBlock(_) => None,
CharacterState::Roll(_) => None, CharacterState::Roll(_) => None,
CharacterState::Wielding(_) => None, CharacterState::Wielding(_) => None,

View File

@ -181,6 +181,9 @@ pub enum ControlAction {
Unwield, Unwield,
Sit, Sit,
Dance, Dance,
Pet {
target_uid: Uid,
},
Sneak, Sneak,
Stand, Stand,
Talk, Talk,

View File

@ -3,18 +3,18 @@ use crate::{
self, self,
character_state::OutputEvents, character_state::OutputEvents,
item::{tool::AbilityMap, MaterialStatManifest}, item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, ControlAction, ActiveAbilities, Alignment, Beam, Body, CharacterActivity, CharacterState, Combo,
Controller, ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory, ControlAction, Controller, ControllerInputs, Density, Energy, Health, InputAttr, InputKind,
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, Scale, SkillSet, Stance, StateUpdate, Inventory, InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, PreviousPhysCache, Scale,
Stats, Vel, SkillSet, Stance, StateUpdate, Stats, Vel,
}, },
link::Is, link::Is,
mounting::{Rider, VolumeRider}, mounting::{Rider, VolumeRider},
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
terrain::TerrainGrid, terrain::TerrainGrid,
uid::Uid, uid::{IdMaps, Uid},
}; };
use specs::{storage::FlaggedAccessMut, Entity, LazyUpdate}; use specs::{storage::FlaggedAccessMut, Entity, LazyUpdate, Read, ReadStorage};
use vek::*; use vek::*;
pub trait CharacterBehavior { pub trait CharacterBehavior {
@ -50,6 +50,14 @@ pub trait CharacterBehavior {
fn dance(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate { fn dance(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate {
StateUpdate::from(data) StateUpdate::from(data)
} }
fn pet(
&self,
data: &JoinData,
_output_events: &mut OutputEvents,
_target_uid: Uid,
) -> StateUpdate {
StateUpdate::from(data)
}
fn sneak(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate { fn sneak(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate {
StateUpdate::from(data) StateUpdate::from(data)
} }
@ -96,6 +104,7 @@ pub trait CharacterBehavior {
ControlAction::Unwield => self.unwield(data, output_events), ControlAction::Unwield => self.unwield(data, output_events),
ControlAction::Sit => self.sit(data, output_events), ControlAction::Sit => self.sit(data, output_events),
ControlAction::Dance => self.dance(data, output_events), ControlAction::Dance => self.dance(data, output_events),
ControlAction::Pet { target_uid } => self.pet(data, output_events, target_uid),
ControlAction::Sneak => { ControlAction::Sneak => {
if data.mount_data.is_none() && data.volume_mount_data.is_none() { if data.mount_data.is_none() && data.volume_mount_data.is_none() {
self.sneak(data, output_events) self.sneak(data, output_events)
@ -149,6 +158,9 @@ pub struct JoinData<'a> {
pub mount_data: Option<&'a Is<Rider>>, pub mount_data: Option<&'a Is<Rider>>,
pub volume_mount_data: Option<&'a Is<VolumeRider>>, pub volume_mount_data: Option<&'a Is<VolumeRider>>,
pub stance: Option<&'a Stance>, pub stance: Option<&'a Stance>,
pub id_maps: &'a Read<'a, IdMaps>,
pub alignments: &'a ReadStorage<'a, Alignment>,
pub prev_phys_caches: &'a ReadStorage<'a, PreviousPhysCache>,
} }
pub struct JoinStruct<'a> { pub struct JoinStruct<'a> {
@ -179,6 +191,9 @@ pub struct JoinStruct<'a> {
pub mount_data: Option<&'a Is<Rider>>, pub mount_data: Option<&'a Is<Rider>>,
pub volume_mount_data: Option<&'a Is<VolumeRider>>, pub volume_mount_data: Option<&'a Is<VolumeRider>>,
pub stance: Option<&'a Stance>, pub stance: Option<&'a Stance>,
pub id_maps: &'a Read<'a, IdMaps>,
pub alignments: &'a ReadStorage<'a, Alignment>,
pub prev_phys_caches: &'a ReadStorage<'a, PreviousPhysCache>,
} }
impl<'a> JoinData<'a> { impl<'a> JoinData<'a> {
@ -223,6 +238,9 @@ impl<'a> JoinData<'a> {
mount_data: j.mount_data, mount_data: j.mount_data,
volume_mount_data: j.volume_mount_data, volume_mount_data: j.volume_mount_data,
stance: j.stance, stance: j.stance,
id_maps: j.id_maps,
alignments: j.alignments,
prev_phys_caches: j.prev_phys_caches,
} }
} }
} }

View File

@ -5,6 +5,7 @@ use crate::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
idle, idle,
}, },
uid::Uid,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -50,6 +51,12 @@ impl CharacterBehavior for Data {
update update
} }
fn pet(&self, data: &JoinData, _: &mut OutputEvents, target_uid: Uid) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_pet(data, &mut update, target_uid);
update
}
fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
// Try to Fall/Stand up/Move // Try to Fall/Stand up/Move

View File

@ -6,6 +6,7 @@ use crate::{
}, },
resources::Time, resources::Time,
states::behavior::{CharacterBehavior, JoinData}, states::behavior::{CharacterBehavior, JoinData},
uid::Uid,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -96,6 +97,12 @@ impl CharacterBehavior for Data {
update update
} }
fn pet(&self, data: &JoinData, _: &mut OutputEvents, target_uid: Uid) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_pet(data, &mut update, target_uid);
update
}
fn sneak(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { fn sneak(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
update.character = CharacterState::Idle(Data { update.character = CharacterState::Idle(Data {

View File

@ -23,6 +23,7 @@ pub mod idle;
pub mod leap_melee; pub mod leap_melee;
pub mod leap_shockwave; pub mod leap_shockwave;
pub mod music; pub mod music;
pub mod pet;
pub mod rapid_melee; pub mod rapid_melee;
pub mod repeater_ranged; pub mod repeater_ranged;
pub mod riposte_melee; pub mod riposte_melee;

99
common/src/states/pet.rs Normal file
View File

@ -0,0 +1,99 @@
use super::utils::*;
use crate::{
comp::{character_state::OutputEvents, CharacterState, InventoryAction, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
idle,
},
uid::Uid,
util::Dir,
};
use serde::{Deserialize, Serialize};
use vek::Vec3;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StaticData {
pub target_uid: Uid,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
pub static_data: StaticData,
}
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
let target_entity = data.id_maps.uid_entity(self.static_data.target_uid);
let target_pos = target_entity
.and_then(|target_entity| data.prev_phys_caches.get(target_entity))
.and_then(|prev_phys| prev_phys.pos);
let can_pet = target_entity.map_or(false, |target_entity| {
target_pos.zip(data.alignments.get(target_entity)).map_or(
false,
|(target_position, target_alignment)| {
can_perform_pet(*data.pos, target_position, *target_alignment)
},
)
});
// Face target if they have a position.
if let Some(target_pos) = target_pos {
let ori_dir = Dir::from_unnormalized(Vec3::from((target_pos.0 - data.pos.0).xy()));
handle_orientation(data, &mut update, 1.0, ori_dir);
}
leave_stance(data, output_events);
handle_wield(data, &mut update);
handle_jump(data, output_events, &mut update, 1.0);
if !can_pet {
update.character = CharacterState::Idle(idle::Data::default());
}
// Try to Fall/Stand up/Move
if data.physics.on_ground.is_none() || data.inputs.move_dir.magnitude_squared() > 0.0 {
update.character = CharacterState::Idle(idle::Data::default());
}
update
}
fn manipulate_loadout(
&self,
data: &JoinData,
output_events: &mut OutputEvents,
inv_action: InventoryAction,
) -> StateUpdate {
let mut update = StateUpdate::from(data);
handle_manipulate_loadout(data, output_events, &mut update, inv_action);
update
}
fn wield(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_wield(data, &mut update);
update
}
fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_dance(data, &mut update);
update
}
fn sit(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_sit(data, &mut update);
update
}
fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
// Try to Fall/Stand up/Move
update.character = CharacterState::Idle(idle::Data::default());
update
}
}

View File

@ -5,6 +5,7 @@ use crate::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
idle, idle,
}, },
uid::Uid,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -54,6 +55,12 @@ impl CharacterBehavior for Data {
update update
} }
fn pet(&self, data: &JoinData, _: &mut OutputEvents, target_uid: Uid) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_pet(data, &mut update, target_uid);
update
}
fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
// Try to Fall/Stand up/Move // Try to Fall/Stand up/Move

View File

@ -15,15 +15,16 @@ use crate::{
}, },
quadruped_low, quadruped_medium, quadruped_small, ship, quadruped_low, quadruped_medium, quadruped_small, ship,
skills::{Skill, SwimSkill, SKILL_MODIFIERS}, skills::{Skill, SwimSkill, SKILL_MODIFIERS},
theropod, Body, CharacterState, Density, InputAttr, InputKind, InventoryAction, Melee, theropod, Alignment, Body, CharacterState, Density, InputAttr, InputKind, InventoryAction,
StateUpdate, Melee, Pos, StateUpdate,
}, },
consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE}, consts::{FRIC_GROUND, GRAVITY, MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
event::{BuffEvent, ChangeStanceEvent, ComboChangeEvent, InventoryManipEvent, LocalEvent}, event::{BuffEvent, ChangeStanceEvent, ComboChangeEvent, InventoryManipEvent, LocalEvent},
mounting::Volume, mounting::Volume,
outcome::Outcome, outcome::Outcome,
states::{behavior::JoinData, utils::CharacterState::Idle, *}, states::{behavior::JoinData, utils::CharacterState::Idle, *},
terrain::{Block, TerrainGrid, UnlockKind}, terrain::{Block, TerrainGrid, UnlockKind},
uid::Uid,
util::Dir, util::Dir,
vol::ReadVol, vol::ReadVol,
}; };
@ -883,6 +884,34 @@ pub fn attempt_dance(data: &JoinData<'_>, update: &mut StateUpdate) {
} }
} }
pub fn can_perform_pet(position: Pos, target_position: Pos, target_alignment: Alignment) -> bool {
let within_distance = position.0.distance_squared(target_position.0) <= MAX_MOUNT_RANGE.powi(2);
let valid_alignment = matches!(target_alignment, Alignment::Owned(_) | Alignment::Tame);
within_distance && valid_alignment
}
pub fn attempt_pet(data: &JoinData<'_>, update: &mut StateUpdate, target_uid: Uid) {
let can_pet = data
.id_maps
.uid_entity(target_uid)
.and_then(|target_entity| {
data.prev_phys_caches
.get(target_entity)
.and_then(|prev_phys| prev_phys.pos)
.zip(data.alignments.get(target_entity))
})
.map_or(false, |(target_position, target_alignment)| {
can_perform_pet(*data.pos, target_position, *target_alignment)
});
if can_pet && data.physics.on_ground.is_some() && data.body.is_humanoid() {
update.character = CharacterState::Pet(pet::Data {
static_data: pet::StaticData { target_uid },
});
}
}
pub fn attempt_talk(data: &JoinData<'_>, update: &mut StateUpdate) { pub fn attempt_talk(data: &JoinData<'_>, update: &mut StateUpdate) {
if data.physics.on_ground.is_some() { if data.physics.on_ground.is_some() {
update.character = CharacterState::Talk; update.character = CharacterState::Talk;

View File

@ -9,6 +9,7 @@ use crate::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
idle, idle,
}, },
uid::Uid,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -104,6 +105,12 @@ impl CharacterBehavior for Data {
update update
} }
fn pet(&self, data: &JoinData, _: &mut OutputEvents, target_uid: Uid) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_pet(data, &mut update, target_uid);
update
}
fn sneak(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { fn sneak(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
if data.physics.on_ground.is_some() && data.body.is_humanoid() { if data.physics.on_ground.is_some() && data.body.is_humanoid() {

View File

@ -9,7 +9,7 @@ use common::{
inventory::item::{tool::AbilityMap, MaterialStatManifest}, inventory::item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, Controller, Density, ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, Controller, Density,
Energy, Health, Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, Energy, Health, Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos,
Scale, SkillSet, Stance, StateUpdate, Stats, Vel, PreviousPhysCache, Scale, SkillSet, Stance, StateUpdate, Stats, Vel,
}, },
event::{self, EventBus, KnockbackEvent, LocalEvent}, event::{self, EventBus, KnockbackEvent, LocalEvent},
link::Is, link::Is,
@ -21,7 +21,7 @@ use common::{
idle, idle,
}, },
terrain::TerrainGrid, terrain::TerrainGrid,
uid::Uid, uid::{IdMaps, Uid},
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
@ -53,6 +53,7 @@ pub struct ReadData<'a> {
terrain: ReadExpect<'a, TerrainGrid>, terrain: ReadExpect<'a, TerrainGrid>,
inventories: ReadStorage<'a, Inventory>, inventories: ReadStorage<'a, Inventory>,
stances: ReadStorage<'a, Stance>, stances: ReadStorage<'a, Stance>,
prev_phys_caches: ReadStorage<'a, PreviousPhysCache>,
} }
/// ## Character Behavior System /// ## Character Behavior System
@ -74,6 +75,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
WriteStorage<'a, Poise>, WriteStorage<'a, Poise>,
Read<'a, EventBus<Outcome>>, Read<'a, EventBus<Outcome>>,
Read<'a, IdMaps>,
); );
const NAME: &'static str = "character_behavior"; const NAME: &'static str = "character_behavior";
@ -94,6 +96,7 @@ impl<'a> System<'a> for Sys {
mut controllers, mut controllers,
mut poises, mut poises,
outcomes, outcomes,
id_maps,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut local_emitter = read_data.local_bus.emitter(); let mut local_emitter = read_data.local_bus.emitter();
@ -217,6 +220,9 @@ impl<'a> System<'a> for Sys {
mount_data: read_data.is_riders.get(entity), mount_data: read_data.is_riders.get(entity),
volume_mount_data: read_data.is_volume_riders.get(entity), volume_mount_data: read_data.is_volume_riders.get(entity),
stance: read_data.stances.get(entity), stance: read_data.stances.get(entity),
id_maps: &id_maps,
alignments: &read_data.alignments,
prev_phys_caches: &read_data.prev_phys_caches,
}; };
for action in actions { for action in actions {

View File

@ -131,6 +131,7 @@ impl<'a> System<'a> for Sys {
CharacterState::Idle(_) CharacterState::Idle(_)
| CharacterState::Talk | CharacterState::Talk
| CharacterState::Dance | CharacterState::Dance
| CharacterState::Pet(_)
| CharacterState::Skate(_) | CharacterState::Skate(_)
| CharacterState::Glide(_) | CharacterState::Glide(_)
| CharacterState::GlideWield(_) | CharacterState::GlideWield(_)

View File

@ -445,6 +445,28 @@ impl<'a> AgentData<'a> {
None => {}, None => {},
} }
let owner_uid = self.alignment.and_then(|alignment| match alignment {
Alignment::Owned(owner_uid) => Some(owner_uid),
_ => None,
});
let owner = owner_uid.and_then(|owner_uid| get_entity_by_id(*owner_uid, read_data));
let is_being_pet = owner
.and_then(|owner| read_data.char_states.get(owner))
.map_or(false, |char_state| match char_state {
CharacterState::Pet(petting_data) => {
petting_data.static_data.target_uid == *self.uid
},
_ => false,
});
let is_in_range = owner
.and_then(|owner| read_data.positions.get(owner))
.map_or(false, |pos| {
pos.0.distance_squared(self.pos.0) < MAX_MOUNT_RANGE.powi(2)
});
// Idle NPCs should try to jump on the shoulders of their owner, sometimes. // Idle NPCs should try to jump on the shoulders of their owner, sometimes.
if read_data.is_riders.contains(*self.entity) { if read_data.is_riders.contains(*self.entity) {
if rng.gen_bool(0.0001) { if rng.gen_bool(0.0001) {
@ -452,10 +474,9 @@ impl<'a> AgentData<'a> {
} else { } else {
break 'activity; break 'activity;
} }
} else if let Some(Alignment::Owned(owner_uid)) = self.alignment } else if let Some(owner_uid) = owner_uid
&& let Some(owner) = get_entity_by_id(*owner_uid, read_data) && is_in_range
&& let Some(pos) = read_data.positions.get(owner) && !is_being_pet
&& pos.0.distance_squared(self.pos.0) < MAX_MOUNT_RANGE.powi(2)
&& rng.gen_bool(0.01) && rng.gen_bool(0.01)
{ {
controller.push_event(ControlEvent::Mount(*owner_uid)); controller.push_event(ControlEvent::Mount(*owner_uid));

View File

@ -20,6 +20,7 @@ pub mod jump;
pub mod leapmelee; pub mod leapmelee;
pub mod mount; pub mod mount;
pub mod music; pub mod music;
pub mod pet;
pub mod rapidmelee; pub mod rapidmelee;
pub mod repeater; pub mod repeater;
pub mod ripostemelee; pub mod ripostemelee;
@ -51,10 +52,11 @@ pub use self::{
dance::DanceAnimation, dash::DashAnimation, divemelee::DiveMeleeAnimation, dance::DanceAnimation, dash::DashAnimation, divemelee::DiveMeleeAnimation,
equip::EquipAnimation, finishermelee::FinisherMeleeAnimation, glidewield::GlideWieldAnimation, equip::EquipAnimation, finishermelee::FinisherMeleeAnimation, glidewield::GlideWieldAnimation,
gliding::GlidingAnimation, idle::IdleAnimation, jump::JumpAnimation, leapmelee::LeapAnimation, gliding::GlidingAnimation, idle::IdleAnimation, jump::JumpAnimation, leapmelee::LeapAnimation,
mount::MountAnimation, music::MusicAnimation, rapidmelee::RapidMeleeAnimation, mount::MountAnimation, music::MusicAnimation, pet::PetAnimation,
repeater::RepeaterAnimation, ripostemelee::RiposteMeleeAnimation, roll::RollAnimation, rapidmelee::RapidMeleeAnimation, repeater::RepeaterAnimation,
run::RunAnimation, selfbuff::SelfBuffAnimation, shockwave::ShockwaveAnimation, ripostemelee::RiposteMeleeAnimation, roll::RollAnimation, run::RunAnimation,
shoot::ShootAnimation, sit::SitAnimation, sleep::SleepAnimation, sneak::SneakAnimation, selfbuff::SelfBuffAnimation, shockwave::ShockwaveAnimation, shoot::ShootAnimation,
sit::SitAnimation, sleep::SleepAnimation, sneak::SneakAnimation,
sneakequip::SneakEquipAnimation, sneakwield::SneakWieldAnimation, sneakequip::SneakEquipAnimation, sneakwield::SneakWieldAnimation,
staggered::StaggeredAnimation, stand::StandAnimation, steer::SteerAnimation, staggered::StaggeredAnimation, stand::StandAnimation, steer::SteerAnimation,
stunned::StunnedAnimation, swim::SwimAnimation, swimwield::SwimWieldAnimation, stunned::StunnedAnimation, swim::SwimAnimation, swimwield::SwimWieldAnimation,

View File

@ -0,0 +1,48 @@
use super::{
super::{vek::*, Animation},
CharacterSkeleton, SkeletonAttr,
};
use std::f32::consts::PI;
pub struct PetAnimation;
impl Animation for PetAnimation {
type Dependency<'a> = (Vec3<f32>, Option<vek::Vec3<f32>>, f32);
type Skeleton = CharacterSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"character_pet\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_pet")]
fn update_skeleton_inner(
skeleton: &Self::Skeleton,
(pos, target_pos, _global_time): Self::Dependency<'_>,
anim_time: f32,
_rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let fast = (anim_time * 3.0).sin();
let fast_offset = (anim_time * 3.0 + PI * 0.5).sin();
let z_diff = target_pos.map_or(0., |target_pos| target_pos.z - pos.z);
// Tilt head down by 10 deg
next.head.orientation = Quaternion::rotation_x(-1. * PI / 2. / 9.);
// Lift hand up and out, slight hand position change depending on height
next.hand_r.position = Vec3::new(
s_a.hand.0 + -2. * fast_offset,
s_a.hand.1 + 8.0,
s_a.hand.2 + 4.0 + 1. * fast + z_diff,
);
// Raise arm 90deg then up and down
next.hand_r.orientation =
Quaternion::rotation_x(PI / 2. + fast * 0.15).rotated_z(fast_offset * 0.5);
next
}
}

View File

@ -2465,39 +2465,49 @@ impl Hud {
] ]
}, },
Some(comp::Alignment::Owned(owner)) Some(comp::Alignment::Owned(owner))
if Some(*owner) == client.uid() if dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2) =>
&& dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2) =>
{ {
let mut options = Vec::new(); let mut options = Vec::new();
if is_mount.is_none() { if is_mount.is_none() {
options.push(( if Some(*owner) == client.uid() {
GameInput::Trade,
i18n.get_msg("hud-trade").to_string(),
));
if !client.is_riding()
&& is_mountable(body, bodies.get(client.entity()))
{
options.push(( options.push((
GameInput::Mount, GameInput::Trade,
i18n.get_msg("hud-mount").to_string(), i18n.get_msg("hud-trade").to_string(),
));
if !client.is_riding()
&& is_mountable(body, bodies.get(client.entity()))
{
options.push((
GameInput::Mount,
i18n.get_msg("hud-mount").to_string(),
));
}
let is_staying = character_activity
.map_or(false, |activity| activity.is_pet_staying);
options.push((
GameInput::StayFollow,
i18n.get_msg(if is_staying {
"hud-follow"
} else {
"hud-stay"
})
.to_string(),
)); ));
} }
let is_staying = character_activity // Anyone can pet a tamed animal
.map_or(false, |activity| activity.is_pet_staying);
options.push(( options.push((
GameInput::StayFollow, GameInput::Interact,
i18n.get_msg(if is_staying { i18n.get_msg("hud-pet").to_string(),
"hud-follow"
} else {
"hud-stay"
})
.to_string(),
)); ));
} }
options options
}, },
Some(comp::Alignment::Tame) => {
vec![(GameInput::Interact, i18n.get_msg("hud-pet").to_string())]
},
_ => Vec::new(), _ => Vec::new(),
}, },
&time, &time,

View File

@ -2112,6 +2112,22 @@ impl FigureMgr {
skeleton_attr, skeleton_attr,
) )
}, },
CharacterState::Pet(s) => {
let target_entity = id_maps.uid_entity(s.static_data.target_uid);
let target_pos = target_entity.and_then(|target_entity| {
ecs.read_component::<Pos>()
.get(target_entity)
.map(|pos| pos.0)
});
anim::character::PetAnimation::update_skeleton(
&target_base,
(pos.0, target_pos, time),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::Music(s) => { CharacterState::Music(s) => {
anim::character::MusicAnimation::update_skeleton( anim::character::MusicAnimation::update_skeleton(
&target_base, &target_base,

View File

@ -28,6 +28,7 @@ use common::{
mounting::{Mount, VolumePos}, mounting::{Mount, VolumePos},
outcome::Outcome, outcome::Outcome,
recipe, recipe,
states::utils::can_perform_pet,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
trade::TradeResult, trade::TradeResult,
util::{Dir, Plane}, util::{Dir, Plane},
@ -1074,6 +1075,22 @@ impl PlayState for SessionState {
.state() .state()
.read_component_cloned::<comp::Body>(*entity); .read_component_cloned::<comp::Body>(*entity);
let pettable = client
.state()
.read_component_cloned::<comp::Pos>(*entity)
.zip(client.state().read_component_cloned::<comp::Alignment>(*entity))
.zip(client.state().read_component_cloned::<comp::Pos>(client.entity()))
.map_or(
false,
|((target_position, target_alignment), client_position)| {
can_perform_pet(
client_position,
target_position,
target_alignment,
)
},
);
if client if client
.state() .state()
.ecs() .ecs()
@ -1097,6 +1114,8 @@ impl PlayState for SessionState {
.flatten() .flatten()
{ {
client.activate_portal(portal_uid); client.activate_portal(portal_uid);
} else if pettable {
client.do_pet(*entity);
} else { } else {
client.npc_interact(*entity, Subject::Regular); client.npc_interact(*entity, Subject::Regular);
} }