2022-01-15 18:22:28 +00:00
|
|
|
use crate::{
|
2023-05-12 20:03:44 +00:00
|
|
|
comp::{self, pet::is_mountable, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
2022-01-16 17:06:35 +00:00
|
|
|
link::{Is, Link, LinkHandle, Role},
|
2023-04-17 13:38:41 +00:00
|
|
|
terrain::{Block, TerrainGrid},
|
2022-01-16 17:06:35 +00:00
|
|
|
uid::{Uid, UidAllocator},
|
2023-04-17 13:38:41 +00:00
|
|
|
vol::ReadVol,
|
2022-01-15 18:22:28 +00:00
|
|
|
};
|
2023-04-17 13:38:41 +00:00
|
|
|
use hashbrown::HashSet;
|
2022-01-15 18:22:28 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2023-04-17 13:38:41 +00:00
|
|
|
use specs::{
|
2023-04-23 15:45:28 +00:00
|
|
|
saveload::MarkerAllocator, storage::GenericWriteStorage, Component, DenseVecStorage, Entities,
|
|
|
|
Entity, Read, ReadExpect, ReadStorage, Write, WriteStorage,
|
2023-04-17 13:38:41 +00:00
|
|
|
};
|
2022-01-15 18:22:28 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2023-01-14 22:47:05 +00:00
|
|
|
#[derive(Debug)]
|
2022-01-16 17:06:35 +00:00
|
|
|
pub enum MountingError {
|
|
|
|
NoSuchEntity,
|
|
|
|
NotMountable,
|
|
|
|
}
|
|
|
|
|
2022-01-15 18:22:28 +00:00
|
|
|
impl Link for Mounting {
|
|
|
|
type CreateData<'a> = (
|
|
|
|
Read<'a, UidAllocator>,
|
|
|
|
WriteStorage<'a, Is<Mount>>,
|
|
|
|
WriteStorage<'a, Is<Rider>>,
|
2023-04-17 13:38:41 +00:00
|
|
|
ReadStorage<'a, Is<VolumeRider>>,
|
2022-01-15 18:22:28 +00:00
|
|
|
);
|
2022-01-16 17:06:35 +00:00
|
|
|
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>,
|
|
|
|
);
|
|
|
|
type Error = MountingError;
|
|
|
|
type PersistData<'a> = (
|
|
|
|
Read<'a, UidAllocator>,
|
|
|
|
Entities<'a>,
|
|
|
|
ReadStorage<'a, comp::Health>,
|
2023-05-12 20:03:44 +00:00
|
|
|
ReadStorage<'a, comp::Body>,
|
2022-01-16 17:06:35 +00:00
|
|
|
ReadStorage<'a, Is<Mount>>,
|
|
|
|
ReadStorage<'a, Is<Rider>>,
|
2023-05-12 20:03:44 +00:00
|
|
|
ReadStorage<'a, comp::CharacterState>,
|
2022-01-16 17:06:35 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
fn create(
|
|
|
|
this: &LinkHandle<Self>,
|
2023-04-17 13:38:41 +00:00
|
|
|
(uid_allocator, mut is_mounts, mut is_riders, is_volume_rider): Self::CreateData<'_>,
|
2022-01-16 17:06:35 +00:00
|
|
|
) -> Result<(), Self::Error> {
|
|
|
|
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
2022-01-15 18:22:28 +00:00
|
|
|
|
2022-01-15 20:15:38 +00:00
|
|
|
if this.mount == this.rider {
|
|
|
|
// Forbid self-mounting
|
2022-01-16 17:06:35 +00:00
|
|
|
Err(MountingError::NotMountable)
|
2022-01-15 20:15:38 +00:00
|
|
|
} else if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
|
2023-01-14 22:47:05 +00:00
|
|
|
// Ensure that neither mount or rider are already part of a mounting
|
|
|
|
// relationship
|
2023-05-12 20:03:44 +00:00
|
|
|
if !is_mounts.contains(mount)
|
|
|
|
&& !is_riders.contains(rider)
|
|
|
|
&& !is_volume_rider.contains(rider)
|
|
|
|
{
|
2023-01-14 22:47:05 +00:00
|
|
|
let _ = is_mounts.insert(mount, this.make_role());
|
|
|
|
let _ = is_riders.insert(rider, this.make_role());
|
|
|
|
Ok(())
|
2022-01-15 18:22:28 +00:00
|
|
|
} else {
|
2022-01-16 17:06:35 +00:00
|
|
|
Err(MountingError::NotMountable)
|
2022-01-15 18:22:28 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-01-16 17:06:35 +00:00
|
|
|
Err(MountingError::NoSuchEntity)
|
2022-01-15 18:22:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-16 17:06:35 +00:00
|
|
|
fn persist(
|
|
|
|
this: &LinkHandle<Self>,
|
2023-05-12 20:03:44 +00:00
|
|
|
(uid_allocator, entities, healths, bodies, is_mounts, is_riders, character_states): Self::PersistData<'_>,
|
2022-01-16 17:06:35 +00:00
|
|
|
) -> bool {
|
|
|
|
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
2022-01-15 18:22:28 +00:00
|
|
|
|
|
|
|
if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
|
2022-01-16 17:06:35 +00:00
|
|
|
let is_alive = |entity| {
|
|
|
|
entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead)
|
|
|
|
};
|
2022-01-15 18:22:28 +00:00
|
|
|
|
2023-05-12 20:03:44 +00:00
|
|
|
let is_in_ridable_state = character_states
|
|
|
|
.get(mount)
|
|
|
|
.map_or(false, |cs| !matches!(cs, comp::CharacterState::Roll(_)));
|
|
|
|
|
2022-01-15 18:22:28 +00:00
|
|
|
// 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()
|
2023-05-12 20:03:44 +00:00
|
|
|
&& bodies.get(mount).map_or(false, |mount_body| {
|
|
|
|
is_mountable(mount_body, bodies.get(rider))
|
|
|
|
})
|
|
|
|
&& is_in_ridable_state
|
2022-01-15 18:22:28 +00:00
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-16 17:06:35 +00:00
|
|
|
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());
|
2022-01-15 18:22:28 +00:00
|
|
|
|
|
|
|
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)))
|
2022-01-16 17:06:35 +00:00
|
|
|
.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),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
});
|
2022-01-15 18:22:28 +00:00
|
|
|
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()))
|
2023-04-12 08:17:49 +00:00
|
|
|
.unwrap_or_else(|| terrain.find_ground(old_pos).map(|e| e as f32))
|
2022-01-16 17:06:35 +00:00
|
|
|
+ Vec3::new(0.5, 0.5, 0.0);
|
2022-07-31 21:01:10 +00:00
|
|
|
if let Some(force_update) = force_update.get_mut(rider) {
|
|
|
|
force_update.update();
|
|
|
|
}
|
2022-01-15 18:22:28 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2023-04-17 13:38:41 +00:00
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct VolumeRider;
|
|
|
|
|
|
|
|
impl Role for VolumeRider {
|
|
|
|
type Link = VolumeMounting;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
|
|
|
pub enum Volume {
|
|
|
|
Terrain,
|
|
|
|
Entity(Uid),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
|
|
|
pub struct VolumePos {
|
|
|
|
pub kind: Volume,
|
|
|
|
pub pos: Vec3<i32>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VolumePos {
|
|
|
|
pub fn terrain(block_pos: Vec3<i32>) -> Self {
|
|
|
|
Self {
|
|
|
|
kind: Volume::Terrain,
|
|
|
|
pos: block_pos,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn entity(block_pos: Vec3<i32>, uid: Uid) -> Self {
|
|
|
|
Self {
|
|
|
|
kind: Volume::Entity(uid),
|
|
|
|
pos: block_pos,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VolumePos {
|
2023-05-06 10:31:54 +00:00
|
|
|
/// Retrieves the block and matrix transformation for this `VolumeBlock`
|
|
|
|
///
|
|
|
|
/// The transform is located in the blocks minimum position relative to the
|
|
|
|
/// volume.
|
2023-04-17 13:38:41 +00:00
|
|
|
pub fn get_block_and_transform(
|
|
|
|
&self,
|
|
|
|
terrain: &TerrainGrid,
|
|
|
|
uid_allocator: &UidAllocator,
|
2023-04-23 15:45:28 +00:00
|
|
|
mut read_pos_and_ori: impl FnMut(Entity) -> Option<(comp::Pos, comp::Ori)>,
|
2023-04-17 13:38:41 +00:00
|
|
|
colliders: &ReadStorage<comp::Collider>,
|
|
|
|
) -> Option<(Mat4<f32>, Block)> {
|
|
|
|
match self.kind {
|
|
|
|
Volume::Terrain => Some((
|
|
|
|
Mat4::translation_3d(self.pos.as_()),
|
|
|
|
*terrain.get(self.pos).ok()?,
|
|
|
|
)),
|
|
|
|
Volume::Entity(uid) => {
|
|
|
|
uid_allocator
|
|
|
|
.retrieve_entity_internal(uid.0)
|
|
|
|
.and_then(|entity| {
|
|
|
|
let collider = colliders.get(entity)?;
|
2023-04-23 15:45:28 +00:00
|
|
|
let (pos, ori) = read_pos_and_ori(entity)?;
|
2023-04-17 13:38:41 +00:00
|
|
|
|
|
|
|
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
|
|
|
|
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
|
|
|
|
|
|
|
|
let block = *voxel_collider.volume().get(self.pos).ok()?;
|
|
|
|
|
|
|
|
let local_translation = voxel_collider.translation + self.pos.as_();
|
|
|
|
|
|
|
|
let trans = Mat4::from(ori.to_quat()).translated_3d(pos.0)
|
|
|
|
* Mat4::<f32>::translation_3d(local_translation);
|
|
|
|
|
|
|
|
Some((trans, block))
|
|
|
|
})
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-06 10:31:54 +00:00
|
|
|
/// Get the block at this `VolumePos`.
|
2023-04-17 13:38:41 +00:00
|
|
|
pub fn get_block(
|
|
|
|
&self,
|
|
|
|
terrain: &TerrainGrid,
|
|
|
|
uid_allocator: &UidAllocator,
|
|
|
|
colliders: &ReadStorage<comp::Collider>,
|
|
|
|
) -> Option<Block> {
|
|
|
|
match self.kind {
|
|
|
|
Volume::Terrain => Some(*terrain.get(self.pos).ok()?),
|
|
|
|
Volume::Entity(uid) => {
|
|
|
|
uid_allocator
|
|
|
|
.retrieve_entity_internal(uid.0)
|
|
|
|
.and_then(|entity| {
|
|
|
|
let collider = colliders.get(entity)?;
|
|
|
|
|
|
|
|
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
|
|
|
|
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
|
|
|
|
|
|
|
|
let block = *voxel_collider.volume().get(self.pos).ok()?;
|
|
|
|
|
|
|
|
Some(block)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct VolumeRiders {
|
|
|
|
riders: HashSet<Vec3<i32>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Component for VolumeRiders {
|
|
|
|
type Storage = DenseVecStorage<Self>;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct VolumeMounting {
|
|
|
|
pub pos: VolumePos,
|
|
|
|
pub block: Block,
|
|
|
|
pub rider: Uid,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Link for VolumeMounting {
|
|
|
|
type CreateData<'a> = (
|
|
|
|
Write<'a, VolumeRiders>,
|
|
|
|
WriteStorage<'a, VolumeRiders>,
|
|
|
|
WriteStorage<'a, Is<VolumeRider>>,
|
|
|
|
ReadStorage<'a, Is<Rider>>,
|
|
|
|
ReadStorage<'a, Is<Mount>>,
|
|
|
|
ReadExpect<'a, TerrainGrid>,
|
|
|
|
Read<'a, UidAllocator>,
|
|
|
|
ReadStorage<'a, comp::Collider>,
|
|
|
|
);
|
|
|
|
type DeleteData<'a> = (
|
|
|
|
Write<'a, VolumeRiders>,
|
|
|
|
WriteStorage<'a, VolumeRiders>,
|
|
|
|
WriteStorage<'a, Is<VolumeRider>>,
|
|
|
|
Read<'a, UidAllocator>,
|
|
|
|
);
|
|
|
|
type Error = MountingError;
|
|
|
|
type PersistData<'a> = (
|
|
|
|
Entities<'a>,
|
|
|
|
ReadStorage<'a, comp::Health>,
|
|
|
|
Read<'a, VolumeRiders>,
|
|
|
|
ReadStorage<'a, VolumeRiders>,
|
|
|
|
ReadStorage<'a, Is<VolumeRider>>,
|
|
|
|
ReadExpect<'a, TerrainGrid>,
|
|
|
|
Read<'a, UidAllocator>,
|
|
|
|
ReadStorage<'a, comp::Collider>,
|
|
|
|
);
|
|
|
|
|
|
|
|
fn create(
|
|
|
|
this: &LinkHandle<Self>,
|
|
|
|
(
|
|
|
|
mut terrain_riders,
|
|
|
|
mut volume_riders,
|
|
|
|
mut is_volume_riders,
|
|
|
|
is_riders,
|
|
|
|
is_mounts,
|
|
|
|
terrain_grid,
|
|
|
|
uid_allocator,
|
|
|
|
colliders,
|
|
|
|
): Self::CreateData<'_>,
|
|
|
|
) -> Result<(), Self::Error> {
|
|
|
|
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
|
|
|
|
|
|
|
let riders = match this.pos.kind {
|
|
|
|
Volume::Terrain => &mut *terrain_riders,
|
|
|
|
Volume::Entity(uid) => entity(uid)
|
|
|
|
.and_then(|entity| volume_riders.get_mut_or_default(entity))
|
|
|
|
.ok_or(MountingError::NoSuchEntity)?,
|
|
|
|
};
|
|
|
|
let rider = entity(this.rider).ok_or(MountingError::NoSuchEntity)?;
|
|
|
|
|
|
|
|
if !riders.riders.contains(&this.pos.pos)
|
|
|
|
&& !is_volume_riders.contains(rider)
|
|
|
|
&& !is_volume_riders.contains(rider)
|
|
|
|
&& !is_riders.contains(rider)
|
|
|
|
&& !is_mounts.contains(rider)
|
|
|
|
{
|
|
|
|
let block = this
|
|
|
|
.pos
|
|
|
|
.get_block(&terrain_grid, &uid_allocator, &colliders)
|
|
|
|
.ok_or(MountingError::NoSuchEntity)?;
|
|
|
|
|
|
|
|
if block == this.block {
|
|
|
|
let _ = is_volume_riders.insert(rider, this.make_role());
|
|
|
|
riders.riders.insert(this.pos.pos);
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(MountingError::NotMountable)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(MountingError::NotMountable)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn persist(
|
|
|
|
this: &LinkHandle<Self>,
|
|
|
|
(
|
|
|
|
entities,
|
|
|
|
healths,
|
|
|
|
terrain_riders,
|
|
|
|
volume_riders,
|
|
|
|
is_volume_riders,
|
|
|
|
terrain_grid,
|
|
|
|
uid_allocator,
|
|
|
|
colliders,
|
|
|
|
): Self::PersistData<'_>,
|
|
|
|
) -> bool {
|
|
|
|
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
|
|
|
let is_alive =
|
|
|
|
|entity| entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead);
|
|
|
|
let riders = match this.pos.kind {
|
|
|
|
Volume::Terrain => &*terrain_riders,
|
|
|
|
Volume::Entity(uid) => {
|
|
|
|
let Some(riders) = entity(uid)
|
|
|
|
.filter(|entity| is_alive(*entity))
|
|
|
|
.and_then(|entity| volume_riders.get(entity)) else {
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
riders
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
let rider_exists = entity(this.rider).map_or(false, |rider| {
|
|
|
|
is_volume_riders.contains(rider) && is_alive(rider)
|
|
|
|
});
|
|
|
|
let mount_spot_exists = riders.riders.contains(&this.pos.pos);
|
|
|
|
|
|
|
|
let block_exists = this
|
|
|
|
.pos
|
|
|
|
.get_block(&terrain_grid, &uid_allocator, &colliders)
|
|
|
|
.map_or(false, |block| block == this.block);
|
|
|
|
|
|
|
|
rider_exists && mount_spot_exists && block_exists
|
|
|
|
}
|
|
|
|
|
|
|
|
fn delete(
|
|
|
|
this: &LinkHandle<Self>,
|
|
|
|
(mut terrain_riders, mut volume_riders, mut is_rider, uid_allocator): Self::DeleteData<'_>,
|
|
|
|
) {
|
|
|
|
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
|
|
|
|
|
|
|
let riders = match this.pos.kind {
|
|
|
|
Volume::Terrain => Some(&mut *terrain_riders),
|
|
|
|
Volume::Entity(uid) => {
|
|
|
|
entity(uid).and_then(|entity| volume_riders.get_mut_or_default(entity))
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(riders) = riders {
|
|
|
|
riders.riders.remove(&this.pos.pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(entity) = entity(this.rider) {
|
|
|
|
is_rider.remove(entity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|