mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'newclarityex/petting-animals' into 'master'
newclarityex/petting animals See merge request veloren/veloren!4408
This commit is contained in:
commit
4ee31ab020
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Petting animals tamed by you or someone else!
|
||||
|
||||
### Changed
|
||||
|
||||
- Fireworks and bombs are (again) available from chests (Sahagin and above).
|
||||
|
@ -54,6 +54,7 @@ hud-mine-needs_pickaxe = Needs Pickaxe
|
||||
hud-mine-needs_shovel = Needs Shovel
|
||||
hud-mine-needs_unhandled_case = Needs ???
|
||||
hud-talk = Talk
|
||||
hud-pet = Pet
|
||||
hud-trade = Trade
|
||||
hud-mount = Mount
|
||||
hud-follow = Follow
|
||||
|
@ -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) {
|
||||
// If we're dead, exit before sending message
|
||||
if self.is_dead() {
|
||||
|
@ -717,6 +717,7 @@ impl From<&CharacterState> for CharacterAbilityType {
|
||||
| CharacterState::Climb(_)
|
||||
| CharacterState::Sit
|
||||
| CharacterState::Dance
|
||||
| CharacterState::Pet(_)
|
||||
| CharacterState::Talk
|
||||
| CharacterState::Glide(_)
|
||||
| CharacterState::GlideWield(_)
|
||||
|
@ -102,6 +102,7 @@ pub enum CharacterState {
|
||||
Sit,
|
||||
Dance,
|
||||
Talk,
|
||||
Pet(pet::Data),
|
||||
Glide(glide::Data),
|
||||
GlideWield(glide_wield::Data),
|
||||
/// A stunned state
|
||||
@ -315,6 +316,7 @@ impl CharacterState {
|
||||
CharacterState::Climb(_)
|
||||
| CharacterState::Equipping(_)
|
||||
| CharacterState::Dance
|
||||
| CharacterState::Pet(_)
|
||||
| CharacterState::Glide(_)
|
||||
| CharacterState::GlideWield(_)
|
||||
| CharacterState::Talk
|
||||
@ -508,6 +510,7 @@ impl CharacterState {
|
||||
CharacterState::Stunned(data) => data.behavior(j, output_events),
|
||||
CharacterState::Sit => sit::Data::behavior(&sit::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::Roll(data) => data.behavior(j, output_events),
|
||||
CharacterState::Wielding(data) => data.behavior(j, output_events),
|
||||
@ -562,6 +565,7 @@ impl CharacterState {
|
||||
CharacterState::Dance => {
|
||||
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::Roll(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::Sit => None,
|
||||
CharacterState::Dance => None,
|
||||
CharacterState::Pet(_) => None,
|
||||
CharacterState::BasicBlock(data) => Some(data.static_data.ability_info),
|
||||
CharacterState::Roll(data) => Some(data.static_data.ability_info),
|
||||
CharacterState::Wielding(_) => None,
|
||||
@ -663,6 +668,7 @@ impl CharacterState {
|
||||
CharacterState::Stunned(data) => Some(data.stage_section),
|
||||
CharacterState::Sit => None,
|
||||
CharacterState::Dance => None,
|
||||
CharacterState::Pet(_) => None,
|
||||
CharacterState::BasicBlock(data) => Some(data.stage_section),
|
||||
CharacterState::Roll(data) => Some(data.stage_section),
|
||||
CharacterState::Equipping(_) => Some(StageSection::Buildup),
|
||||
@ -712,6 +718,7 @@ impl CharacterState {
|
||||
}),
|
||||
CharacterState::Sit => None,
|
||||
CharacterState::Dance => None,
|
||||
CharacterState::Pet(_) => None,
|
||||
CharacterState::BasicBlock(data) => Some(DurationsInfo {
|
||||
buildup: Some(data.static_data.buildup_duration),
|
||||
recover: Some(data.static_data.recover_duration),
|
||||
@ -901,6 +908,7 @@ impl CharacterState {
|
||||
CharacterState::Stunned(data) => Some(data.timer),
|
||||
CharacterState::Sit => None,
|
||||
CharacterState::Dance => None,
|
||||
CharacterState::Pet(_) => None,
|
||||
CharacterState::BasicBlock(data) => Some(data.timer),
|
||||
CharacterState::Roll(data) => Some(data.timer),
|
||||
CharacterState::Wielding(_) => None,
|
||||
@ -946,6 +954,7 @@ impl CharacterState {
|
||||
CharacterState::Stunned(_) => None,
|
||||
CharacterState::Sit => None,
|
||||
CharacterState::Dance => None,
|
||||
CharacterState::Pet(_) => None,
|
||||
CharacterState::BasicBlock(_) => None,
|
||||
CharacterState::Roll(_) => None,
|
||||
CharacterState::Wielding(_) => None,
|
||||
|
@ -181,6 +181,9 @@ pub enum ControlAction {
|
||||
Unwield,
|
||||
Sit,
|
||||
Dance,
|
||||
Pet {
|
||||
target_uid: Uid,
|
||||
},
|
||||
Sneak,
|
||||
Stand,
|
||||
Talk,
|
||||
|
@ -3,18 +3,18 @@ use crate::{
|
||||
self,
|
||||
character_state::OutputEvents,
|
||||
item::{tool::AbilityMap, MaterialStatManifest},
|
||||
ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, ControlAction,
|
||||
Controller, ControllerInputs, Density, Energy, Health, InputAttr, InputKind, Inventory,
|
||||
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, Scale, SkillSet, Stance, StateUpdate,
|
||||
Stats, Vel,
|
||||
ActiveAbilities, Alignment, Beam, Body, CharacterActivity, CharacterState, Combo,
|
||||
ControlAction, Controller, ControllerInputs, Density, Energy, Health, InputAttr, InputKind,
|
||||
Inventory, InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, PreviousPhysCache, Scale,
|
||||
SkillSet, Stance, StateUpdate, Stats, Vel,
|
||||
},
|
||||
link::Is,
|
||||
mounting::{Rider, VolumeRider},
|
||||
resources::{DeltaTime, Time},
|
||||
terrain::TerrainGrid,
|
||||
uid::Uid,
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use specs::{storage::FlaggedAccessMut, Entity, LazyUpdate};
|
||||
use specs::{storage::FlaggedAccessMut, Entity, LazyUpdate, Read, ReadStorage};
|
||||
use vek::*;
|
||||
|
||||
pub trait CharacterBehavior {
|
||||
@ -50,6 +50,14 @@ pub trait CharacterBehavior {
|
||||
fn dance(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate {
|
||||
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 {
|
||||
StateUpdate::from(data)
|
||||
}
|
||||
@ -96,6 +104,7 @@ pub trait CharacterBehavior {
|
||||
ControlAction::Unwield => self.unwield(data, output_events),
|
||||
ControlAction::Sit => self.sit(data, output_events),
|
||||
ControlAction::Dance => self.dance(data, output_events),
|
||||
ControlAction::Pet { target_uid } => self.pet(data, output_events, target_uid),
|
||||
ControlAction::Sneak => {
|
||||
if data.mount_data.is_none() && data.volume_mount_data.is_none() {
|
||||
self.sneak(data, output_events)
|
||||
@ -149,6 +158,9 @@ pub struct JoinData<'a> {
|
||||
pub mount_data: Option<&'a Is<Rider>>,
|
||||
pub volume_mount_data: Option<&'a Is<VolumeRider>>,
|
||||
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> {
|
||||
@ -179,6 +191,9 @@ pub struct JoinStruct<'a> {
|
||||
pub mount_data: Option<&'a Is<Rider>>,
|
||||
pub volume_mount_data: Option<&'a Is<VolumeRider>>,
|
||||
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> {
|
||||
@ -223,6 +238,9 @@ impl<'a> JoinData<'a> {
|
||||
mount_data: j.mount_data,
|
||||
volume_mount_data: j.volume_mount_data,
|
||||
stance: j.stance,
|
||||
id_maps: j.id_maps,
|
||||
alignments: j.alignments,
|
||||
prev_phys_caches: j.prev_phys_caches,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use crate::{
|
||||
behavior::{CharacterBehavior, JoinData},
|
||||
idle,
|
||||
},
|
||||
uid::Uid,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -50,6 +51,12 @@ impl CharacterBehavior for Data {
|
||||
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 {
|
||||
let mut update = StateUpdate::from(data);
|
||||
// Try to Fall/Stand up/Move
|
||||
|
@ -6,6 +6,7 @@ use crate::{
|
||||
},
|
||||
resources::Time,
|
||||
states::behavior::{CharacterBehavior, JoinData},
|
||||
uid::Uid,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -96,6 +97,12 @@ impl CharacterBehavior for Data {
|
||||
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 {
|
||||
let mut update = StateUpdate::from(data);
|
||||
update.character = CharacterState::Idle(Data {
|
||||
|
@ -23,6 +23,7 @@ pub mod idle;
|
||||
pub mod leap_melee;
|
||||
pub mod leap_shockwave;
|
||||
pub mod music;
|
||||
pub mod pet;
|
||||
pub mod rapid_melee;
|
||||
pub mod repeater_ranged;
|
||||
pub mod riposte_melee;
|
||||
|
99
common/src/states/pet.rs
Normal file
99
common/src/states/pet.rs
Normal 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
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ use crate::{
|
||||
behavior::{CharacterBehavior, JoinData},
|
||||
idle,
|
||||
},
|
||||
uid::Uid,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -54,6 +55,12 @@ impl CharacterBehavior for Data {
|
||||
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 {
|
||||
let mut update = StateUpdate::from(data);
|
||||
// Try to Fall/Stand up/Move
|
||||
|
@ -15,15 +15,16 @@ use crate::{
|
||||
},
|
||||
quadruped_low, quadruped_medium, quadruped_small, ship,
|
||||
skills::{Skill, SwimSkill, SKILL_MODIFIERS},
|
||||
theropod, Body, CharacterState, Density, InputAttr, InputKind, InventoryAction, Melee,
|
||||
StateUpdate,
|
||||
theropod, Alignment, Body, CharacterState, Density, InputAttr, InputKind, InventoryAction,
|
||||
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},
|
||||
mounting::Volume,
|
||||
outcome::Outcome,
|
||||
states::{behavior::JoinData, utils::CharacterState::Idle, *},
|
||||
terrain::{Block, TerrainGrid, UnlockKind},
|
||||
uid::Uid,
|
||||
util::Dir,
|
||||
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) {
|
||||
if data.physics.on_ground.is_some() {
|
||||
update.character = CharacterState::Talk;
|
||||
|
@ -9,6 +9,7 @@ use crate::{
|
||||
behavior::{CharacterBehavior, JoinData},
|
||||
idle,
|
||||
},
|
||||
uid::Uid,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -104,6 +105,12 @@ impl CharacterBehavior for Data {
|
||||
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 {
|
||||
let mut update = StateUpdate::from(data);
|
||||
if data.physics.on_ground.is_some() && data.body.is_humanoid() {
|
||||
|
@ -9,7 +9,7 @@ use common::{
|
||||
inventory::item::{tool::AbilityMap, MaterialStatManifest},
|
||||
ActiveAbilities, Beam, Body, CharacterActivity, CharacterState, Combo, Controller, Density,
|
||||
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},
|
||||
link::Is,
|
||||
@ -21,7 +21,7 @@ use common::{
|
||||
idle,
|
||||
},
|
||||
terrain::TerrainGrid,
|
||||
uid::Uid,
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
|
||||
@ -53,6 +53,7 @@ pub struct ReadData<'a> {
|
||||
terrain: ReadExpect<'a, TerrainGrid>,
|
||||
inventories: ReadStorage<'a, Inventory>,
|
||||
stances: ReadStorage<'a, Stance>,
|
||||
prev_phys_caches: ReadStorage<'a, PreviousPhysCache>,
|
||||
}
|
||||
|
||||
/// ## Character Behavior System
|
||||
@ -74,6 +75,7 @@ impl<'a> System<'a> for Sys {
|
||||
WriteStorage<'a, Controller>,
|
||||
WriteStorage<'a, Poise>,
|
||||
Read<'a, EventBus<Outcome>>,
|
||||
Read<'a, IdMaps>,
|
||||
);
|
||||
|
||||
const NAME: &'static str = "character_behavior";
|
||||
@ -94,6 +96,7 @@ impl<'a> System<'a> for Sys {
|
||||
mut controllers,
|
||||
mut poises,
|
||||
outcomes,
|
||||
id_maps,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
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),
|
||||
volume_mount_data: read_data.is_volume_riders.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 {
|
||||
|
@ -131,6 +131,7 @@ impl<'a> System<'a> for Sys {
|
||||
CharacterState::Idle(_)
|
||||
| CharacterState::Talk
|
||||
| CharacterState::Dance
|
||||
| CharacterState::Pet(_)
|
||||
| CharacterState::Skate(_)
|
||||
| CharacterState::Glide(_)
|
||||
| CharacterState::GlideWield(_)
|
||||
|
@ -445,6 +445,28 @@ impl<'a> AgentData<'a> {
|
||||
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.
|
||||
if read_data.is_riders.contains(*self.entity) {
|
||||
if rng.gen_bool(0.0001) {
|
||||
@ -452,10 +474,9 @@ impl<'a> AgentData<'a> {
|
||||
} else {
|
||||
break 'activity;
|
||||
}
|
||||
} else if let Some(Alignment::Owned(owner_uid)) = self.alignment
|
||||
&& let Some(owner) = get_entity_by_id(*owner_uid, read_data)
|
||||
&& let Some(pos) = read_data.positions.get(owner)
|
||||
&& pos.0.distance_squared(self.pos.0) < MAX_MOUNT_RANGE.powi(2)
|
||||
} else if let Some(owner_uid) = owner_uid
|
||||
&& is_in_range
|
||||
&& !is_being_pet
|
||||
&& rng.gen_bool(0.01)
|
||||
{
|
||||
controller.push_event(ControlEvent::Mount(*owner_uid));
|
||||
|
@ -20,6 +20,7 @@ pub mod jump;
|
||||
pub mod leapmelee;
|
||||
pub mod mount;
|
||||
pub mod music;
|
||||
pub mod pet;
|
||||
pub mod rapidmelee;
|
||||
pub mod repeater;
|
||||
pub mod ripostemelee;
|
||||
@ -51,10 +52,11 @@ pub use self::{
|
||||
dance::DanceAnimation, dash::DashAnimation, divemelee::DiveMeleeAnimation,
|
||||
equip::EquipAnimation, finishermelee::FinisherMeleeAnimation, glidewield::GlideWieldAnimation,
|
||||
gliding::GlidingAnimation, idle::IdleAnimation, jump::JumpAnimation, leapmelee::LeapAnimation,
|
||||
mount::MountAnimation, music::MusicAnimation, rapidmelee::RapidMeleeAnimation,
|
||||
repeater::RepeaterAnimation, ripostemelee::RiposteMeleeAnimation, roll::RollAnimation,
|
||||
run::RunAnimation, selfbuff::SelfBuffAnimation, shockwave::ShockwaveAnimation,
|
||||
shoot::ShootAnimation, sit::SitAnimation, sleep::SleepAnimation, sneak::SneakAnimation,
|
||||
mount::MountAnimation, music::MusicAnimation, pet::PetAnimation,
|
||||
rapidmelee::RapidMeleeAnimation, repeater::RepeaterAnimation,
|
||||
ripostemelee::RiposteMeleeAnimation, roll::RollAnimation, run::RunAnimation,
|
||||
selfbuff::SelfBuffAnimation, shockwave::ShockwaveAnimation, shoot::ShootAnimation,
|
||||
sit::SitAnimation, sleep::SleepAnimation, sneak::SneakAnimation,
|
||||
sneakequip::SneakEquipAnimation, sneakwield::SneakWieldAnimation,
|
||||
staggered::StaggeredAnimation, stand::StandAnimation, steer::SteerAnimation,
|
||||
stunned::StunnedAnimation, swim::SwimAnimation, swimwield::SwimWieldAnimation,
|
||||
|
48
voxygen/anim/src/character/pet.rs
Normal file
48
voxygen/anim/src/character/pet.rs
Normal 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
|
||||
}
|
||||
}
|
@ -2465,39 +2465,49 @@ impl Hud {
|
||||
]
|
||||
},
|
||||
Some(comp::Alignment::Owned(owner))
|
||||
if Some(*owner) == client.uid()
|
||||
&& dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2) =>
|
||||
if dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2) =>
|
||||
{
|
||||
let mut options = Vec::new();
|
||||
if is_mount.is_none() {
|
||||
options.push((
|
||||
GameInput::Trade,
|
||||
i18n.get_msg("hud-trade").to_string(),
|
||||
));
|
||||
if !client.is_riding()
|
||||
&& is_mountable(body, bodies.get(client.entity()))
|
||||
{
|
||||
if Some(*owner) == client.uid() {
|
||||
options.push((
|
||||
GameInput::Mount,
|
||||
i18n.get_msg("hud-mount").to_string(),
|
||||
GameInput::Trade,
|
||||
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
|
||||
.map_or(false, |activity| activity.is_pet_staying);
|
||||
|
||||
// Anyone can pet a tamed animal
|
||||
options.push((
|
||||
GameInput::StayFollow,
|
||||
i18n.get_msg(if is_staying {
|
||||
"hud-follow"
|
||||
} else {
|
||||
"hud-stay"
|
||||
})
|
||||
.to_string(),
|
||||
GameInput::Interact,
|
||||
i18n.get_msg("hud-pet").to_string(),
|
||||
));
|
||||
}
|
||||
options
|
||||
},
|
||||
Some(comp::Alignment::Tame) => {
|
||||
vec![(GameInput::Interact, i18n.get_msg("hud-pet").to_string())]
|
||||
},
|
||||
_ => Vec::new(),
|
||||
},
|
||||
&time,
|
||||
|
@ -2112,6 +2112,22 @@ impl FigureMgr {
|
||||
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) => {
|
||||
anim::character::MusicAnimation::update_skeleton(
|
||||
&target_base,
|
||||
|
@ -28,6 +28,7 @@ use common::{
|
||||
mounting::{Mount, VolumePos},
|
||||
outcome::Outcome,
|
||||
recipe,
|
||||
states::utils::can_perform_pet,
|
||||
terrain::{Block, BlockKind},
|
||||
trade::TradeResult,
|
||||
util::{Dir, Plane},
|
||||
@ -1074,6 +1075,22 @@ impl PlayState for SessionState {
|
||||
.state()
|
||||
.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
|
||||
.state()
|
||||
.ecs()
|
||||
@ -1097,6 +1114,8 @@ impl PlayState for SessionState {
|
||||
.flatten()
|
||||
{
|
||||
client.activate_portal(portal_uid);
|
||||
} else if pettable {
|
||||
client.do_pet(*entity);
|
||||
} else {
|
||||
client.npc_interact(*entity, Subject::Regular);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user