Overhauled mounting to make it more reliable

This commit is contained in:
Joshua Barretto 2022-01-15 18:22:28 +00:00
parent 37c14037cb
commit b3e2d825ed
37 changed files with 446 additions and 232 deletions

View File

@ -44,6 +44,8 @@ use common::{
trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
uid::{Uid, UidAllocator},
vol::RectVolSize,
mounting::Rider,
link::Is,
};
use common_base::{prof_span, span};
use common_net::{
@ -1152,10 +1154,10 @@ impl Client {
)));
}
pub fn is_mounted(&self) -> bool {
pub fn is_riding(&self) -> bool {
self.state
.ecs()
.read_storage::<comp::Mounting>()
.read_storage::<Is<Rider>>()
.get(self.entity())
.is_some()
}

View File

@ -1,5 +1,10 @@
use crate::sync;
use common::{comp, resources::Time};
use common::{
comp,
resources::Time,
mounting::{Mount, Rider},
link::Is,
};
use serde::{Deserialize, Serialize};
use specs::WorldExt;
use std::marker::PhantomData;
@ -27,8 +32,8 @@ sum_type! {
Item(comp::Item),
Scale(comp::Scale),
Group(comp::Group),
MountState(comp::MountState),
Mounting(comp::Mounting),
IsMount(Is<Mount>),
IsRider(Is<Rider>),
Mass(comp::Mass),
Density(comp::Density),
Collider(comp::Collider),
@ -63,8 +68,8 @@ sum_type! {
Item(PhantomData<comp::Item>),
Scale(PhantomData<comp::Scale>),
Group(PhantomData<comp::Group>),
MountState(PhantomData<comp::MountState>),
Mounting(PhantomData<comp::Mounting>),
IsMount(PhantomData<Is<Mount>>),
IsRider(PhantomData<Is<Rider>>),
Mass(PhantomData<comp::Mass>),
Density(PhantomData<comp::Density>),
Collider(PhantomData<comp::Collider>),
@ -104,8 +109,8 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Group(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Mounting(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::IsMount(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::IsRider(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Density(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Collider(comp) => sync::handle_insert(comp, entity, world),
@ -149,8 +154,8 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Group(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Mounting(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::IsMount(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::IsRider(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Density(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Collider(comp) => sync::handle_modify(comp, entity, world),
@ -193,8 +198,8 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPhantom::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
EcsCompPhantom::Group(_) => sync::handle_remove::<comp::Group>(entity, world),
EcsCompPhantom::MountState(_) => sync::handle_remove::<comp::MountState>(entity, world),
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
EcsCompPhantom::IsMount(_) => sync::handle_remove::<Is<Mount>>(entity, world),
EcsCompPhantom::IsRider(_) => sync::handle_remove::<Is<Rider>>(entity, world),
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
EcsCompPhantom::Density(_) => sync::handle_remove::<comp::Density>(entity, world),
EcsCompPhantom::Collider(_) => sync::handle_remove::<comp::Collider>(entity, world),

View File

@ -730,8 +730,8 @@ impl Body {
)
}
/// Component of the mounting offset specific to the mountee
pub fn mountee_offset(&self) -> Vec3<f32> {
/// Component of the mounting offset specific to the mount
pub fn mount_offset(&self) -> Vec3<f32> {
match self {
Body::QuadrupedMedium(quadruped_medium) => {
match (quadruped_medium.species, quadruped_medium.body_type) {
@ -789,8 +789,8 @@ impl Body {
.into()
}
/// Component of the mounting offset specific to the mounter
pub fn mounter_offset(&self) -> Vec3<f32> {
/// Component of the mounting offset specific to the rider
pub fn rider_offset(&self) -> Vec3<f32> {
match self {
Body::Humanoid(_) => [0.0, 0.0, 0.0],
_ => [0.0, 0.0, 0.0],

View File

@ -284,20 +284,3 @@ impl Controller {
impl Component for Controller {
type Storage = IdvStorage<Self>;
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum MountState {
Unmounted,
MountedBy(Uid),
}
impl Component for MountState {
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Mounting(pub Uid);
impl Component for Mounting {
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
}

View File

@ -73,7 +73,7 @@ pub use self::{
combo::Combo,
controller::{
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputAttr,
InputKind, InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting,
InputKind, InventoryAction, InventoryEvent, InventoryManip,
UtteranceKind,
},
energy::Energy,

View File

@ -12,7 +12,9 @@
trait_alias,
type_alias_impl_trait,
extend_one,
arbitrary_enum_discriminant
arbitrary_enum_discriminant,
generic_associated_types,
arbitrary_self_types
)]
#![feature(hash_drain_filter)]
@ -78,6 +80,10 @@ pub mod uid;
#[cfg(not(target_arch = "wasm32"))] pub mod vol;
#[cfg(not(target_arch = "wasm32"))]
pub mod volumes;
#[cfg(not(target_arch = "wasm32"))]
pub mod link;
#[cfg(not(target_arch = "wasm32"))]
pub mod mounting;
#[cfg(not(target_arch = "wasm32"))]
pub use cached_spatial_grid::CachedSpatialGrid;

76
common/src/link.rs Normal file
View File

@ -0,0 +1,76 @@
use serde::{Deserialize, Serialize};
use specs::{SystemData, Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use std::{
ops::Deref,
sync::Arc,
};
pub trait Link: Sized + Send + Sync + 'static {
type CreateData<'a>: SystemData<'a>;
fn create(this: &LinkHandle<Self>, data: Self::CreateData<'_>) -> Result<(), ()>;
type PersistData<'a>: SystemData<'a>;
fn persist(this: &LinkHandle<Self>, data: Self::PersistData<'_>) -> bool;
type DeleteData<'a>: SystemData<'a>;
fn delete(this: &LinkHandle<Self>, data: Self::DeleteData<'_>);
}
pub trait Role {
type Link: Link;
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Is<R: Role> {
#[serde(bound(serialize = "R::Link: Serialize"))]
#[serde(bound(deserialize = "R::Link: Deserialize<'de>"))]
link: LinkHandle<R::Link>,
}
impl<R: Role> Is<R> {
pub fn delete(&self, data: <R::Link as Link>::DeleteData<'_>) {
R::Link::delete(&self.link, data)
}
}
impl<R: Role> Clone for Is<R> {
fn clone(&self) -> Self { Self { link: self.link.clone() } }
}
impl<R: Role> Deref for Is<R> {
type Target = R::Link;
fn deref(&self) -> &Self::Target { &self.link }
}
impl<R: Role + 'static> Component for Is<R>
where R::Link: Send + Sync + 'static
{
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
}
#[derive(Serialize, Deserialize, Debug)]
pub struct LinkHandle<L: Link> {
link: Arc<L>,
}
impl<L: Link> Clone for LinkHandle<L> {
fn clone(&self) -> Self {
Self { link: self.link.clone() }
}
}
impl<L: Link> LinkHandle<L> {
pub fn from_link(link: L) -> Self {
Self { link: Arc::new(link) }
}
pub fn make_role<R: Role<Link = L>>(&self) -> Is<R> {
Is { link: self.clone() }
}
}
impl<L: Link> Deref for LinkHandle<L> {
type Target = L;
fn deref(&self) -> &Self::Target { &self.link }
}

119
common/src/mounting.rs Normal file
View File

@ -0,0 +1,119 @@
use crate::{
comp,
link::{Is, Link, Role, LinkHandle},
uid::{Uid, UidAllocator},
terrain::TerrainGrid,
};
use specs::{Entities, ReadStorage, Read, ReadExpect, WriteStorage};
use specs::saveload::MarkerAllocator;
use serde::{Deserialize, Serialize};
use vek::*;
#[derive(Serialize, Deserialize, Debug)]
pub struct Rider;
impl Role for Rider {
type Link = Mounting;
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Mount;
impl Role for Mount {
type Link = Mounting;
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Mounting {
pub mount: Uid,
pub rider: Uid,
}
impl Link for Mounting {
type CreateData<'a> = (
Read<'a, UidAllocator>,
WriteStorage<'a, Is<Mount>>,
WriteStorage<'a, Is<Rider>>,
);
fn create(this: &LinkHandle<Self>, (uid_allocator, mut is_mounts, mut is_riders): Self::CreateData<'_>) -> Result<(), ()> {
let entity = |uid: Uid| uid_allocator
.retrieve_entity_internal(uid.into());
if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
let can_mount_with = |entity| is_mounts.get(entity).is_none() && is_riders.get(entity).is_none();
if can_mount_with(mount) && can_mount_with(rider) {
let _ = is_mounts.insert(mount, this.make_role());
let _ = is_riders.insert(rider, this.make_role());
Ok(())
} else {
Err(())
}
} else {
Err(())
}
}
type PersistData<'a> = (
Read<'a, UidAllocator>,
Entities<'a>,
ReadStorage<'a, comp::Health>,
ReadStorage<'a, Is<Mount>>,
ReadStorage<'a, Is<Rider>>,
);
fn persist(this: &LinkHandle<Self>, (uid_allocator, entities, healths, is_mounts, is_riders): Self::PersistData<'_>) -> bool {
let entity = |uid: Uid| uid_allocator
.retrieve_entity_internal(uid.into());
if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
let is_alive = |entity| entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead);
// 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()
} else {
false
}
}
type DeleteData<'a> = (
Read<'a, UidAllocator>,
WriteStorage<'a, Is<Mount>>,
WriteStorage<'a, Is<Rider>>,
WriteStorage<'a, comp::Pos>,
WriteStorage<'a, comp::ForceUpdate>,
ReadExpect<'a, TerrainGrid>,
);
fn delete(this: &LinkHandle<Self>, (uid_allocator, mut is_mounts, mut is_riders, mut positions, mut force_update, terrain): Self::DeleteData<'_>) {
let entity = |uid: Uid| uid_allocator
.retrieve_entity_internal(uid.into());
let mount = entity(this.mount);
let rider = entity(this.rider);
// Delete link components
mount.map(|mount| is_mounts.remove(mount));
rider.map(|rider| is_riders.remove(rider));
// Try to move the rider to a safe place when dismounting
let safe_pos = rider
.and_then(|rider| positions.get(rider).copied())
.filter(|rider_pos| terrain.is_space(rider_pos.0.map(|e| e.floor() as i32)))
.or_else(|| mount
.and_then(|mount| positions.get(mount).copied())
.filter(|mount_pos| terrain.is_space((mount_pos.0 + Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))));
rider
.and_then(|rider| Some(rider).zip(positions.get_mut(rider)))
.map(|(rider, pos)| {
let old_pos = pos.0.map(|e| e.floor() as i32);
pos.0 = safe_pos
.map(|p| p.0.map(|e| e.floor()))
.unwrap_or_else(|| terrain.find_space(old_pos).map(|e| e as f32))
+ Vec3::new(0.5, 0.5, 0.0);
force_update.insert(rider, comp::ForceUpdate);
});
}
}

View File

@ -162,19 +162,21 @@ impl TerrainGrid {
self.try_find_space(pos).unwrap_or(pos)
}
pub fn is_space(&self, pos: Vec3<i32>) -> bool {
(0..2).all(|z| {
self.get(pos + Vec3::unit_z() * z)
.map_or(true, |b| !b.is_solid())
})
}
pub fn try_find_space(&self, pos: Vec3<i32>) -> Option<Vec3<i32>> {
const SEARCH_DIST: i32 = 63;
(0..SEARCH_DIST * 2 + 1)
.map(|i| if i % 2 == 0 { i } else { -i } / 2)
.map(|z_diff| pos + Vec3::unit_z() * z_diff)
.find(|test_pos| {
self.get(test_pos - Vec3::unit_z())
.map_or(false, |b| b.is_filled())
&& (0..2).all(|z| {
self.get(test_pos + Vec3::unit_z() * z)
.map_or(true, |b| !b.is_solid())
})
})
.find(|pos| self.get(pos - Vec3::unit_z())
.map_or(false, |b| b.is_filled())
&& self.is_space(*pos))
}
}

View File

@ -19,6 +19,8 @@ use common::{
time::DayPeriod,
trade::Trades,
vol::{ReadVol, WriteVol},
link::Is,
mounting::{Mount, Rider},
};
use common_base::span;
use common_ecs::{PhysicsMetrics, SysMetrics};
@ -32,7 +34,10 @@ use specs::{
storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
};
use std::sync::Arc;
use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
use vek::*;
/// How much faster should an in-game day be compared to a real day?
@ -141,8 +146,8 @@ impl State {
ecs.register::<comp::LightEmitter>();
ecs.register::<comp::Item>();
ecs.register::<comp::Scale>();
ecs.register::<comp::Mounting>();
ecs.register::<comp::MountState>();
ecs.register::<Is<Mount>>();
ecs.register::<Is<Rider>>();
ecs.register::<comp::Mass>();
ecs.register::<comp::Density>();
ecs.register::<comp::Collider>();

View File

@ -7,7 +7,7 @@ use common::{
comp::{
self, character_state::OutputEvents, inventory::item::MaterialStatManifest,
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health,
Inventory, InventoryManip, Mass, Melee, Mounting, Ori, PhysicsState, Poise, Pos, SkillSet,
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, SkillSet,
StateUpdate, Stats, Vel,
},
event::{EventBus, LocalEvent, ServerEvent},
@ -19,6 +19,8 @@ use common::{
},
terrain::TerrainGrid,
uid::Uid,
mounting::Rider,
link::Is,
};
use common_ecs::{Job, Origin, Phase, System};
@ -37,7 +39,7 @@ pub struct ReadData<'a> {
melee_attacks: ReadStorage<'a, Melee>,
beams: ReadStorage<'a, Beam>,
uids: ReadStorage<'a, Uid>,
mountings: ReadStorage<'a, Mounting>,
is_riders: ReadStorage<'a, Is<Rider>>,
stats: ReadStorage<'a, Stats>,
skill_sets: ReadStorage<'a, SkillSet>,
active_abilities: ReadStorage<'a, ActiveAbilities>,
@ -110,7 +112,7 @@ impl<'a> System<'a> for Sys {
health,
body,
physics,
(stat, skill_set, active_abilities),
(stat, skill_set, active_abilities, is_rider),
combo,
) in (
&read_data.entities,
@ -131,6 +133,7 @@ impl<'a> System<'a> for Sys {
&read_data.stats,
&read_data.skill_sets,
&read_data.active_abilities,
read_data.is_riders.maybe(),
),
&read_data.combos,
)
@ -207,7 +210,7 @@ impl<'a> System<'a> for Sys {
// Mounted occurs after control actions have been handled
// If mounted, character state is controlled by mount
if let Some(Mounting(_)) = read_data.mountings.get(entity) {
if is_rider.is_some() {
let idle_state = CharacterState::Idle(idle::Data { is_sneaking: false });
if *join_struct.char_state != idle_state {
*join_struct.char_state = idle_state;

View File

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

View File

@ -1,11 +1,14 @@
use common::{
comp::{Body, Controller, MountState, Mounting, Ori, Pos, Vel},
comp::{Body, Controller, Ori, Pos, Vel},
uid::UidAllocator,
mounting::{Mount, Rider},
link::Is,
uid::Uid,
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{
saveload::{Marker, MarkerAllocator},
Entities, Join, Read, ReadStorage, WriteStorage,
Entities, Join, Read, ReadStorage, WriteStorage, WriteExpect,
};
use vek::*;
@ -17,9 +20,10 @@ impl<'a> System<'a> for Sys {
type SystemData = (
Read<'a, UidAllocator>,
Entities<'a>,
ReadStorage<'a, Uid>,
WriteStorage<'a, Controller>,
WriteStorage<'a, MountState>,
WriteStorage<'a, Mounting>,
ReadStorage<'a, Is<Rider>>,
ReadStorage<'a, Is<Mount>>,
WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
@ -35,70 +39,49 @@ impl<'a> System<'a> for Sys {
(
uid_allocator,
entities,
uids,
mut controllers,
mut mount_state,
mut mountings,
is_riders,
is_mounts,
mut positions,
mut velocities,
mut orientations,
bodies,
): Self::SystemData,
) {
// Mounted entities.
for (entity, mut mount_states, body) in (&entities, &mut mount_state, bodies.maybe()).join()
{
match *mount_states {
MountState::Unmounted => {},
MountState::MountedBy(mounter_uid) => {
// Note: currently controller events are not passed through since none of them
// are currently relevant to controlling the mounted entity
if let Some((inputs, queued_inputs, mounter)) = uid_allocator
.retrieve_entity_internal(mounter_uid.id())
.and_then(|mounter| {
controllers
.get(mounter)
.map(|c| (c.inputs.clone(), c.queued_inputs.clone(), mounter))
})
{
// TODO: consider joining on these? (remember we can use .maybe())
let pos = positions.get(entity).copied();
let ori = orientations.get(entity).copied();
let vel = velocities.get(entity).copied();
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
let mounter_body = bodies.get(mounter);
let mounting_offset = body.map_or(Vec3::unit_z(), Body::mountee_offset)
+ mounter_body.map_or(Vec3::zero(), Body::mounter_offset);
let _ = positions
.insert(mounter, Pos(pos.0 + ori.to_quat() * mounting_offset));
let _ = orientations.insert(mounter, ori);
let _ = velocities.insert(mounter, vel);
}
if let Some(controller) = controllers.get_mut(entity) {
*controller = Controller {
inputs,
queued_inputs,
..Default::default()
}
}
} else {
*mount_states = MountState::Unmounted;
}
},
}
}
// For each mount...
for (entity, is_mount, body) in (&entities, &is_mounts, bodies.maybe()).join() {
// ...find the rider...
let Some((inputs, queued_inputs, rider)) = uid_allocator
.retrieve_entity_internal(is_mount.rider.id())
.and_then(|rider| {
controllers
.get(rider)
.map(|c| (c.inputs.clone(), c.queued_inputs.clone(), rider))
})
else { continue };
let mut to_unmount = Vec::new();
for (entity, Mounting(mountee_uid)) in (&entities, &mountings).join() {
if uid_allocator
.retrieve_entity_internal(mountee_uid.id())
.filter(|mountee| entities.is_alive(*mountee))
.is_none()
{
to_unmount.push(entity);
// ...apply the mount's position/ori/velocity to the rider...
let pos = positions.get(entity).copied();
let ori = orientations.get(entity).copied();
let vel = velocities.get(entity).copied();
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
let mounter_body = bodies.get(rider);
let mounting_offset = body.map_or(Vec3::unit_z(), Body::mount_offset)
+ mounter_body.map_or(Vec3::zero(), Body::rider_offset);
let _ = positions
.insert(rider, Pos(pos.0 + ori.to_quat() * mounting_offset));
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) {
*controller = Controller {
inputs,
queued_inputs,
..Default::default()
}
}
}
for entity in to_unmount {
mountings.remove(entity);
}
}
}

View File

@ -2,7 +2,7 @@ use common::{
comp::{
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
fluid_dynamics::{Fluid, LiquidKind, Wings},
Body, CharacterState, Collider, Density, Mass, Mounting, Ori, PhysicsState, Pos,
Body, CharacterState, Collider, Density, Mass, Ori, PhysicsState, Pos,
PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel,
},
consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY},
@ -14,6 +14,8 @@ use common::{
uid::Uid,
util::{Projection, SpatialGrid},
vol::{BaseVol, ReadVol},
mounting::Rider,
link::Is,
};
use common_base::{prof_span, span};
use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System};
@ -111,7 +113,7 @@ pub struct PhysicsRead<'a> {
stickies: ReadStorage<'a, Sticky>,
masses: ReadStorage<'a, Mass>,
colliders: ReadStorage<'a, Collider>,
mountings: ReadStorage<'a, Mounting>,
is_ridings: ReadStorage<'a, Is<Rider>>,
projectiles: ReadStorage<'a, Projectile>,
char_states: ReadStorage<'a, CharacterState>,
bodies: ReadStorage<'a, Body>,
@ -169,7 +171,7 @@ impl<'a> PhysicsData<'a> {
&self.write.velocities,
&self.write.positions,
!&self.write.previous_phys_cache,
!&self.read.mountings,
!&self.read.is_ridings,
)
.join()
.map(|(e, _, _, _, _, _)| e)
@ -201,7 +203,7 @@ impl<'a> PhysicsData<'a> {
&self.read.colliders,
self.read.scales.maybe(),
self.read.char_states.maybe(),
!&self.read.mountings,
!&self.read.is_ridings,
)
.join()
{
@ -292,14 +294,13 @@ impl<'a> PhysicsData<'a> {
let lg2_large_cell_size = 6;
let radius_cutoff = 8;
let mut spatial_grid = SpatialGrid::new(lg2_cell_size, lg2_large_cell_size, radius_cutoff);
for (entity, pos, phys_cache, _, _, _, _) in (
for (entity, pos, phys_cache, _, _, _) in (
&read.entities,
&write.positions,
&write.previous_phys_cache,
write.velocities.mask(),
!&read.projectiles, // Not needed because they are skipped in the inner loop below
!&read.mountings,
read.colliders.mask(),
!&read.is_ridings,
)
.join()
{
@ -328,7 +329,7 @@ impl<'a> PhysicsData<'a> {
previous_phys_cache,
&read.masses,
&read.colliders,
!&read.mountings,
!&read.is_ridings,
read.stickies.maybe(),
&mut write.physics_states,
// TODO: if we need to avoid collisions for other things consider
@ -476,6 +477,7 @@ impl<'a> PhysicsData<'a> {
collider_other,
*mass,
*mass_other,
vel,
);
}
},
@ -570,7 +572,7 @@ impl<'a> PhysicsData<'a> {
write.physics_states.mask(),
!&write.pos_vel_ori_defers, // This is the one we are adding
write.previous_phys_cache.mask(),
!&read.mountings,
!&read.is_ridings,
)
.join()
.map(|t| (t.0, *t.2, *t.3, *t.4))
@ -601,7 +603,7 @@ impl<'a> PhysicsData<'a> {
&write.physics_states,
&read.masses,
&read.densities,
!&read.mountings,
!&read.is_ridings,
)
.par_join()
.for_each_init(
@ -730,7 +732,7 @@ impl<'a> PhysicsData<'a> {
&mut write.physics_states,
&mut write.pos_vel_ori_defers,
previous_phys_cache,
!&read.mountings,
!&read.is_ridings,
)
.par_join()
.filter(|tuple| tuple.3.is_voxel() == terrain_like_entities)
@ -1792,6 +1794,7 @@ fn resolve_e2e_collision(
collider_other: &Collider,
mass: Mass,
mass_other: Mass,
vel: &Vel,
) -> bool {
// Find the distance betwen our collider and
// collider we collide with and get vector of pushback.
@ -1869,7 +1872,9 @@ fn resolve_e2e_collision(
let distance_coefficient = collision_dist - diff.magnitude();
let force = ELASTIC_FORCE_COEFFICIENT * distance_coefficient * mass_coefficient;
*vel_delta += Vec3::from(diff.normalized()) * force * step_delta;
let diff = diff.normalized();
*vel_delta += Vec3::from(diff) * force * step_delta * vel.0.xy().try_normalized().map_or(1.0, |dir| diff.dot(-dir).max(0.0));
}
*collision_registered = true;

View File

@ -622,8 +622,7 @@ fn handle_make_npc(
.create_npc(pos, stats, skill_set, health, poise, inventory, body)
.with(alignment)
.with(scale)
.with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)))
.with(comp::MountState::Unmounted);
.with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)));
if let Some(agent) = agent {
entity_builder = entity_builder.with(agent);
@ -1168,7 +1167,6 @@ fn handle_spawn(
body,
)
.with(comp::Vel(vel))
.with(comp::MountState::Unmounted)
.with(alignment);
if ai {
@ -1251,7 +1249,6 @@ fn handle_spawn_training_dummy(
body,
)
.with(comp::Vel(vel))
.with(comp::MountState::Unmounted)
.build();
server.notify_client(

View File

@ -19,6 +19,8 @@ use common::{
terrain::{Block, SpriteKind},
uid::Uid,
vol::ReadVol,
mounting::{Mount, Rider, Mounting},
link::Is,
};
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
@ -97,62 +99,50 @@ pub fn handle_npc_interaction(server: &mut Server, interactor: EcsEntity, npc_en
}
}
/// FIXME: Make mounting more robust, avoid bidirectional links.
pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) {
pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) {
let state = server.state_mut();
if state
.ecs()
.read_storage::<comp::Mounting>()
.get(mounter)
.read_storage::<Is<Rider>>()
.get(rider)
.is_none()
{
let not_mounting_yet = matches!(
state.ecs().read_storage::<comp::MountState>().get(mountee),
Some(comp::MountState::Unmounted)
);
let not_mounting_yet = state
.ecs()
.read_storage::<Is<Mount>>()
.get(mount)
.is_none();
let within_range = || {
let positions = state.ecs().read_storage::<comp::Pos>();
within_mounting_range(positions.get(mounter), positions.get(mountee))
within_mounting_range(positions.get(rider), positions.get(mount))
};
let healths = state.ecs().read_storage::<comp::Health>();
let alive = |e| healths.get(e).map_or(true, |h| !h.is_dead);
if not_mounting_yet && within_range() && alive(mounter) && alive(mountee) {
if not_mounting_yet && within_range() && alive(rider) && alive(mount) {
let uids = state.ecs().read_storage::<Uid>();
if let (Some(mounter_uid), Some(mountee_uid)) =
(uids.get(mounter).copied(), uids.get(mountee).copied())
if let (Some(rider_uid), Some(mount_uid)) =
(uids.get(rider).copied(), uids.get(mount).copied())
{
drop(uids);
drop(healths);
// We know the entities must exist to be able to look up their UIDs, so these
// are guaranteed to work; hence we can ignore possible errors here.
state.write_component_ignore_entity_dead(
mountee,
comp::MountState::MountedBy(mounter_uid),
);
state.write_component_ignore_entity_dead(mounter, comp::Mounting(mountee_uid));
let _ = state.link(Mounting {
mount: mount_uid,
rider: rider_uid,
});
}
}
}
}
pub fn handle_unmount(server: &mut Server, mounter: EcsEntity) {
pub fn handle_unmount(server: &mut Server, rider: EcsEntity) {
let state = server.state_mut();
let mountee_entity = state
state
.ecs()
.write_storage::<comp::Mounting>()
.get(mounter)
.and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into()));
if let Some(mountee_entity) = mountee_entity {
state
.ecs()
.write_storage::<comp::MountState>()
.get_mut(mountee_entity)
.map(|mut ms| *ms = comp::MountState::Unmounted);
}
state.delete_component::<comp::Mounting>(mounter);
.write_storage::<Is<Rider>>()
.remove(rider);
}
/// FIXME: This code is dangerous and needs to be refactored. We can't just

View File

@ -663,6 +663,9 @@ impl Server {
// will be processed once handle_events() is called below
let disconnect_type = self.disconnect_all_clients_if_requested();
// Handle entity links (such as mounting)
self.state.maintain_links();
// Handle game events
frontend_events.append(&mut self.handle_events());

View File

@ -21,6 +21,8 @@ use common::{
resources::{Time, TimeOfDay},
slowjob::SlowJobPool,
uid::{Uid, UidAllocator},
link::{Is, Link, Role, LinkHandle},
mounting::Mounting,
};
use common_net::{
msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral},
@ -30,7 +32,7 @@ use common_state::State;
use rand::prelude::*;
use specs::{
saveload::MarkerAllocator, Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder,
Join, WorldExt,
Join, WorldExt, Component,
};
use std::time::Duration;
use tracing::{trace, warn};
@ -110,6 +112,10 @@ pub trait StateExt {
fn send_chat(&self, msg: comp::UnresolvedChatMsg);
fn notify_players(&self, msg: ServerGeneral);
fn notify_in_game_clients(&self, msg: ServerGeneral);
/// Create a new link between entities (see [`common::mounting`] for an example).
fn link<L: Link>(&mut self, link: L) -> Result<(), ()>;
/// Maintain active links between entities
fn maintain_links(&mut self);
/// Delete an entity, recording the deletion in [`DeletedEntities`]
fn delete_entity_recorded(
&mut self,
@ -294,7 +300,7 @@ impl StateExt for State {
.with(comp::Combo::default());
if mountable {
builder = builder.with(comp::MountState::Unmounted);
// TODO: Re-add mounting check
}
builder
}
@ -825,6 +831,37 @@ impl StateExt for State {
}
}
fn link<L: Link>(&mut self, link: L) -> Result<(), ()> {
let linker = LinkHandle::from_link(link);
L::create(
&linker,
self.ecs().system_data(),
)?;
self.ecs_mut()
.entry::<Vec<LinkHandle<L>>>()
.or_insert_with(Vec::new)
.push(linker);
Ok(())
}
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>>>() {
handles.retain(|link| if L::persist(link, state.ecs().system_data()) {
true
} else {
L::delete(link, state.ecs().system_data());
false
});
}
}
maintain_link::<Mounting>(self);
}
fn delete_entity_recorded(
&mut self,
entity: EcsEntity,

View File

@ -21,7 +21,7 @@ use common::{
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
AbilityInput, ActiveAbilities, Agent, Alignment, BehaviorCapability, BehaviorState, Body,
CharacterAbility, CharacterState, Combo, ControlAction, ControlEvent, Controller, Energy,
Health, HealthChange, InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori,
Health, HealthChange, InputKind, Inventory, InventoryAction, LightEmitter, Ori,
PhysicsState, Pos, Scale, SkillSet, Stats, UnresolvedChatMsg, UtteranceKind, Vel,
},
consts::GRAVITY,
@ -37,6 +37,8 @@ use common::{
uid::{Uid, UidAllocator},
util::Dir,
vol::ReadVol,
mounting::Mount,
link::Is,
};
use common_base::prof_span;
use common_ecs::{Job, Origin, ParMode, Phase, System};
@ -152,7 +154,7 @@ pub struct ReadData<'a> {
terrain: ReadExpect<'a, TerrainGrid>,
alignments: ReadStorage<'a, Alignment>,
bodies: ReadStorage<'a, Body>,
mount_states: ReadStorage<'a, MountState>,
is_mounts: ReadStorage<'a, Is<Mount>>,
time_of_day: Read<'a, TimeOfDay>,
light_emitter: ReadStorage<'a, LightEmitter>,
#[cfg(feature = "worldgen")]
@ -224,15 +226,9 @@ impl<'a> System<'a> for Sys {
&mut controllers,
read_data.light_emitter.maybe(),
read_data.groups.maybe(),
read_data.mount_states.maybe(),
!&read_data.is_mounts,
)
.par_join()
.filter(|(_, _, _, _, _, _, _, _, _, _, _, _, mount_state)| {
// Skip mounted entities
mount_state
.map(|ms| *ms == MountState::Unmounted)
.unwrap_or(true)
})
.for_each_init(
|| {
prof_span!(guard, "agent rayon job");

View File

@ -3,10 +3,12 @@ use common::{
comp::{
item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider,
Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass, MountState,
Mounting, Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel,
Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass,
Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel,
},
uid::Uid,
mounting::{Mount, Rider},
link::Is,
};
use common_ecs::{Job, Origin, Phase, System};
use common_net::{
@ -56,8 +58,8 @@ pub struct TrackedComps<'a> {
pub light_emitter: ReadStorage<'a, LightEmitter>,
pub item: ReadStorage<'a, Item>,
pub scale: ReadStorage<'a, Scale>,
pub mounting: ReadStorage<'a, Mounting>,
pub mount_state: ReadStorage<'a, MountState>,
pub is_mount: ReadStorage<'a, Is<Mount>>,
pub is_rider: ReadStorage<'a, Is<Rider>>,
pub group: ReadStorage<'a, Group>,
pub mass: ReadStorage<'a, Mass>,
pub density: ReadStorage<'a, Density>,
@ -137,11 +139,11 @@ impl<'a> TrackedComps<'a> {
.get(entity)
.copied()
.map(|c| comps.push(c.into()));
self.mounting
self.is_mount
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
self.mount_state
self.is_rider
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
@ -205,8 +207,8 @@ pub struct ReadTrackers<'a> {
pub inventory: ReadExpect<'a, UpdateTracker<Inventory>>,
pub item: ReadExpect<'a, UpdateTracker<Item>>,
pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
pub mount_state: ReadExpect<'a, UpdateTracker<MountState>>,
pub is_mount: ReadExpect<'a, UpdateTracker<Is<Mount>>>,
pub is_rider: ReadExpect<'a, UpdateTracker<Is<Rider>>>,
pub group: ReadExpect<'a, UpdateTracker<Group>>,
pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
pub density: ReadExpect<'a, UpdateTracker<Density>>,
@ -251,8 +253,8 @@ impl<'a> ReadTrackers<'a> {
)
.with_component(&comps.uid, &*self.item, &comps.item, filter)
.with_component(&comps.uid, &*self.scale, &comps.scale, filter)
.with_component(&comps.uid, &*self.mounting, &comps.mounting, filter)
.with_component(&comps.uid, &*self.mount_state, &comps.mount_state, filter)
.with_component(&comps.uid, &*self.is_mount, &comps.is_mount, filter)
.with_component(&comps.uid, &*self.is_rider, &comps.is_rider, filter)
.with_component(&comps.uid, &*self.group, &comps.group, filter)
.with_component(&comps.uid, &*self.mass, &comps.mass, filter)
.with_component(&comps.uid, &*self.density, &comps.density, filter)
@ -290,8 +292,8 @@ pub struct WriteTrackers<'a> {
light_emitter: WriteExpect<'a, UpdateTracker<LightEmitter>>,
item: WriteExpect<'a, UpdateTracker<Item>>,
scale: WriteExpect<'a, UpdateTracker<Scale>>,
mounting: WriteExpect<'a, UpdateTracker<Mounting>>,
mount_state: WriteExpect<'a, UpdateTracker<MountState>>,
is_mounts: WriteExpect<'a, UpdateTracker<Is<Mount>>>,
is_riders: WriteExpect<'a, UpdateTracker<Is<Rider>>>,
group: WriteExpect<'a, UpdateTracker<Group>>,
mass: WriteExpect<'a, UpdateTracker<Mass>>,
density: WriteExpect<'a, UpdateTracker<Density>>,
@ -323,8 +325,8 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
trackers.light_emitter.record_changes(&comps.light_emitter);
trackers.item.record_changes(&comps.item);
trackers.scale.record_changes(&comps.scale);
trackers.mounting.record_changes(&comps.mounting);
trackers.mount_state.record_changes(&comps.mount_state);
trackers.is_mounts.record_changes(&comps.is_mount);
trackers.is_riders.record_changes(&comps.is_rider);
trackers.group.record_changes(&comps.group);
trackers.mass.record_changes(&comps.mass);
trackers.density.record_changes(&comps.density);
@ -366,8 +368,8 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
log_counts!(light_emitter, "Light emitters");
log_counts!(item, "Items");
log_counts!(scale, "Scales");
log_counts!(mounting, "Mountings");
log_counts!(mount_state, "Mount States");
log_counts!(is_mounts, "mounts");
log_counts!(is_riders, "riders");
log_counts!(mass, "Masses");
log_counts!(mass, "Densities");
log_counts!(collider, "Colliders");
@ -396,8 +398,8 @@ pub fn register_trackers(world: &mut World) {
world.register_tracker::<LightEmitter>();
world.register_tracker::<Item>();
world.register_tracker::<Scale>();
world.register_tracker::<Mounting>();
world.register_tracker::<MountState>();
world.register_tracker::<Is<Mount>>();
world.register_tracker::<Is<Rider>>();
world.register_tracker::<Group>();
world.register_tracker::<Mass>();
world.register_tracker::<Density>();

View File

@ -31,10 +31,11 @@ tracy = ["profiling", "profiling/profile-with-tracy", "common-frontend/tracy", "
tracy-memory = ["tracy"] # enables heap profiling with tracy
plugins = ["client/plugins"]
egui-ui = ["voxygen-egui", "egui", "egui_wgpu_backend", "egui_winit_platform"]
shaderc-from-source = ["shaderc/build-from-source"]
# We don't ship egui with published release builds so a separate feature is required that excludes it.
default-publish = ["singleplayer", "native-dialog", "plugins", "simd"]
default = ["default-publish", "egui-ui", "hot-reloading"]
default = ["default-publish", "egui-ui", "hot-reloading", "shaderc-from-source"]
[dependencies]
client = {package = "veloren-client", path = "../client"}

View File

@ -132,7 +132,7 @@ impl Skeleton for BipedLargeSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::BipedLarge(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -73,7 +73,7 @@ impl Skeleton for BipedSmallSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::BipedSmall(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -101,7 +101,7 @@ impl Skeleton for BirdLargeSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::BirdLarge(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -55,7 +55,7 @@ impl Skeleton for BirdMediumSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::BirdMedium(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -149,7 +149,7 @@ impl Skeleton for CharacterSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::Humanoid(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -76,7 +76,7 @@ impl Skeleton for DragonSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::Dragon(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -55,7 +55,7 @@ impl Skeleton for FishMediumSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::FishMedium(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -46,7 +46,7 @@ impl Skeleton for FishSmallSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::FishSmall(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -78,7 +78,7 @@ impl Skeleton for GolemSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::Golem(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -44,7 +44,7 @@ impl Skeleton for ObjectSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::Object(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -74,7 +74,7 @@ impl Skeleton for QuadrupedLowSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::QuadrupedLow(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -60,7 +60,7 @@ impl Skeleton for QuadrupedSmallSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::QuadrupedSmall(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -47,7 +47,7 @@ impl Skeleton for ShipSkeleton {
mount_bone: Transform {
position: (base_mat * scale_mat).mul_point(
common::comp::Body::Ship(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
),

View File

@ -79,7 +79,7 @@ impl Skeleton for TheropodSkeleton {
// TODO: see quadruped_medium for how to animate this
mount_bone: Transform {
position: common::comp::Body::Theropod(body)
.mountee_offset()
.mount_offset()
.into_tuple()
.into(),
..Default::default()

View File

@ -33,13 +33,15 @@ use common::{
inventory::slot::EquipSlot,
item::{Hands, ItemKind, ToolKind},
Body, CharacterState, Collider, Controller, Health, Inventory, Item, Last, LightAnimation,
LightEmitter, Mounting, Ori, PhysicsState, PoiseState, Pos, Scale, Vel,
LightEmitter, Ori, PhysicsState, PoiseState, Pos, Scale, Vel,
},
resources::{DeltaTime, Time},
states::{equipping, idle, utils::StageSection, wielding},
terrain::TerrainChunk,
uid::UidAllocator,
vol::RectRasterableVol,
link::Is,
mounting::Rider,
};
use common_base::span;
use common_state::State;
@ -626,7 +628,7 @@ impl FigureMgr {
inventory,
item,
light_emitter,
mountings,
is_rider,
collider,
),
) in (
@ -644,7 +646,7 @@ impl FigureMgr {
ecs.read_storage::<Inventory>().maybe(),
ecs.read_storage::<Item>().maybe(),
ecs.read_storage::<LightEmitter>().maybe(),
ecs.read_storage::<Mounting>().maybe(),
ecs.read_storage::<Is<Rider>>().maybe(),
ecs.read_storage::<Collider>().maybe(),
)
.join()
@ -791,12 +793,12 @@ impl FigureMgr {
let hands = (active_tool_hand, second_tool_hand);
// If a mountee exists, get its animated mounting transform and its position
// If a mount exists, get its animated mounting transform and its position
let mount_transform_pos = (|| -> Option<_> {
let Mounting(entity) = mountings?;
let entity = uid_allocator.retrieve_entity_internal((*entity).into())?;
let body = *bodies.get(entity)?;
let meta = self.states.get_mut(&body, &entity)?;
let mount = is_rider?.mount;
let mount = uid_allocator.retrieve_entity_internal(mount.into())?;
let body = *bodies.get(mount)?;
let meta = self.states.get_mut(&body, &mount)?;
Some((meta.mount_transform, meta.mount_world_pos))
})();
@ -869,7 +871,7 @@ impl FigureMgr {
physics.on_ground.is_some(),
rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
physics.in_liquid().is_some(), // In water
mountings.is_some(),
is_rider.is_some(),
) {
// Standing
(true, false, false, false) => {
@ -5519,9 +5521,9 @@ impl FigureColLights {
pub struct FigureStateMeta {
lantern_offset: Option<anim::vek::Vec3<f32>>,
// Animation to be applied to mounter of this entity
// Animation to be applied to rider of this entity
mount_transform: anim::vek::Transform<f32, f32, f32>,
// Contains the position of this figure or if it is a mounter it will contain the mountee's
// Contains the position of this figure or if it is a rider it will contain the mount's
// mount_world_pos
// Unlike the interpolated position stored in the ecs this will be propagated along
// mount chains
@ -5635,7 +5637,7 @@ impl<S: Skeleton> FigureState<S> {
model: Option<&FigureModelEntry<N>>,
// TODO: there is the potential to drop the optional body from the common params and just
// use this one but we need to add a function to the skelton trait or something in order to
// get the mounter offset
// get the rider offset
skel_body: S::Body,
) {
// NOTE: As long as update() always gets called after get_or_create_model(), and
@ -5672,26 +5674,26 @@ impl<S: Skeleton> FigureState<S> {
let scale_mat = anim::vek::Mat4::scaling_3d(anim::vek::Vec3::from(*scale));
if let Some((transform, _)) = *mount_transform_pos {
// Note: if we had a way to compute a "default" transform of the bones then in
// the animations we could make use of the mountee_offset from common by
// computing what the offset of the mounter is from the mounted
// bone in its default position when the mounter has the mount
// the animations we could make use of the mount_offset from common by
// computing what the offset of the rider is from the mounted
// bone in its default position when the rider has the mount
// offset in common applied to it. Since we don't have this
// right now we instead need to recreate the same effect in the
// animations and keep it in sync.
//
// Component of mounting offset specific to the mounter.
let mounter_offset = anim::vek::Mat4::<f32>::translation_3d(
body.map_or_else(Vec3::zero, |b| b.mounter_offset()),
// Component of mounting offset specific to the rider.
let rider_offset = anim::vek::Mat4::<f32>::translation_3d(
body.map_or_else(Vec3::zero, |b| b.rider_offset()),
);
// NOTE: It is kind of a hack to use this entity's ori here if it is
// mounted on another but this happens to match the ori of the
// mountee so it works, change this if it causes jankiness in the future.
// mount so it works, change this if it causes jankiness in the future.
let transform = anim::vek::Transform {
orientation: *ori * transform.orientation,
..transform
};
anim::vek::Mat4::from(transform) * mounter_offset * scale_mat
anim::vek::Mat4::from(transform) * rider_offset * scale_mat
} else {
let ori_mat = anim::vek::Mat4::from(*ori);
ori_mat * scale_mat

View File

@ -27,6 +27,8 @@ use common::{
trade::TradeResult,
util::{Dir, Plane},
vol::ReadVol,
link::Is,
mounting::{Mounting, Mount},
};
use common_base::{prof_span, span};
use common_net::{
@ -670,7 +672,7 @@ impl PlayState for SessionState {
},
GameInput::Mount if state => {
let mut client = self.client.borrow_mut();
if client.is_mounted() {
if client.is_riding() {
client.unmount();
} else {
let player_pos = client
@ -683,16 +685,11 @@ impl PlayState for SessionState {
let closest_mountable_entity = (
&client.state().ecs().entities(),
&client.state().ecs().read_storage::<comp::Pos>(),
&client
.state()
.ecs()
.read_storage::<comp::MountState>(),
// TODO: More cleverly filter by things that can actually be mounted
!&client.state().ecs().read_storage::<Is<Mount>>(),
)
.join()
.filter(|(entity, _, mount_state)| {
*entity != client.entity()
&& **mount_state == comp::MountState::Unmounted
})
.filter(|(entity, _, mount_state)| *entity != client.entity())
.map(|(entity, pos, _)| {
(entity, player_pos.0.distance_squared(pos.0))
})