use crate::{ comp, link::{Is, Link, LinkHandle, Role}, terrain::TerrainGrid, uid::{Uid, UidAllocator}, }; use serde::{Deserialize, Serialize}; use specs::{saveload::MarkerAllocator, Entities, Read, ReadExpect, ReadStorage, WriteStorage}; 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, } pub enum MountingError { NoSuchEntity, NotMountable, } impl Link for Mounting { type CreateData<'a> = ( Read<'a, UidAllocator>, WriteStorage<'a, Is>, WriteStorage<'a, Is>, ); type DeleteData<'a> = ( Read<'a, UidAllocator>, WriteStorage<'a, Is>, WriteStorage<'a, Is>, 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>, ReadStorage<'a, Is>, ReadStorage<'a, Is>, ); fn create( this: &LinkHandle, (uid_allocator, mut is_mounts, mut is_riders): Self::CreateData<'_>, ) -> Result<(), Self::Error> { let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into()); if this.mount == this.rider { // Forbid self-mounting Err(MountingError::NotMountable) } else if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) { let can_mount_with = |entity| is_mounts.get(entity).is_none() && is_riders.get(entity).is_none(); // Ensure that neither mount or rider are already part of a mounting // relationship 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(MountingError::NotMountable) } } else { Err(MountingError::NoSuchEntity) } } fn persist( this: &LinkHandle, (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 } } fn delete( this: &LinkHandle, (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); let _ = force_update.insert(rider, comp::ForceUpdate); }); } }