mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/in-soviet-russia-pet-mount-you' into 'master'
Allow pet birds to sit on the player's shoulder See merge request veloren/veloren!3930
This commit is contained in:
commit
eb7d712093
@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Item drops that are spatially close and compatible will now merge with one-another to reduce performance problems.
|
||||
- Airships can now have sprites, which can be interacted with.
|
||||
- Some sprites can be sat on.
|
||||
- Pet birds can now sit on the player's shoulder as they explore the world.
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -1012,6 +1012,9 @@ impl Body {
|
||||
/// Component of the mounting offset specific to the mount
|
||||
pub fn mount_offset(&self) -> Vec3<f32> {
|
||||
match self {
|
||||
Body::Humanoid(_) | Body::BipedLarge(_) => {
|
||||
(self.dimensions() * Vec3::new(0.5, 0.0, 0.6)).into_array()
|
||||
},
|
||||
Body::QuadrupedMedium(quadruped_medium) => {
|
||||
match (quadruped_medium.species, quadruped_medium.body_type) {
|
||||
(quadruped_medium::Species::Grolgar, _) => [0.0, 0.5, 1.8],
|
||||
|
@ -62,6 +62,8 @@ pub fn is_mountable(mount: &Body, rider: Option<&Body>) -> bool {
|
||||
|rider: Option<&Body>| -> bool { rider.map_or(false, |b| b.mass() <= Mass(500.0)) };
|
||||
|
||||
match mount {
|
||||
Body::Humanoid(_) => matches!(rider, Some(Body::BirdMedium(_))),
|
||||
Body::BipedLarge(_) => is_light_enough(rider),
|
||||
Body::QuadrupedMedium(body) => match body.species {
|
||||
quadruped_medium::Species::Alpaca
|
||||
| quadruped_medium::Species::Antelope
|
||||
|
@ -6,13 +6,13 @@ pub trait Link: Sized + Send + Sync + 'static {
|
||||
type Error;
|
||||
|
||||
type CreateData<'a>: SystemData<'a>;
|
||||
fn create(this: &LinkHandle<Self>, data: Self::CreateData<'_>) -> Result<(), Self::Error>;
|
||||
fn create(this: &LinkHandle<Self>, data: &mut Self::CreateData<'_>) -> Result<(), Self::Error>;
|
||||
|
||||
type PersistData<'a>: SystemData<'a>;
|
||||
fn persist(this: &LinkHandle<Self>, data: Self::PersistData<'_>) -> bool;
|
||||
fn persist(this: &LinkHandle<Self>, data: &mut Self::PersistData<'_>) -> bool;
|
||||
|
||||
type DeleteData<'a>: SystemData<'a>;
|
||||
fn delete(this: &LinkHandle<Self>, data: Self::DeleteData<'_>);
|
||||
fn delete(this: &LinkHandle<Self>, data: &mut Self::DeleteData<'_>);
|
||||
}
|
||||
|
||||
pub trait Role {
|
||||
@ -27,7 +27,9 @@ pub struct Is<R: Role> {
|
||||
}
|
||||
|
||||
impl<R: Role> Is<R> {
|
||||
pub fn delete(&self, data: <R::Link as Link>::DeleteData<'_>) { Link::delete(&self.link, data) }
|
||||
pub fn delete(&self, data: &mut <R::Link as Link>::DeleteData<'_>) {
|
||||
Link::delete(&self.link, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Role> Clone for Is<R> {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
comp::{self, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
||||
comp::{self, pet::is_mountable, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
||||
link::{Is, Link, LinkHandle, Role},
|
||||
terrain::{Block, TerrainGrid},
|
||||
uid::{Uid, UidAllocator},
|
||||
@ -59,13 +59,15 @@ impl Link for Mounting {
|
||||
Read<'a, UidAllocator>,
|
||||
Entities<'a>,
|
||||
ReadStorage<'a, comp::Health>,
|
||||
ReadStorage<'a, comp::Body>,
|
||||
ReadStorage<'a, Is<Mount>>,
|
||||
ReadStorage<'a, Is<Rider>>,
|
||||
ReadStorage<'a, comp::CharacterState>,
|
||||
);
|
||||
|
||||
fn create(
|
||||
this: &LinkHandle<Self>,
|
||||
(uid_allocator, mut is_mounts, mut is_riders, is_volume_rider): Self::CreateData<'_>,
|
||||
(uid_allocator, is_mounts, is_riders, is_volume_rider): &mut Self::CreateData<'_>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
|
||||
@ -73,15 +75,12 @@ impl Link for Mounting {
|
||||
// Forbid self-mounting
|
||||
Err(MountingError::NotMountable)
|
||||
} 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
|
||||
// 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_riders.insert(rider, this.make_role());
|
||||
Ok(())
|
||||
@ -95,7 +94,7 @@ impl Link for Mounting {
|
||||
|
||||
fn persist(
|
||||
this: &LinkHandle<Self>,
|
||||
(uid_allocator, entities, healths, is_mounts, is_riders): Self::PersistData<'_>,
|
||||
(uid_allocator, entities, healths, bodies, is_mounts, is_riders, character_states): &mut Self::PersistData<'_>,
|
||||
) -> bool {
|
||||
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)
|
||||
};
|
||||
|
||||
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
|
||||
is_alive(mount)
|
||||
&& is_alive(rider)
|
||||
&& is_mounts.get(mount).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 {
|
||||
false
|
||||
}
|
||||
@ -116,7 +123,7 @@ impl Link for Mounting {
|
||||
|
||||
fn delete(
|
||||
this: &LinkHandle<Self>,
|
||||
(uid_allocator, mut is_mounts, mut is_riders, mut positions, mut force_update, terrain): Self::DeleteData<'_>,
|
||||
(uid_allocator, is_mounts, is_riders, positions, force_update, terrain): &mut Self::DeleteData<'_>,
|
||||
) {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
|
||||
@ -279,7 +286,6 @@ impl Link for VolumeMounting {
|
||||
WriteStorage<'a, VolumeRiders>,
|
||||
WriteStorage<'a, Is<VolumeRider>>,
|
||||
ReadStorage<'a, Is<Rider>>,
|
||||
ReadStorage<'a, Is<Mount>>,
|
||||
ReadExpect<'a, TerrainGrid>,
|
||||
Read<'a, UidAllocator>,
|
||||
ReadStorage<'a, comp::Collider>,
|
||||
@ -305,15 +311,14 @@ impl Link for VolumeMounting {
|
||||
fn create(
|
||||
this: &LinkHandle<Self>,
|
||||
(
|
||||
mut terrain_riders,
|
||||
mut volume_riders,
|
||||
mut is_volume_riders,
|
||||
terrain_riders,
|
||||
volume_riders,
|
||||
is_volume_riders,
|
||||
is_riders,
|
||||
is_mounts,
|
||||
terrain_grid,
|
||||
uid_allocator,
|
||||
colliders,
|
||||
): Self::CreateData<'_>,
|
||||
): &mut Self::CreateData<'_>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
|
||||
@ -329,11 +334,10 @@ impl Link for VolumeMounting {
|
||||
&& !is_volume_riders.contains(rider)
|
||||
&& !is_volume_riders.contains(rider)
|
||||
&& !is_riders.contains(rider)
|
||||
&& !is_mounts.contains(rider)
|
||||
{
|
||||
let block = this
|
||||
.pos
|
||||
.get_block(&terrain_grid, &uid_allocator, &colliders)
|
||||
.get_block(terrain_grid, uid_allocator, colliders)
|
||||
.ok_or(MountingError::NoSuchEntity)?;
|
||||
|
||||
if block == this.block {
|
||||
@ -359,7 +363,7 @@ impl Link for VolumeMounting {
|
||||
terrain_grid,
|
||||
uid_allocator,
|
||||
colliders,
|
||||
): Self::PersistData<'_>,
|
||||
): &mut Self::PersistData<'_>,
|
||||
) -> bool {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
let is_alive =
|
||||
@ -383,7 +387,7 @@ impl Link for VolumeMounting {
|
||||
|
||||
let block_exists = this
|
||||
.pos
|
||||
.get_block(&terrain_grid, &uid_allocator, &colliders)
|
||||
.get_block(terrain_grid, uid_allocator, colliders)
|
||||
.map_or(false, |block| block == this.block);
|
||||
|
||||
rider_exists && mount_spot_exists && block_exists
|
||||
@ -391,12 +395,12 @@ impl Link for VolumeMounting {
|
||||
|
||||
fn delete(
|
||||
this: &LinkHandle<Self>,
|
||||
(mut terrain_riders, mut volume_riders, mut is_rider, uid_allocator): Self::DeleteData<'_>,
|
||||
(terrain_riders, volume_riders, is_rider, uid_allocator): &mut Self::DeleteData<'_>,
|
||||
) {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
|
||||
let riders = match this.pos.kind {
|
||||
Volume::Terrain => Some(&mut *terrain_riders),
|
||||
Volume::Terrain => Some(&mut **terrain_riders),
|
||||
Volume::Entity(uid) => {
|
||||
entity(uid).and_then(|entity| volume_riders.get_mut_or_default(entity))
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
#![feature(drain_filter)]
|
||||
#![feature(drain_filter, let_chains)]
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
|
||||
mod aura;
|
||||
|
@ -56,19 +56,25 @@ impl<'a> System<'a> for Sys {
|
||||
// For each mount...
|
||||
for (entity, is_mount, body) in (&entities, &is_mounts, bodies.maybe()).join() {
|
||||
// ...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())
|
||||
.and_then(|rider| {
|
||||
controllers
|
||||
.get_mut(rider)
|
||||
.map(|c| {
|
||||
let actions = c.actions.drain_filter(|action| match action {
|
||||
ControlAction::StartInput { input: i, .. }
|
||||
| ControlAction::CancelInput(i) => matches!(i, InputKind::Jump | InputKind::Fly | InputKind::Roll),
|
||||
_ => false
|
||||
}).collect();
|
||||
(c.inputs.clone(), actions, rider)
|
||||
})
|
||||
.map(|c| (
|
||||
// Only take inputs and actions from the rider if the mount is not intelligent (TODO: expand the definition of 'intelligent').
|
||||
if !matches!(body, Some(Body::Humanoid(_))) {
|
||||
let actions = c.actions.drain_filter(|action| match action {
|
||||
ControlAction::StartInput { input: i, .. }
|
||||
| ControlAction::CancelInput(i) => matches!(i, InputKind::Jump | InputKind::Fly | InputKind::Roll),
|
||||
_ => false
|
||||
}).collect();
|
||||
Some((c.inputs.clone(), actions))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
rider,
|
||||
))
|
||||
})
|
||||
else { continue };
|
||||
|
||||
@ -86,8 +92,10 @@ impl<'a> System<'a> for Sys {
|
||||
let _ = orientations.insert(rider, ori);
|
||||
let _ = velocities.insert(rider, vel);
|
||||
}
|
||||
// ...and apply the rider's inputs to the mount's controller.
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
// ...and apply the rider's inputs to the mount's controller
|
||||
if let Some((inputs, actions)) = inputs_and_actions
|
||||
&& let Some(controller) = controllers.get_mut(entity)
|
||||
{
|
||||
controller.inputs = inputs;
|
||||
controller.actions = actions;
|
||||
}
|
||||
|
@ -25,19 +25,20 @@ use common::{
|
||||
Agent, Alignment, Body, CharacterState, Content, ControlAction, ControlEvent, Controller,
|
||||
HealthChange, InputKind, InventoryAction, Pos, Scale, UnresolvedChatMsg, UtteranceKind,
|
||||
},
|
||||
consts::MAX_MOUNT_RANGE,
|
||||
effect::{BuffEffect, Effect},
|
||||
event::{Emitter, ServerEvent},
|
||||
path::TraversalConfig,
|
||||
rtsim::NpcActivity,
|
||||
states::basic_beam,
|
||||
terrain::{Block, TerrainGrid},
|
||||
terrain::Block,
|
||||
time::DayPeriod,
|
||||
util::Dir,
|
||||
vol::ReadVol,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::Entity as EcsEntity;
|
||||
use specs::{saveload::Marker, Entity as EcsEntity};
|
||||
use vek::*;
|
||||
|
||||
#[cfg(feature = "use-dyn-lib")]
|
||||
@ -48,7 +49,11 @@ impl<'a> AgentData<'a> {
|
||||
// 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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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.inputs.move_z = 1.0;
|
||||
}
|
||||
@ -86,6 +95,10 @@ impl<'a> AgentData<'a> {
|
||||
path: Path,
|
||||
speed_multiplier: Option<f32>,
|
||||
) -> bool {
|
||||
if read_data.is_riders.contains(*self.entity) {
|
||||
controller.push_event(ControlEvent::Unmount);
|
||||
}
|
||||
|
||||
let partial_path_tgt_pos = |pos_difference: Vec3<f32>| {
|
||||
self.pos.0
|
||||
+ PARTIAL_PATH_DIST * pos_difference.try_normalized().unwrap_or_else(Vec3::zero)
|
||||
@ -369,6 +382,23 @@ impl<'a> AgentData<'a> {
|
||||
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
|
||||
// Use a proportional controller as the bouncing effect mimics bat flight
|
||||
if self.traversal_config.can_fly
|
||||
@ -488,11 +518,15 @@ impl<'a> AgentData<'a> {
|
||||
&self,
|
||||
agent: &mut Agent,
|
||||
controller: &mut Controller,
|
||||
terrain: &TerrainGrid,
|
||||
read_data: &ReadData,
|
||||
tgt_pos: &Pos,
|
||||
) {
|
||||
if read_data.is_riders.contains(*self.entity) {
|
||||
controller.push_event(ControlEvent::Unmount);
|
||||
}
|
||||
|
||||
if let Some((bearing, speed)) = agent.chaser.chase(
|
||||
terrain,
|
||||
&*read_data.terrain,
|
||||
self.pos.0,
|
||||
self.vel.0,
|
||||
tgt_pos.0,
|
||||
@ -536,9 +570,13 @@ impl<'a> AgentData<'a> {
|
||||
&self,
|
||||
agent: &mut Agent,
|
||||
controller: &mut Controller,
|
||||
read_data: &ReadData,
|
||||
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 body.can_strafe() && !self.is_gliding {
|
||||
controller.push_action(ControlAction::Unwield);
|
||||
@ -546,7 +584,7 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
|
||||
if let Some((bearing, speed)) = agent.chaser.chase(
|
||||
terrain,
|
||||
&*read_data.terrain,
|
||||
self.pos.0,
|
||||
self.vel.0,
|
||||
// Away from the target (ironically)
|
||||
@ -887,6 +925,10 @@ impl<'a> AgentData<'a> {
|
||||
#[cfg(feature = "be-dyn-lib")]
|
||||
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 {
|
||||
ToolKind::Bow => Tactic::Bow,
|
||||
ToolKind::Staff => Tactic::Staff,
|
||||
@ -1458,9 +1500,9 @@ impl<'a> AgentData<'a> {
|
||||
|
||||
if sound_was_threatening && is_close {
|
||||
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 {
|
||||
self.flee(agent, controller, &sound_pos, &read_data.terrain);
|
||||
self.flee(agent, controller, read_data, &sound_pos);
|
||||
} else {
|
||||
self.idle(agent, controller, read_data, event_emitter, rng);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
#![feature(exclusive_range_pattern)]
|
||||
#![feature(exclusive_range_pattern, let_chains)]
|
||||
|
||||
#[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");
|
||||
|
@ -113,13 +113,15 @@ pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) {
|
||||
if let (Some(rider_uid), Some(mount_uid)) =
|
||||
(uids.get(rider).copied(), uids.get(mount).copied())
|
||||
{
|
||||
let is_pet = matches!(
|
||||
state
|
||||
.ecs()
|
||||
.read_storage::<comp::Alignment>()
|
||||
.get(mount),
|
||||
Some(comp::Alignment::Owned(owner)) if *owner == rider_uid,
|
||||
);
|
||||
let is_pet_of = |mount, rider_uid| {
|
||||
matches!(
|
||||
state
|
||||
.ecs()
|
||||
.read_storage::<comp::Alignment>()
|
||||
.get(mount),
|
||||
Some(comp::Alignment::Owned(owner)) if *owner == rider_uid,
|
||||
)
|
||||
};
|
||||
|
||||
let can_ride = state
|
||||
.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))
|
||||
});
|
||||
|
||||
if is_pet && can_ride {
|
||||
if (is_pet_of(mount, rider_uid) || is_pet_of(rider, mount_uid)) && can_ride {
|
||||
drop(uids);
|
||||
let _ = state.link(Mounting {
|
||||
mount: mount_uid,
|
||||
|
@ -1074,7 +1074,7 @@ impl StateExt for State {
|
||||
fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error> {
|
||||
let linker = LinkHandle::from_link(link);
|
||||
|
||||
L::create(&linker, self.ecs().system_data())?;
|
||||
L::create(&linker, &mut self.ecs().system_data())?;
|
||||
|
||||
self.ecs_mut()
|
||||
.entry::<Vec<LinkHandle<L>>>()
|
||||
@ -1087,11 +1087,18 @@ impl StateExt for State {
|
||||
fn maintain_links(&mut self) {
|
||||
fn maintain_link<L: Link>(state: &State) {
|
||||
if let Some(mut handles) = state.ecs().try_fetch_mut::<Vec<LinkHandle<L>>>() {
|
||||
let mut persist_data = None;
|
||||
handles.retain(|link| {
|
||||
if L::persist(link, state.ecs().system_data()) {
|
||||
if L::persist(
|
||||
link,
|
||||
persist_data.get_or_insert_with(|| state.ecs().system_data()),
|
||||
) {
|
||||
true
|
||||
} else {
|
||||
L::delete(link, state.ecs().system_data());
|
||||
// Make sure to drop persist data before running deletion to avoid potential
|
||||
// access violations
|
||||
persist_data.take();
|
||||
L::delete(link, &mut state.ecs().system_data());
|
||||
false
|
||||
}
|
||||
});
|
||||
|
@ -191,12 +191,21 @@ fn react_on_dangerous_fall(bdata: &mut BehaviorData) -> bool {
|
||||
// But keep in mind our 25 m/s gravity
|
||||
let is_falling_dangerous = bdata.agent_data.vel.0.z < -20.0;
|
||||
|
||||
if is_falling_dangerous && bdata.agent_data.traversal_config.can_fly {
|
||||
bdata.agent_data.fly_upward(bdata.controller);
|
||||
return true;
|
||||
} else if is_falling_dangerous && bdata.agent_data.glider_equipped {
|
||||
bdata.agent_data.glider_fall(bdata.controller);
|
||||
return true;
|
||||
if is_falling_dangerous {
|
||||
if bdata.read_data.is_riders.contains(*bdata.agent_data.entity) {
|
||||
bdata.controller.push_event(ControlEvent::Unmount);
|
||||
}
|
||||
if bdata.agent_data.traversal_config.can_fly {
|
||||
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
|
||||
}
|
||||
@ -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);
|
||||
|
||||
if dist_sqrd > (MAX_FOLLOW_DIST).powi(2) {
|
||||
bdata.agent_data.follow(
|
||||
bdata.agent,
|
||||
bdata.controller,
|
||||
&bdata.read_data.terrain,
|
||||
tgt_pos,
|
||||
);
|
||||
bdata
|
||||
.agent_data
|
||||
.follow(bdata.agent, bdata.controller, bdata.read_data, tgt_pos);
|
||||
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(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;
|
||||
}
|
||||
@ -774,11 +780,11 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
|
||||
};
|
||||
} else if !flee_timer_done {
|
||||
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 {
|
||||
agent_data.flee(agent, controller, &random_pos, &read_data.terrain);
|
||||
agent_data.flee(agent, controller, read_data, &random_pos);
|
||||
} else {
|
||||
agent_data.flee(agent, controller, tgt_pos, &read_data.terrain);
|
||||
agent_data.flee(agent, controller, read_data, tgt_pos);
|
||||
}
|
||||
|
||||
agent.action_state.timers
|
||||
|
@ -130,16 +130,28 @@ impl Skeleton for BipedLargeSkeleton {
|
||||
// FIXME: Should this be control_l_mat?
|
||||
make_bone(upper_torso_mat * 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 = (arm_control_r
|
||||
* Mat4::<f32>::from(self.shoulder_r)
|
||||
* Vec4::from_point(mount_point(&body)))
|
||||
.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.upper_torso.orientation
|
||||
* self.arm_control_r.orientation
|
||||
* self.shoulder_r.orientation;
|
||||
|
||||
Offsets {
|
||||
lantern: None,
|
||||
viewpoint: Some((jaw_mat * Vec4::new(0.0, 4.0, 0.0, 1.0)).xyz()),
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: comp::Body::BipedLarge(body)
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
position: mount_position,
|
||||
orientation: mount_orientation,
|
||||
scale: Vec3::one(),
|
||||
},
|
||||
primary_trail_mat: None,
|
||||
secondary_trail_mat: None,
|
||||
@ -567,3 +579,33 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mount_point(body: &Body) -> Vec3<f32> {
|
||||
use comp::biped_large::Species::*;
|
||||
match (body.species, body.body_type) {
|
||||
(Ogre, _) => (0.0, 3.0, 1.0),
|
||||
(Cyclops, _) => (0.0, 3.0, 1.0),
|
||||
(Wendigo, _) => (0.0, 0.0, -1.0),
|
||||
(Cavetroll, _) => (0.0, 1.0, 2.0),
|
||||
(Mountaintroll, _) => (0.0, 4.0, 2.0),
|
||||
(Swamptroll, _) => (0.0, 0.0, 3.0),
|
||||
(Dullahan, _) => (0.0, 0.0, 3.0),
|
||||
(Werewolf, _) => (-1.0, 0.0, 0.0),
|
||||
(Occultsaurok, _) => (0.0, 0.0, -1.0),
|
||||
(Mightysaurok, _) => (0.0, 0.0, -1.0),
|
||||
(Slysaurok, _) => (0.0, 0.0, -1.0),
|
||||
(Mindflayer, _) => (1.0, 1.0, 1.0),
|
||||
(Minotaur, _) => (0.0, 2.0, 0.0),
|
||||
(Tidalwarrior, _) => (-4.5, 0.0, 5.0),
|
||||
(Yeti, _) => (0.0, 2.0, 3.0),
|
||||
(Harvester, _) => (0.0, 1.5, 2.0),
|
||||
(Blueoni, _) => (0.0, 1.0, 3.0),
|
||||
(Redoni, _) => (0.0, 1.0, 3.0),
|
||||
(Cultistwarlord, _) => (-2.5, 2.0, -1.5),
|
||||
(Cultistwarlock, _) => (0.0, 1.5, 1.0),
|
||||
(Huskbrute, _) => (0.0, 3.0, 3.0),
|
||||
(Tursus, _) => (0.0, 2.0, 3.0),
|
||||
(Gigasfrost, _) => (1.0, 2.0, 4.0),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
@ -162,17 +162,25 @@ impl Skeleton for CharacterSkeleton {
|
||||
// FIXME: Should this be control_l_mat?
|
||||
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;
|
||||
Offsets {
|
||||
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()),
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: comp::Body::Humanoid(body)
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
position: mount_position,
|
||||
orientation: mount_orientation,
|
||||
scale: Vec3::one(),
|
||||
},
|
||||
primary_trail_mat: if weapon_trails {
|
||||
self.main_weapon_trail
|
||||
|
@ -107,7 +107,7 @@ use common::{
|
||||
},
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
link::Is,
|
||||
mounting::{Mount, VolumePos},
|
||||
mounting::{Mount, Rider, VolumePos},
|
||||
outcome::Outcome,
|
||||
resources::{Secs, Time},
|
||||
slowjob::SlowJobPool,
|
||||
@ -1500,7 +1500,8 @@ impl Hud {
|
||||
let me = info.viewpoint_entity;
|
||||
let poises = ecs.read_storage::<comp::Poise>();
|
||||
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 time = ecs.read_resource::<Time>();
|
||||
|
||||
@ -2266,7 +2267,12 @@ impl Hud {
|
||||
&uids,
|
||||
&inventories,
|
||||
poises.maybe(),
|
||||
(alignments.maybe(), is_mount.maybe(), stances.maybe()),
|
||||
(
|
||||
alignments.maybe(),
|
||||
is_mounts.maybe(),
|
||||
is_riders.maybe(),
|
||||
stances.maybe(),
|
||||
),
|
||||
)
|
||||
.join()
|
||||
.filter(|t| {
|
||||
@ -2289,7 +2295,7 @@ impl Hud {
|
||||
uid,
|
||||
inventory,
|
||||
poise,
|
||||
(alignment, is_mount, stance),
|
||||
(alignment, is_mount, is_rider, stance),
|
||||
)| {
|
||||
// Use interpolated position if available
|
||||
let pos = interpolated.map_or(pos.0, |i| i.pos);
|
||||
@ -2304,6 +2310,8 @@ impl Hud {
|
||||
let display_overhead_info = !is_me
|
||||
&& (is_mount.is_none()
|
||||
|| 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.selected_entity.map_or(false, |s| s.0 == entity)
|
||||
|| health.map_or(true, overhead::should_show_healthbar)
|
||||
|
@ -683,8 +683,7 @@ impl FigureMgr {
|
||||
);
|
||||
Some(
|
||||
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,
|
||||
)
|
||||
})
|
||||
@ -3157,45 +3156,52 @@ impl FigureMgr {
|
||||
physics.on_ground.is_some(),
|
||||
rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_liquid().is_some(), // In water
|
||||
is_rider.is_some() || is_volume_rider.is_some(),
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false) => anim::bird_medium::IdleAnimation::update_skeleton(
|
||||
&BirdMediumSkeleton::default(),
|
||||
time,
|
||||
state.state_time,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
),
|
||||
(true, false, false, _) => {
|
||||
anim::bird_medium::IdleAnimation::update_skeleton(
|
||||
&BirdMediumSkeleton::default(),
|
||||
time,
|
||||
state.state_time,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
)
|
||||
},
|
||||
// Running
|
||||
(true, true, false) => anim::bird_medium::RunAnimation::update_skeleton(
|
||||
&BirdMediumSkeleton::default(),
|
||||
(
|
||||
rel_vel,
|
||||
// TODO: Update to use the quaternion.
|
||||
ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
rel_avg_vel,
|
||||
state.acc_vel,
|
||||
),
|
||||
state.state_time,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
),
|
||||
(true, true, false, false) => {
|
||||
anim::bird_medium::RunAnimation::update_skeleton(
|
||||
&BirdMediumSkeleton::default(),
|
||||
(
|
||||
rel_vel,
|
||||
// TODO: Update to use the quaternion.
|
||||
ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
rel_avg_vel,
|
||||
state.acc_vel,
|
||||
),
|
||||
state.state_time,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
)
|
||||
},
|
||||
// In air
|
||||
(false, _, false) => anim::bird_medium::FlyAnimation::update_skeleton(
|
||||
&BirdMediumSkeleton::default(),
|
||||
(
|
||||
rel_vel,
|
||||
// TODO: Update to use the quaternion.
|
||||
ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
),
|
||||
state.state_time,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
),
|
||||
(false, _, false, false) => {
|
||||
anim::bird_medium::FlyAnimation::update_skeleton(
|
||||
&BirdMediumSkeleton::default(),
|
||||
(
|
||||
rel_vel,
|
||||
// TODO: Update to use the quaternion.
|
||||
ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
),
|
||||
state.state_time,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
)
|
||||
},
|
||||
// Swim
|
||||
(_, true, _) => anim::bird_medium::SwimAnimation::update_skeleton(
|
||||
(_, true, _, false) => anim::bird_medium::SwimAnimation::update_skeleton(
|
||||
&BirdMediumSkeleton::default(),
|
||||
time,
|
||||
state.state_time,
|
||||
|
@ -12,7 +12,7 @@ use common::{
|
||||
comp::{ship::figuredata::VOXEL_COLLIDER_MANIFEST, tool::ToolKind, Collider},
|
||||
consts::{MAX_PICKUP_RANGE, MAX_SPRITE_MOUNT_RANGE},
|
||||
link::Is,
|
||||
mounting::{Mount, VolumePos, VolumeRider},
|
||||
mounting::{Mount, Rider, VolumePos, VolumeRider},
|
||||
terrain::{Block, TerrainGrid, UnlockKind},
|
||||
uid::{Uid, UidAllocator},
|
||||
util::find_dist::{Cube, Cylinder, FindDist},
|
||||
@ -111,6 +111,7 @@ pub(super) fn select_interactable(
|
||||
collect_target: Option<Target<target::Collectable>>,
|
||||
entity_target: Option<Target<target::Entity>>,
|
||||
mine_target: Option<Target<target::Mine>>,
|
||||
viewpoint_entity: specs::Entity,
|
||||
scene: &Scene,
|
||||
) -> Option<Interactable> {
|
||||
span!(_guard, "select_interactable");
|
||||
@ -182,10 +183,12 @@ pub(super) fn select_interactable(
|
||||
let positions = ecs.read_storage::<comp::Pos>();
|
||||
let player_pos = positions.get(player_entity)?.0;
|
||||
|
||||
let uids = ecs.read_storage::<Uid>();
|
||||
let scales = ecs.read_storage::<comp::Scale>();
|
||||
let colliders = ecs.read_storage::<comp::Collider>();
|
||||
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 items = ecs.read_storage::<comp::Item>();
|
||||
let stats = ecs.read_storage::<comp::Stats>();
|
||||
@ -208,15 +211,16 @@ pub(super) fn select_interactable(
|
||||
scales.maybe(),
|
||||
colliders.maybe(),
|
||||
char_states.maybe(),
|
||||
!&is_mount,
|
||||
!&is_mounts,
|
||||
is_riders.maybe(),
|
||||
(stats.mask() | items.mask()).maybe(),
|
||||
)
|
||||
.join();
|
||||
|
||||
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, 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!
|
||||
//
|
||||
// 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
|
||||
// interacting with actual interactable entities that are closer by)
|
||||
// * 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 {
|
||||
return None;
|
||||
};
|
||||
@ -262,7 +269,7 @@ pub(super) fn select_interactable(
|
||||
let mut volumes_data = volumes_data.join();
|
||||
|
||||
let volumes = spacial_grid.0.in_circle_aabr(player_pos.xy(), search_dist)
|
||||
.filter(|&e| e != player_entity) // skip the player's entity
|
||||
.filter(|&e| e != player_entity) // skip the player's entity
|
||||
.filter_map(|e| volumes_data.get(e, &entities))
|
||||
.filter_map(|(entity, uid, body, interpolated, collider)| {
|
||||
let vol = collider.get_vol(&voxel_colliders_manifest)?;
|
||||
|
@ -610,13 +610,21 @@ impl PlayState for SessionState {
|
||||
|
||||
// Check to see whether we're aiming at anything
|
||||
let (build_target, collect_target, entity_target, mine_target, terrain_target) =
|
||||
targets_under_cursor(&client, cam_pos, cam_dir, can_build, is_mining);
|
||||
targets_under_cursor(
|
||||
&client,
|
||||
cam_pos,
|
||||
cam_dir,
|
||||
can_build,
|
||||
is_mining,
|
||||
self.viewpoint_entity().0,
|
||||
);
|
||||
|
||||
self.interactable = select_interactable(
|
||||
&client,
|
||||
collect_target,
|
||||
entity_target,
|
||||
mine_target,
|
||||
self.viewpoint_entity().0,
|
||||
&self.scene,
|
||||
);
|
||||
|
||||
|
@ -6,8 +6,9 @@ use common::{
|
||||
comp,
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
link::Is,
|
||||
mounting::Mount,
|
||||
mounting::{Mount, Rider},
|
||||
terrain::Block,
|
||||
uid::Uid,
|
||||
util::find_dist::{Cylinder, FindDist},
|
||||
vol::ReadVol,
|
||||
};
|
||||
@ -50,6 +51,7 @@ pub(super) fn targets_under_cursor(
|
||||
cam_dir: Vec3<f32>,
|
||||
can_build: bool,
|
||||
is_mining: bool,
|
||||
viewpoint_entity: specs::Entity,
|
||||
) -> (
|
||||
Option<Target<Build>>,
|
||||
Option<Target<Collectable>>,
|
||||
@ -115,6 +117,8 @@ pub(super) fn targets_under_cursor(
|
||||
.map(|(d, _)| d.min(MAX_TARGET_RANGE))
|
||||
.unwrap_or(MAX_TARGET_RANGE);
|
||||
|
||||
let uids = ecs.read_storage::<Uid>();
|
||||
|
||||
// Need to raycast by distance to cam
|
||||
// But also filter out by distance to the player (but this only needs to be done
|
||||
// on final result)
|
||||
@ -125,10 +129,11 @@ pub(super) fn targets_under_cursor(
|
||||
&ecs.read_storage::<comp::Body>(),
|
||||
ecs.read_storage::<comp::Item>().maybe(),
|
||||
!&ecs.read_storage::<Is<Mount>>(),
|
||||
ecs.read_storage::<Is<Rider>>().maybe(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(e, _, _, _, _, _)| *e != player_entity)
|
||||
.filter_map(|(e, p, s, b, i, _)| {
|
||||
.filter(|(e, _, _, _, _, _, _)| *e != player_entity)
|
||||
.filter_map(|(e, p, s, b, i, _, is_rider)| {
|
||||
const RADIUS_SCALE: f32 = 3.0;
|
||||
// TODO: use collider radius instead of body radius?
|
||||
let radius = s.map_or(1.0, |s| s.0) * b.max_radius() * RADIUS_SCALE;
|
||||
@ -137,8 +142,10 @@ pub(super) fn targets_under_cursor(
|
||||
// Distance squared from camera to the entity
|
||||
let dist_sqr = pos.distance_squared(cam_pos);
|
||||
// We only care about interacting with entities that contain items,
|
||||
// or are not inanimate (to trade with)
|
||||
if i.is_some() || !matches!(b, comp::Body::Object(_)) {
|
||||
// or are not inanimate (to trade with), and are not riding the player.
|
||||
let not_riding_player = is_rider
|
||||
.map_or(true, |is_rider| Some(&is_rider.mount) != uids.get(viewpoint_entity));
|
||||
if (i.is_some() || !matches!(b, comp::Body::Object(_))) && not_riding_player {
|
||||
Some((e, pos, radius, dist_sqr))
|
||||
} else {
|
||||
None
|
||||
|
Loading…
Reference in New Issue
Block a user