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

View File

@ -1,5 +1,10 @@
use crate::sync; use crate::sync;
use common::{comp, resources::Time}; use common::{
comp,
resources::Time,
mounting::{Mount, Rider},
link::Is,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::WorldExt; use specs::WorldExt;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -27,8 +32,8 @@ sum_type! {
Item(comp::Item), Item(comp::Item),
Scale(comp::Scale), Scale(comp::Scale),
Group(comp::Group), Group(comp::Group),
MountState(comp::MountState), IsMount(Is<Mount>),
Mounting(comp::Mounting), IsRider(Is<Rider>),
Mass(comp::Mass), Mass(comp::Mass),
Density(comp::Density), Density(comp::Density),
Collider(comp::Collider), Collider(comp::Collider),
@ -63,8 +68,8 @@ sum_type! {
Item(PhantomData<comp::Item>), Item(PhantomData<comp::Item>),
Scale(PhantomData<comp::Scale>), Scale(PhantomData<comp::Scale>),
Group(PhantomData<comp::Group>), Group(PhantomData<comp::Group>),
MountState(PhantomData<comp::MountState>), IsMount(PhantomData<Is<Mount>>),
Mounting(PhantomData<comp::Mounting>), IsRider(PhantomData<Is<Rider>>),
Mass(PhantomData<comp::Mass>), Mass(PhantomData<comp::Mass>),
Density(PhantomData<comp::Density>), Density(PhantomData<comp::Density>),
Collider(PhantomData<comp::Collider>), Collider(PhantomData<comp::Collider>),
@ -104,8 +109,8 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Scale(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::Group(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::IsMount(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Mounting(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::Mass(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Density(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), 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::Item(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Scale(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::Group(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::IsMount(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Mounting(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::Mass(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Density(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), 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::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world), EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
EcsCompPhantom::Group(_) => sync::handle_remove::<comp::Group>(entity, world), EcsCompPhantom::Group(_) => sync::handle_remove::<comp::Group>(entity, world),
EcsCompPhantom::MountState(_) => sync::handle_remove::<comp::MountState>(entity, world), EcsCompPhantom::IsMount(_) => sync::handle_remove::<Is<Mount>>(entity, world),
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world), EcsCompPhantom::IsRider(_) => sync::handle_remove::<Is<Rider>>(entity, world),
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world), EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
EcsCompPhantom::Density(_) => sync::handle_remove::<comp::Density>(entity, world), EcsCompPhantom::Density(_) => sync::handle_remove::<comp::Density>(entity, world),
EcsCompPhantom::Collider(_) => sync::handle_remove::<comp::Collider>(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 /// Component of the mounting offset specific to the mount
pub fn mountee_offset(&self) -> Vec3<f32> { pub fn mount_offset(&self) -> Vec3<f32> {
match self { match self {
Body::QuadrupedMedium(quadruped_medium) => { Body::QuadrupedMedium(quadruped_medium) => {
match (quadruped_medium.species, quadruped_medium.body_type) { match (quadruped_medium.species, quadruped_medium.body_type) {
@ -789,8 +789,8 @@ impl Body {
.into() .into()
} }
/// Component of the mounting offset specific to the mounter /// Component of the mounting offset specific to the rider
pub fn mounter_offset(&self) -> Vec3<f32> { pub fn rider_offset(&self) -> Vec3<f32> {
match self { match self {
Body::Humanoid(_) => [0.0, 0.0, 0.0], Body::Humanoid(_) => [0.0, 0.0, 0.0],
_ => [0.0, 0.0, 0.0], _ => [0.0, 0.0, 0.0],

View File

@ -284,20 +284,3 @@ impl Controller {
impl Component for Controller { impl Component for Controller {
type Storage = IdvStorage<Self>; 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, combo::Combo,
controller::{ controller::{
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputAttr, Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputAttr,
InputKind, InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting, InputKind, InventoryAction, InventoryEvent, InventoryManip,
UtteranceKind, UtteranceKind,
}, },
energy::Energy, energy::Energy,

View File

@ -12,7 +12,9 @@
trait_alias, trait_alias,
type_alias_impl_trait, type_alias_impl_trait,
extend_one, extend_one,
arbitrary_enum_discriminant arbitrary_enum_discriminant,
generic_associated_types,
arbitrary_self_types
)] )]
#![feature(hash_drain_filter)] #![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 vol;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod volumes; pub mod volumes;
#[cfg(not(target_arch = "wasm32"))]
pub mod link;
#[cfg(not(target_arch = "wasm32"))]
pub mod mounting;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use cached_spatial_grid::CachedSpatialGrid; 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) 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>> { pub fn try_find_space(&self, pos: Vec3<i32>) -> Option<Vec3<i32>> {
const SEARCH_DIST: i32 = 63; const SEARCH_DIST: i32 = 63;
(0..SEARCH_DIST * 2 + 1) (0..SEARCH_DIST * 2 + 1)
.map(|i| if i % 2 == 0 { i } else { -i } / 2) .map(|i| if i % 2 == 0 { i } else { -i } / 2)
.map(|z_diff| pos + Vec3::unit_z() * z_diff) .map(|z_diff| pos + Vec3::unit_z() * z_diff)
.find(|test_pos| { .find(|pos| self.get(pos - Vec3::unit_z())
self.get(test_pos - Vec3::unit_z())
.map_or(false, |b| b.is_filled()) .map_or(false, |b| b.is_filled())
&& (0..2).all(|z| { && self.is_space(*pos))
self.get(test_pos + Vec3::unit_z() * z)
.map_or(true, |b| !b.is_solid())
})
})
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,8 @@ use common::{
terrain::{Block, SpriteKind}, terrain::{Block, SpriteKind},
uid::Uid, uid::Uid,
vol::ReadVol, vol::ReadVol,
mounting::{Mount, Rider, Mounting},
link::Is,
}; };
use common_net::{msg::ServerGeneral, sync::WorldSyncExt}; 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, rider: EcsEntity, mount: EcsEntity) {
pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) {
let state = server.state_mut(); let state = server.state_mut();
if state if state
.ecs() .ecs()
.read_storage::<comp::Mounting>() .read_storage::<Is<Rider>>()
.get(mounter) .get(rider)
.is_none() .is_none()
{ {
let not_mounting_yet = matches!( let not_mounting_yet = state
state.ecs().read_storage::<comp::MountState>().get(mountee), .ecs()
Some(comp::MountState::Unmounted) .read_storage::<Is<Mount>>()
); .get(mount)
.is_none();
let within_range = || { let within_range = || {
let positions = state.ecs().read_storage::<comp::Pos>(); 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 healths = state.ecs().read_storage::<comp::Health>();
let alive = |e| healths.get(e).map_or(true, |h| !h.is_dead); 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>(); let uids = state.ecs().read_storage::<Uid>();
if let (Some(mounter_uid), Some(mountee_uid)) = if let (Some(rider_uid), Some(mount_uid)) =
(uids.get(mounter).copied(), uids.get(mountee).copied()) (uids.get(rider).copied(), uids.get(mount).copied())
{ {
drop(uids); drop(uids);
drop(healths); drop(healths);
// We know the entities must exist to be able to look up their UIDs, so these let _ = state.link(Mounting {
// are guaranteed to work; hence we can ignore possible errors here. mount: mount_uid,
state.write_component_ignore_entity_dead( rider: rider_uid,
mountee, });
comp::MountState::MountedBy(mounter_uid),
);
state.write_component_ignore_entity_dead(mounter, comp::Mounting(mountee_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 state = server.state_mut();
let mountee_entity = 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 state
.ecs() .ecs()
.write_storage::<comp::MountState>() .write_storage::<Is<Rider>>()
.get_mut(mountee_entity) .remove(rider);
.map(|mut ms| *ms = comp::MountState::Unmounted);
}
state.delete_component::<comp::Mounting>(mounter);
} }
/// FIXME: This code is dangerous and needs to be refactored. We can't just /// 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 // will be processed once handle_events() is called below
let disconnect_type = self.disconnect_all_clients_if_requested(); let disconnect_type = self.disconnect_all_clients_if_requested();
// Handle entity links (such as mounting)
self.state.maintain_links();
// Handle game events // Handle game events
frontend_events.append(&mut self.handle_events()); frontend_events.append(&mut self.handle_events());

View File

@ -21,6 +21,8 @@ use common::{
resources::{Time, TimeOfDay}, resources::{Time, TimeOfDay},
slowjob::SlowJobPool, slowjob::SlowJobPool,
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
link::{Is, Link, Role, LinkHandle},
mounting::Mounting,
}; };
use common_net::{ use common_net::{
msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral}, msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral},
@ -30,7 +32,7 @@ use common_state::State;
use rand::prelude::*; use rand::prelude::*;
use specs::{ use specs::{
saveload::MarkerAllocator, Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder, saveload::MarkerAllocator, Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder,
Join, WorldExt, Join, WorldExt, Component,
}; };
use std::time::Duration; use std::time::Duration;
use tracing::{trace, warn}; use tracing::{trace, warn};
@ -110,6 +112,10 @@ pub trait StateExt {
fn send_chat(&self, msg: comp::UnresolvedChatMsg); fn send_chat(&self, msg: comp::UnresolvedChatMsg);
fn notify_players(&self, msg: ServerGeneral); fn notify_players(&self, msg: ServerGeneral);
fn notify_in_game_clients(&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`] /// Delete an entity, recording the deletion in [`DeletedEntities`]
fn delete_entity_recorded( fn delete_entity_recorded(
&mut self, &mut self,
@ -294,7 +300,7 @@ impl StateExt for State {
.with(comp::Combo::default()); .with(comp::Combo::default());
if mountable { if mountable {
builder = builder.with(comp::MountState::Unmounted); // TODO: Re-add mounting check
} }
builder 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( fn delete_entity_recorded(
&mut self, &mut self,
entity: EcsEntity, entity: EcsEntity,

View File

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

View File

@ -3,10 +3,12 @@ use common::{
comp::{ comp::{
item::{tool::AbilityMap, MaterialStatManifest}, item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider, ActiveAbilities, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider,
Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass, MountState, Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass,
Mounting, Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel, Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel,
}, },
uid::Uid, uid::Uid,
mounting::{Mount, Rider},
link::Is,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::{ use common_net::{
@ -56,8 +58,8 @@ pub struct TrackedComps<'a> {
pub light_emitter: ReadStorage<'a, LightEmitter>, pub light_emitter: ReadStorage<'a, LightEmitter>,
pub item: ReadStorage<'a, Item>, pub item: ReadStorage<'a, Item>,
pub scale: ReadStorage<'a, Scale>, pub scale: ReadStorage<'a, Scale>,
pub mounting: ReadStorage<'a, Mounting>, pub is_mount: ReadStorage<'a, Is<Mount>>,
pub mount_state: ReadStorage<'a, MountState>, pub is_rider: ReadStorage<'a, Is<Rider>>,
pub group: ReadStorage<'a, Group>, pub group: ReadStorage<'a, Group>,
pub mass: ReadStorage<'a, Mass>, pub mass: ReadStorage<'a, Mass>,
pub density: ReadStorage<'a, Density>, pub density: ReadStorage<'a, Density>,
@ -137,11 +139,11 @@ impl<'a> TrackedComps<'a> {
.get(entity) .get(entity)
.copied() .copied()
.map(|c| comps.push(c.into())); .map(|c| comps.push(c.into()));
self.mounting self.is_mount
.get(entity) .get(entity)
.cloned() .cloned()
.map(|c| comps.push(c.into())); .map(|c| comps.push(c.into()));
self.mount_state self.is_rider
.get(entity) .get(entity)
.cloned() .cloned()
.map(|c| comps.push(c.into())); .map(|c| comps.push(c.into()));
@ -205,8 +207,8 @@ pub struct ReadTrackers<'a> {
pub inventory: ReadExpect<'a, UpdateTracker<Inventory>>, pub inventory: ReadExpect<'a, UpdateTracker<Inventory>>,
pub item: ReadExpect<'a, UpdateTracker<Item>>, pub item: ReadExpect<'a, UpdateTracker<Item>>,
pub scale: ReadExpect<'a, UpdateTracker<Scale>>, pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>, pub is_mount: ReadExpect<'a, UpdateTracker<Is<Mount>>>,
pub mount_state: ReadExpect<'a, UpdateTracker<MountState>>, pub is_rider: ReadExpect<'a, UpdateTracker<Is<Rider>>>,
pub group: ReadExpect<'a, UpdateTracker<Group>>, pub group: ReadExpect<'a, UpdateTracker<Group>>,
pub mass: ReadExpect<'a, UpdateTracker<Mass>>, pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
pub density: ReadExpect<'a, UpdateTracker<Density>>, 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.item, &comps.item, filter)
.with_component(&comps.uid, &*self.scale, &comps.scale, filter) .with_component(&comps.uid, &*self.scale, &comps.scale, filter)
.with_component(&comps.uid, &*self.mounting, &comps.mounting, filter) .with_component(&comps.uid, &*self.is_mount, &comps.is_mount, filter)
.with_component(&comps.uid, &*self.mount_state, &comps.mount_state, 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.group, &comps.group, filter)
.with_component(&comps.uid, &*self.mass, &comps.mass, filter) .with_component(&comps.uid, &*self.mass, &comps.mass, filter)
.with_component(&comps.uid, &*self.density, &comps.density, filter) .with_component(&comps.uid, &*self.density, &comps.density, filter)
@ -290,8 +292,8 @@ pub struct WriteTrackers<'a> {
light_emitter: WriteExpect<'a, UpdateTracker<LightEmitter>>, light_emitter: WriteExpect<'a, UpdateTracker<LightEmitter>>,
item: WriteExpect<'a, UpdateTracker<Item>>, item: WriteExpect<'a, UpdateTracker<Item>>,
scale: WriteExpect<'a, UpdateTracker<Scale>>, scale: WriteExpect<'a, UpdateTracker<Scale>>,
mounting: WriteExpect<'a, UpdateTracker<Mounting>>, is_mounts: WriteExpect<'a, UpdateTracker<Is<Mount>>>,
mount_state: WriteExpect<'a, UpdateTracker<MountState>>, is_riders: WriteExpect<'a, UpdateTracker<Is<Rider>>>,
group: WriteExpect<'a, UpdateTracker<Group>>, group: WriteExpect<'a, UpdateTracker<Group>>,
mass: WriteExpect<'a, UpdateTracker<Mass>>, mass: WriteExpect<'a, UpdateTracker<Mass>>,
density: WriteExpect<'a, UpdateTracker<Density>>, 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.light_emitter.record_changes(&comps.light_emitter);
trackers.item.record_changes(&comps.item); trackers.item.record_changes(&comps.item);
trackers.scale.record_changes(&comps.scale); trackers.scale.record_changes(&comps.scale);
trackers.mounting.record_changes(&comps.mounting); trackers.is_mounts.record_changes(&comps.is_mount);
trackers.mount_state.record_changes(&comps.mount_state); trackers.is_riders.record_changes(&comps.is_rider);
trackers.group.record_changes(&comps.group); trackers.group.record_changes(&comps.group);
trackers.mass.record_changes(&comps.mass); trackers.mass.record_changes(&comps.mass);
trackers.density.record_changes(&comps.density); 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!(light_emitter, "Light emitters");
log_counts!(item, "Items"); log_counts!(item, "Items");
log_counts!(scale, "Scales"); log_counts!(scale, "Scales");
log_counts!(mounting, "Mountings"); log_counts!(is_mounts, "mounts");
log_counts!(mount_state, "Mount States"); log_counts!(is_riders, "riders");
log_counts!(mass, "Masses"); log_counts!(mass, "Masses");
log_counts!(mass, "Densities"); log_counts!(mass, "Densities");
log_counts!(collider, "Colliders"); log_counts!(collider, "Colliders");
@ -396,8 +398,8 @@ pub fn register_trackers(world: &mut World) {
world.register_tracker::<LightEmitter>(); world.register_tracker::<LightEmitter>();
world.register_tracker::<Item>(); world.register_tracker::<Item>();
world.register_tracker::<Scale>(); world.register_tracker::<Scale>();
world.register_tracker::<Mounting>(); world.register_tracker::<Is<Mount>>();
world.register_tracker::<MountState>(); world.register_tracker::<Is<Rider>>();
world.register_tracker::<Group>(); world.register_tracker::<Group>();
world.register_tracker::<Mass>(); world.register_tracker::<Mass>();
world.register_tracker::<Density>(); 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 tracy-memory = ["tracy"] # enables heap profiling with tracy
plugins = ["client/plugins"] plugins = ["client/plugins"]
egui-ui = ["voxygen-egui", "egui", "egui_wgpu_backend", "egui_winit_platform"] 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. # 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-publish = ["singleplayer", "native-dialog", "plugins", "simd"]
default = ["default-publish", "egui-ui", "hot-reloading"] default = ["default-publish", "egui-ui", "hot-reloading", "shaderc-from-source"]
[dependencies] [dependencies]
client = {package = "veloren-client", path = "../client"} 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 // TODO: see quadruped_medium for how to animate this
mount_bone: Transform { mount_bone: Transform {
position: common::comp::Body::BipedLarge(body) position: common::comp::Body::BipedLarge(body)
.mountee_offset() .mount_offset()
.into_tuple() .into_tuple()
.into(), .into(),
..Default::default() ..Default::default()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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