Allowed bird pets to mount their owners when nearby

This commit is contained in:
Joshua Barretto 2023-05-12 21:03:44 +01:00
parent db860de517
commit 9e9889eed6
14 changed files with 206 additions and 109 deletions

View File

@ -1012,6 +1012,7 @@ impl Body {
/// Component of the mounting offset specific to the mount /// Component of the mounting offset specific to the mount
pub fn mount_offset(&self) -> Vec3<f32> { pub fn mount_offset(&self) -> Vec3<f32> {
match self { match self {
Body::Humanoid(_) => (self.dimensions() * Vec3::new(0.7, 0.0, 0.6)).into_array(),
Body::QuadrupedMedium(quadruped_medium) => { Body::QuadrupedMedium(quadruped_medium) => {
match (quadruped_medium.species, quadruped_medium.body_type) { match (quadruped_medium.species, quadruped_medium.body_type) {
(quadruped_medium::Species::Grolgar, _) => [0.0, 0.5, 1.8], (quadruped_medium::Species::Grolgar, _) => [0.0, 0.5, 1.8],

View File

@ -62,6 +62,7 @@ pub fn is_mountable(mount: &Body, rider: Option<&Body>) -> bool {
|rider: Option<&Body>| -> bool { rider.map_or(false, |b| b.mass() <= Mass(500.0)) }; |rider: Option<&Body>| -> bool { rider.map_or(false, |b| b.mass() <= Mass(500.0)) };
match mount { match mount {
Body::Humanoid(_) => matches!(rider, Some(Body::BirdMedium(_))),
Body::QuadrupedMedium(body) => match body.species { Body::QuadrupedMedium(body) => match body.species {
quadruped_medium::Species::Alpaca quadruped_medium::Species::Alpaca
| quadruped_medium::Species::Antelope | quadruped_medium::Species::Antelope

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
comp::{self, ship::figuredata::VOXEL_COLLIDER_MANIFEST}, comp::{self, pet::is_mountable, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
link::{Is, Link, LinkHandle, Role}, link::{Is, Link, LinkHandle, Role},
terrain::{Block, TerrainGrid}, terrain::{Block, TerrainGrid},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
@ -59,8 +59,10 @@ impl Link for Mounting {
Read<'a, UidAllocator>, Read<'a, UidAllocator>,
Entities<'a>, Entities<'a>,
ReadStorage<'a, comp::Health>, ReadStorage<'a, comp::Health>,
ReadStorage<'a, comp::Body>,
ReadStorage<'a, Is<Mount>>, ReadStorage<'a, Is<Mount>>,
ReadStorage<'a, Is<Rider>>, ReadStorage<'a, Is<Rider>>,
ReadStorage<'a, comp::CharacterState>,
); );
fn create( fn create(
@ -73,15 +75,12 @@ impl Link for Mounting {
// Forbid self-mounting // Forbid self-mounting
Err(MountingError::NotMountable) Err(MountingError::NotMountable)
} else if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) { } else if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
let can_mount_with = |entity| {
!is_mounts.contains(entity)
&& !is_riders.contains(entity)
&& !is_volume_rider.contains(entity)
};
// Ensure that neither mount or rider are already part of a mounting // Ensure that neither mount or rider are already part of a mounting
// relationship // relationship
if can_mount_with(mount) && can_mount_with(rider) { if !is_mounts.contains(mount)
&& !is_riders.contains(rider)
&& !is_volume_rider.contains(rider)
{
let _ = is_mounts.insert(mount, this.make_role()); let _ = is_mounts.insert(mount, this.make_role());
let _ = is_riders.insert(rider, this.make_role()); let _ = is_riders.insert(rider, this.make_role());
Ok(()) Ok(())
@ -95,7 +94,7 @@ impl Link for Mounting {
fn persist( fn persist(
this: &LinkHandle<Self>, this: &LinkHandle<Self>,
(uid_allocator, entities, healths, is_mounts, is_riders): Self::PersistData<'_>, (uid_allocator, entities, healths, bodies, is_mounts, is_riders, character_states): Self::PersistData<'_>,
) -> bool { ) -> bool {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into()); let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
@ -104,11 +103,19 @@ impl Link for Mounting {
entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead) entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead)
}; };
let is_in_ridable_state = character_states
.get(mount)
.map_or(false, |cs| !matches!(cs, comp::CharacterState::Roll(_)));
// Ensure that both entities are alive and that they continue to be linked // Ensure that both entities are alive and that they continue to be linked
is_alive(mount) is_alive(mount)
&& is_alive(rider) && is_alive(rider)
&& is_mounts.get(mount).is_some() && is_mounts.get(mount).is_some()
&& is_riders.get(rider).is_some() && is_riders.get(rider).is_some()
&& bodies.get(mount).map_or(false, |mount_body| {
is_mountable(mount_body, bodies.get(rider))
})
&& is_in_ridable_state
} else { } else {
false false
} }

View File

@ -1,4 +1,4 @@
#![feature(drain_filter)] #![feature(drain_filter, let_chains)]
#![allow(clippy::option_map_unit_fn)] #![allow(clippy::option_map_unit_fn)]
mod aura; mod aura;

View File

@ -56,19 +56,25 @@ impl<'a> System<'a> for Sys {
// For each mount... // For each mount...
for (entity, is_mount, body) in (&entities, &is_mounts, bodies.maybe()).join() { for (entity, is_mount, body) in (&entities, &is_mounts, bodies.maybe()).join() {
// ...find the rider... // ...find the rider...
let Some((inputs, actions, rider)) = uid_allocator let Some((inputs_and_actions, rider)) = uid_allocator
.retrieve_entity_internal(is_mount.rider.id()) .retrieve_entity_internal(is_mount.rider.id())
.and_then(|rider| { .and_then(|rider| {
controllers controllers
.get_mut(rider) .get_mut(rider)
.map(|c| { .map(|c| (
let actions = c.actions.drain_filter(|action| match action { // Only take inputs and actions from the rider if the mount is not intelligent (TODO: expand the definition of 'intelligent').
ControlAction::StartInput { input: i, .. } if !matches!(body, Some(Body::Humanoid(_))) {
| ControlAction::CancelInput(i) => matches!(i, InputKind::Jump | InputKind::Fly | InputKind::Roll), let actions = c.actions.drain_filter(|action| match action {
_ => false ControlAction::StartInput { input: i, .. }
}).collect(); | ControlAction::CancelInput(i) => matches!(i, InputKind::Jump | InputKind::Fly | InputKind::Roll),
(c.inputs.clone(), actions, rider) _ => false
}) }).collect();
Some((c.inputs.clone(), actions))
} else {
None
},
rider,
))
}) })
else { continue }; else { continue };
@ -86,8 +92,10 @@ impl<'a> System<'a> for Sys {
let _ = orientations.insert(rider, ori); let _ = orientations.insert(rider, ori);
let _ = velocities.insert(rider, vel); let _ = velocities.insert(rider, vel);
} }
// ...and apply the rider's inputs to the mount's controller. // ...and apply the rider's inputs to the mount's controller
if let Some(controller) = controllers.get_mut(entity) { if let Some((inputs, actions)) = inputs_and_actions
&& let Some(controller) = controllers.get_mut(entity)
{
controller.inputs = inputs; controller.inputs = inputs;
controller.actions = actions; controller.actions = actions;
} }

View File

@ -25,19 +25,20 @@ use common::{
Agent, Alignment, Body, CharacterState, Content, ControlAction, ControlEvent, Controller, Agent, Alignment, Body, CharacterState, Content, ControlAction, ControlEvent, Controller,
HealthChange, InputKind, InventoryAction, Pos, Scale, UnresolvedChatMsg, UtteranceKind, HealthChange, InputKind, InventoryAction, Pos, Scale, UnresolvedChatMsg, UtteranceKind,
}, },
consts::MAX_MOUNT_RANGE,
effect::{BuffEffect, Effect}, effect::{BuffEffect, Effect},
event::{Emitter, ServerEvent}, event::{Emitter, ServerEvent},
path::TraversalConfig, path::TraversalConfig,
rtsim::NpcActivity, rtsim::NpcActivity,
states::basic_beam, states::basic_beam,
terrain::{Block, TerrainGrid}, terrain::Block,
time::DayPeriod, time::DayPeriod,
util::Dir, util::Dir,
vol::ReadVol, vol::ReadVol,
}; };
use itertools::Itertools; use itertools::Itertools;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use specs::Entity as EcsEntity; use specs::{saveload::Marker, Entity as EcsEntity};
use vek::*; use vek::*;
#[cfg(feature = "use-dyn-lib")] #[cfg(feature = "use-dyn-lib")]
@ -48,7 +49,11 @@ impl<'a> AgentData<'a> {
// Action Nodes // Action Nodes
//////////////////////////////////////// ////////////////////////////////////////
pub fn glider_fall(&self, controller: &mut Controller) { pub fn glider_fall(&self, controller: &mut Controller, read_data: &ReadData) {
if read_data.is_riders.contains(*self.entity) {
controller.push_event(ControlEvent::Unmount);
}
controller.push_action(ControlAction::GlideWield); controller.push_action(ControlAction::GlideWield);
let flight_direction = let flight_direction =
@ -66,7 +71,11 @@ impl<'a> AgentData<'a> {
controller.inputs.look_dir = Dir::from_unnormalized(look_dir).unwrap_or_else(Dir::forward); controller.inputs.look_dir = Dir::from_unnormalized(look_dir).unwrap_or_else(Dir::forward);
} }
pub fn fly_upward(&self, controller: &mut Controller) { pub fn fly_upward(&self, controller: &mut Controller, read_data: &ReadData) {
if read_data.is_riders.contains(*self.entity) {
controller.push_event(ControlEvent::Unmount);
}
controller.push_basic_input(InputKind::Fly); controller.push_basic_input(InputKind::Fly);
controller.inputs.move_z = 1.0; controller.inputs.move_z = 1.0;
} }
@ -86,6 +95,10 @@ impl<'a> AgentData<'a> {
path: Path, path: Path,
speed_multiplier: Option<f32>, speed_multiplier: Option<f32>,
) -> bool { ) -> bool {
if read_data.is_riders.contains(*self.entity) {
controller.push_event(ControlEvent::Unmount);
}
let partial_path_tgt_pos = |pos_difference: Vec3<f32>| { let partial_path_tgt_pos = |pos_difference: Vec3<f32>| {
self.pos.0 self.pos.0
+ PARTIAL_PATH_DIST * pos_difference.try_normalized().unwrap_or_else(Vec3::zero) + PARTIAL_PATH_DIST * pos_difference.try_normalized().unwrap_or_else(Vec3::zero)
@ -369,6 +382,23 @@ impl<'a> AgentData<'a> {
None => {}, None => {},
} }
// 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) {
controller.push_event(ControlEvent::Unmount);
} else {
break 'activity;
}
} else if let Some(Alignment::Owned(owner_uid)) = self.alignment
&& let Some(owner) = get_entity_by_id(owner_uid.id(), read_data)
&& let Some(pos) = read_data.positions.get(owner)
&& pos.0.distance_squared(self.pos.0) < MAX_MOUNT_RANGE.powi(2)
&& rng.gen_bool(0.01)
{
controller.push_event(ControlEvent::Mount(*owner_uid));
break 'activity;
}
// Bats should fly // Bats should fly
// Use a proportional controller as the bouncing effect mimics bat flight // Use a proportional controller as the bouncing effect mimics bat flight
if self.traversal_config.can_fly if self.traversal_config.can_fly
@ -488,11 +518,15 @@ impl<'a> AgentData<'a> {
&self, &self,
agent: &mut Agent, agent: &mut Agent,
controller: &mut Controller, controller: &mut Controller,
terrain: &TerrainGrid, read_data: &ReadData,
tgt_pos: &Pos, tgt_pos: &Pos,
) { ) {
if read_data.is_riders.contains(*self.entity) {
controller.push_event(ControlEvent::Unmount);
}
if let Some((bearing, speed)) = agent.chaser.chase( if let Some((bearing, speed)) = agent.chaser.chase(
terrain, &*read_data.terrain,
self.pos.0, self.pos.0,
self.vel.0, self.vel.0,
tgt_pos.0, tgt_pos.0,
@ -536,9 +570,13 @@ impl<'a> AgentData<'a> {
&self, &self,
agent: &mut Agent, agent: &mut Agent,
controller: &mut Controller, controller: &mut Controller,
read_data: &ReadData,
tgt_pos: &Pos, tgt_pos: &Pos,
terrain: &TerrainGrid,
) { ) {
if read_data.is_riders.contains(*self.entity) {
controller.push_event(ControlEvent::Unmount);
}
if let Some(body) = self.body { if let Some(body) = self.body {
if body.can_strafe() && !self.is_gliding { if body.can_strafe() && !self.is_gliding {
controller.push_action(ControlAction::Unwield); controller.push_action(ControlAction::Unwield);
@ -546,7 +584,7 @@ impl<'a> AgentData<'a> {
} }
if let Some((bearing, speed)) = agent.chaser.chase( if let Some((bearing, speed)) = agent.chaser.chase(
terrain, &*read_data.terrain,
self.pos.0, self.pos.0,
self.vel.0, self.vel.0,
// Away from the target (ironically) // Away from the target (ironically)
@ -887,6 +925,10 @@ impl<'a> AgentData<'a> {
#[cfg(feature = "be-dyn-lib")] #[cfg(feature = "be-dyn-lib")]
let rng = &mut thread_rng(); let rng = &mut thread_rng();
if read_data.is_riders.contains(*self.entity) {
controller.push_event(ControlEvent::Unmount);
}
let tool_tactic = |tool_kind| match tool_kind { let tool_tactic = |tool_kind| match tool_kind {
ToolKind::Bow => Tactic::Bow, ToolKind::Bow => Tactic::Bow,
ToolKind::Staff => Tactic::Staff, ToolKind::Staff => Tactic::Staff,
@ -1458,9 +1500,9 @@ impl<'a> AgentData<'a> {
if sound_was_threatening && is_close { if sound_was_threatening && is_close {
if !self.below_flee_health(agent) && follows_threatening_sounds { if !self.below_flee_health(agent) && follows_threatening_sounds {
self.follow(agent, controller, &read_data.terrain, &sound_pos); self.follow(agent, controller, read_data, &sound_pos);
} else if self.below_flee_health(agent) || !follows_threatening_sounds { } else if self.below_flee_health(agent) || !follows_threatening_sounds {
self.flee(agent, controller, &sound_pos, &read_data.terrain); self.flee(agent, controller, read_data, &sound_pos);
} else { } else {
self.idle(agent, controller, read_data, event_emitter, rng); self.idle(agent, controller, read_data, event_emitter, rng);
} }

View File

@ -1,4 +1,4 @@
#![feature(exclusive_range_pattern)] #![feature(exclusive_range_pattern, let_chains)]
#[cfg(all(feature = "be-dyn-lib", feature = "use-dyn-lib"))] #[cfg(all(feature = "be-dyn-lib", feature = "use-dyn-lib"))]
compile_error!("Can't use both \"be-dyn-lib\" and \"use-dyn-lib\" features at once"); compile_error!("Can't use both \"be-dyn-lib\" and \"use-dyn-lib\" features at once");

View File

@ -113,13 +113,15 @@ pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) {
if let (Some(rider_uid), Some(mount_uid)) = if let (Some(rider_uid), Some(mount_uid)) =
(uids.get(rider).copied(), uids.get(mount).copied()) (uids.get(rider).copied(), uids.get(mount).copied())
{ {
let is_pet = matches!( let is_pet_of = |mount, rider_uid| {
state matches!(
.ecs() state
.read_storage::<comp::Alignment>() .ecs()
.get(mount), .read_storage::<comp::Alignment>()
Some(comp::Alignment::Owned(owner)) if *owner == rider_uid, .get(mount),
); Some(comp::Alignment::Owned(owner)) if *owner == rider_uid,
)
};
let can_ride = state let can_ride = state
.ecs() .ecs()
@ -129,7 +131,7 @@ pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) {
is_mountable(mount_body, state.ecs().read_storage().get(rider)) is_mountable(mount_body, state.ecs().read_storage().get(rider))
}); });
if is_pet && can_ride { if (is_pet_of(mount, rider_uid) || is_pet_of(rider, mount_uid)) && can_ride {
drop(uids); drop(uids);
let _ = state.link(Mounting { let _ = state.link(Mounting {
mount: mount_uid, mount: mount_uid,

View File

@ -191,12 +191,21 @@ fn react_on_dangerous_fall(bdata: &mut BehaviorData) -> bool {
// But keep in mind our 25 m/s gravity // But keep in mind our 25 m/s gravity
let is_falling_dangerous = bdata.agent_data.vel.0.z < -20.0; let is_falling_dangerous = bdata.agent_data.vel.0.z < -20.0;
if is_falling_dangerous && bdata.agent_data.traversal_config.can_fly { if is_falling_dangerous {
bdata.agent_data.fly_upward(bdata.controller); if bdata.read_data.is_riders.contains(*bdata.agent_data.entity) {
return true; bdata.controller.push_event(ControlEvent::Unmount);
} else if is_falling_dangerous && bdata.agent_data.glider_equipped { }
bdata.agent_data.glider_fall(bdata.controller); if bdata.agent_data.traversal_config.can_fly {
return true; bdata
.agent_data
.fly_upward(bdata.controller, bdata.read_data);
return true;
} else if bdata.agent_data.glider_equipped {
bdata
.agent_data
.glider_fall(bdata.controller, bdata.read_data);
return true;
}
} }
false false
} }
@ -407,12 +416,9 @@ fn follow_if_far_away(bdata: &mut BehaviorData) -> bool {
let dist_sqrd = bdata.agent_data.pos.0.distance_squared(tgt_pos.0); let dist_sqrd = bdata.agent_data.pos.0.distance_squared(tgt_pos.0);
if dist_sqrd > (MAX_FOLLOW_DIST).powi(2) { if dist_sqrd > (MAX_FOLLOW_DIST).powi(2) {
bdata.agent_data.follow( bdata
bdata.agent, .agent_data
bdata.controller, .follow(bdata.agent, bdata.controller, bdata.read_data, tgt_pos);
&bdata.read_data.terrain,
tgt_pos,
);
return true; return true;
} }
} }
@ -698,7 +704,7 @@ fn search_last_known_pos_if_not_alert(bdata: &mut BehaviorData) -> bool {
if let Some(target) = agent.target { if let Some(target) = agent.target {
if let Some(last_known_pos) = target.last_known_pos { if let Some(last_known_pos) = target.last_known_pos {
agent_data.follow(agent, controller, &read_data.terrain, &Pos(last_known_pos)); agent_data.follow(agent, controller, read_data, &Pos(last_known_pos));
return true; return true;
} }
@ -774,11 +780,11 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
}; };
} else if !flee_timer_done { } else if !flee_timer_done {
if within_normal_flee_dir_dist { if within_normal_flee_dir_dist {
agent_data.flee(agent, controller, tgt_pos, &read_data.terrain); agent_data.flee(agent, controller, read_data, tgt_pos);
} else if let Some(random_pos) = agent.flee_from_pos { } else if let Some(random_pos) = agent.flee_from_pos {
agent_data.flee(agent, controller, &random_pos, &read_data.terrain); agent_data.flee(agent, controller, read_data, &random_pos);
} else { } else {
agent_data.flee(agent, controller, tgt_pos, &read_data.terrain); agent_data.flee(agent, controller, read_data, tgt_pos);
} }
agent.action_state.timers agent.action_state.timers

View File

@ -162,17 +162,25 @@ impl Skeleton for CharacterSkeleton {
// FIXME: Should this be control_l_mat? // FIXME: Should this be control_l_mat?
make_bone(control_mat * hand_l_mat * Mat4::<f32>::from(self.hold)), make_bone(control_mat * hand_l_mat * Mat4::<f32>::from(self.hold)),
]; ];
// Offset from the mounted bone's origin.
// Note: This could be its own bone if we need to animate it independently.
let mount_position = (chest_mat * Vec4::from_point(Vec3::new(5.5, 0.0, 6.5)))
.homogenized()
.xyz();
// NOTE: We apply the ori from base_mat externally so we don't need to worry
// about it here for now.
let mount_orientation =
self.torso.orientation * self.chest.orientation * Quaternion::rotation_y(0.4);
let weapon_trails = self.main_weapon_trail || self.off_weapon_trail; let weapon_trails = self.main_weapon_trail || self.off_weapon_trail;
Offsets { Offsets {
lantern: Some((lantern_mat * Vec4::new(0.0, 0.5, -6.0, 1.0)).xyz()), lantern: Some((lantern_mat * Vec4::new(0.0, 0.5, -6.0, 1.0)).xyz()),
viewpoint: Some((head_mat * Vec4::new(0.0, 0.0, 4.0, 1.0)).xyz()), viewpoint: Some((head_mat * Vec4::new(0.0, 0.0, 4.0, 1.0)).xyz()),
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform { mount_bone: Transform {
position: comp::Body::Humanoid(body) position: mount_position,
.mount_offset() orientation: mount_orientation,
.into_tuple() scale: Vec3::one(),
.into(),
..Default::default()
}, },
primary_trail_mat: if weapon_trails { primary_trail_mat: if weapon_trails {
self.main_weapon_trail self.main_weapon_trail

View File

@ -107,7 +107,7 @@ use common::{
}, },
consts::MAX_PICKUP_RANGE, consts::MAX_PICKUP_RANGE,
link::Is, link::Is,
mounting::{Mount, VolumePos}, mounting::{Mount, Rider, VolumePos},
outcome::Outcome, outcome::Outcome,
resources::{Secs, Time}, resources::{Secs, Time},
slowjob::SlowJobPool, slowjob::SlowJobPool,
@ -1500,7 +1500,8 @@ impl Hud {
let me = info.viewpoint_entity; let me = info.viewpoint_entity;
let poises = ecs.read_storage::<comp::Poise>(); let poises = ecs.read_storage::<comp::Poise>();
let alignments = ecs.read_storage::<comp::Alignment>(); let alignments = ecs.read_storage::<comp::Alignment>();
let is_mount = ecs.read_storage::<Is<Mount>>(); let is_mounts = ecs.read_storage::<Is<Mount>>();
let is_riders = ecs.read_storage::<Is<Rider>>();
let stances = ecs.read_storage::<comp::Stance>(); let stances = ecs.read_storage::<comp::Stance>();
let time = ecs.read_resource::<Time>(); let time = ecs.read_resource::<Time>();
@ -2266,7 +2267,12 @@ impl Hud {
&uids, &uids,
&inventories, &inventories,
poises.maybe(), poises.maybe(),
(alignments.maybe(), is_mount.maybe(), stances.maybe()), (
alignments.maybe(),
is_mounts.maybe(),
is_riders.maybe(),
stances.maybe(),
),
) )
.join() .join()
.filter(|t| { .filter(|t| {
@ -2289,7 +2295,7 @@ impl Hud {
uid, uid,
inventory, inventory,
poise, poise,
(alignment, is_mount, stance), (alignment, is_mount, is_rider, stance),
)| { )| {
// Use interpolated position if available // Use interpolated position if available
let pos = interpolated.map_or(pos.0, |i| i.pos); let pos = interpolated.map_or(pos.0, |i| i.pos);
@ -2304,6 +2310,8 @@ impl Hud {
let display_overhead_info = !is_me let display_overhead_info = !is_me
&& (is_mount.is_none() && (is_mount.is_none()
|| health.map_or(true, overhead::should_show_healthbar)) || health.map_or(true, overhead::should_show_healthbar))
&& is_rider
.map_or(true, |is_rider| Some(&is_rider.mount) != uids.get(me))
&& (info.target_entity.map_or(false, |e| e == entity) && (info.target_entity.map_or(false, |e| e == entity)
|| info.selected_entity.map_or(false, |s| s.0 == entity) || info.selected_entity.map_or(false, |s| s.0 == entity)
|| health.map_or(true, overhead::should_show_healthbar) || health.map_or(true, overhead::should_show_healthbar)

View File

@ -683,8 +683,7 @@ impl FigureMgr {
); );
Some( Some(
state.mount_world_pos state.mount_world_pos
+ state.mount_transform.orientation + anim::vek::Vec3::from(state.lantern_offset?.into_array())
* anim::vek::Vec3::from(state.lantern_offset?.into_array())
- pos, - pos,
) )
}) })
@ -3157,45 +3156,52 @@ impl FigureMgr {
physics.on_ground.is_some(), physics.on_ground.is_some(),
rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_liquid().is_some(), // In water physics.in_liquid().is_some(), // In water
is_rider.is_some() || is_volume_rider.is_some(),
) { ) {
// Standing // Standing
(true, false, false) => anim::bird_medium::IdleAnimation::update_skeleton( (true, false, false, _) => {
&BirdMediumSkeleton::default(), anim::bird_medium::IdleAnimation::update_skeleton(
time, &BirdMediumSkeleton::default(),
state.state_time, time,
&mut state_animation_rate, state.state_time,
skeleton_attr, &mut state_animation_rate,
), skeleton_attr,
)
},
// Running // Running
(true, true, false) => anim::bird_medium::RunAnimation::update_skeleton( (true, true, false, false) => {
&BirdMediumSkeleton::default(), anim::bird_medium::RunAnimation::update_skeleton(
( &BirdMediumSkeleton::default(),
rel_vel, (
// TODO: Update to use the quaternion. rel_vel,
ori * anim::vek::Vec3::<f32>::unit_y(), // TODO: Update to use the quaternion.
state.last_ori * anim::vek::Vec3::<f32>::unit_y(), ori * anim::vek::Vec3::<f32>::unit_y(),
rel_avg_vel, state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
state.acc_vel, rel_avg_vel,
), state.acc_vel,
state.state_time, ),
&mut state_animation_rate, state.state_time,
skeleton_attr, &mut state_animation_rate,
), skeleton_attr,
)
},
// In air // In air
(false, _, false) => anim::bird_medium::FlyAnimation::update_skeleton( (false, _, false, false) => {
&BirdMediumSkeleton::default(), anim::bird_medium::FlyAnimation::update_skeleton(
( &BirdMediumSkeleton::default(),
rel_vel, (
// TODO: Update to use the quaternion. rel_vel,
ori * anim::vek::Vec3::<f32>::unit_y(), // TODO: Update to use the quaternion.
state.last_ori * anim::vek::Vec3::<f32>::unit_y(), ori * anim::vek::Vec3::<f32>::unit_y(),
), state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
state.state_time, ),
&mut state_animation_rate, state.state_time,
skeleton_attr, &mut state_animation_rate,
), skeleton_attr,
)
},
// Swim // Swim
(_, true, _) => anim::bird_medium::SwimAnimation::update_skeleton( (_, true, _, false) => anim::bird_medium::SwimAnimation::update_skeleton(
&BirdMediumSkeleton::default(), &BirdMediumSkeleton::default(),
time, time,
state.state_time, state.state_time,

View File

@ -12,7 +12,7 @@ use common::{
comp::{ship::figuredata::VOXEL_COLLIDER_MANIFEST, tool::ToolKind, Collider}, comp::{ship::figuredata::VOXEL_COLLIDER_MANIFEST, tool::ToolKind, Collider},
consts::{MAX_PICKUP_RANGE, MAX_SPRITE_MOUNT_RANGE}, consts::{MAX_PICKUP_RANGE, MAX_SPRITE_MOUNT_RANGE},
link::Is, link::Is,
mounting::{Mount, VolumePos, VolumeRider}, mounting::{Mount, Rider, VolumePos, VolumeRider},
terrain::{Block, TerrainGrid, UnlockKind}, terrain::{Block, TerrainGrid, UnlockKind},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
util::find_dist::{Cube, Cylinder, FindDist}, util::find_dist::{Cube, Cylinder, FindDist},
@ -111,6 +111,7 @@ pub(super) fn select_interactable(
collect_target: Option<Target<target::Collectable>>, collect_target: Option<Target<target::Collectable>>,
entity_target: Option<Target<target::Entity>>, entity_target: Option<Target<target::Entity>>,
mine_target: Option<Target<target::Mine>>, mine_target: Option<Target<target::Mine>>,
viewpoint_entity: specs::Entity,
scene: &Scene, scene: &Scene,
) -> Option<Interactable> { ) -> Option<Interactable> {
span!(_guard, "select_interactable"); span!(_guard, "select_interactable");
@ -182,10 +183,12 @@ pub(super) fn select_interactable(
let positions = ecs.read_storage::<comp::Pos>(); let positions = ecs.read_storage::<comp::Pos>();
let player_pos = positions.get(player_entity)?.0; let player_pos = positions.get(player_entity)?.0;
let uids = ecs.read_storage::<Uid>();
let scales = ecs.read_storage::<comp::Scale>(); let scales = ecs.read_storage::<comp::Scale>();
let colliders = ecs.read_storage::<comp::Collider>(); let colliders = ecs.read_storage::<comp::Collider>();
let char_states = ecs.read_storage::<comp::CharacterState>(); let char_states = ecs.read_storage::<comp::CharacterState>();
let is_mount = ecs.read_storage::<Is<Mount>>(); let is_mounts = ecs.read_storage::<Is<Mount>>();
let is_riders = ecs.read_storage::<Is<Rider>>();
let bodies = ecs.read_storage::<comp::Body>(); let bodies = ecs.read_storage::<comp::Body>();
let items = ecs.read_storage::<comp::Item>(); let items = ecs.read_storage::<comp::Item>();
let stats = ecs.read_storage::<comp::Stats>(); let stats = ecs.read_storage::<comp::Stats>();
@ -208,7 +211,8 @@ pub(super) fn select_interactable(
scales.maybe(), scales.maybe(),
colliders.maybe(), colliders.maybe(),
char_states.maybe(), char_states.maybe(),
!&is_mount, !&is_mounts,
is_riders.maybe(),
(stats.mask() | items.mask()).maybe(), (stats.mask() | items.mask()).maybe(),
) )
.join(); .join();
@ -216,7 +220,7 @@ pub(super) fn select_interactable(
let closest_interactable_entity = spacial_grid.0.in_circle_aabr(player_pos.xy(), MAX_PICKUP_RANGE) let closest_interactable_entity = spacial_grid.0.in_circle_aabr(player_pos.xy(), MAX_PICKUP_RANGE)
.filter(|&e| e != player_entity) // skip the player's entity .filter(|&e| e != player_entity) // skip the player's entity
.filter_map(|e| entity_data.get(e, &entities)) .filter_map(|e| entity_data.get(e, &entities))
.filter_map(|(e, p, b, s, c, cs, _, has_stats_or_item)| { .filter_map(|(e, p, b, s, c, cs, _, is_rider, has_stats_or_item)| {
// Note, if this becomes expensive to compute do it after the distance check! // Note, if this becomes expensive to compute do it after the distance check!
// //
// The entities that can be interacted with: // The entities that can be interacted with:
@ -227,7 +231,10 @@ pub(super) fn select_interactable(
// some false positives here as long as it doesn't frequently prevent us from // some false positives here as long as it doesn't frequently prevent us from
// interacting with actual interactable entities that are closer by) // interacting with actual interactable entities that are closer by)
// * Dropped items that can be picked up (Item component) // * Dropped items that can be picked up (Item component)
let is_interactable = b.is_campfire() || has_stats_or_item.is_some(); // * Are not riding the player
let not_riding_player = is_rider
.map_or(true, |is_rider| Some(&is_rider.mount) != uids.get(viewpoint_entity));
let is_interactable = (b.is_campfire() || has_stats_or_item.is_some()) && not_riding_player;
if !is_interactable { if !is_interactable {
return None; return None;
}; };

View File

@ -617,6 +617,7 @@ impl PlayState for SessionState {
collect_target, collect_target,
entity_target, entity_target,
mine_target, mine_target,
self.viewpoint_entity().0,
&self.scene, &self.scene,
); );