use common::{ comp::{Body, Collider, ControlAction, Controller, InputKind, Ori, Pos, Scale, Vel}, link::Is, mounting::{Mount, VolumeRider}, terrain::TerrainGrid, uid::IdMaps, }; use common_ecs::{Job, Origin, Phase, System}; use specs::{Entities, Join, LendJoin, Read, ReadExpect, ReadStorage, WriteStorage}; use tracing::error; use vek::*; /// This system is responsible for controlling mounts #[derive(Default)] pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( Read<'a, IdMaps>, ReadExpect<'a, TerrainGrid>, Entities<'a>, WriteStorage<'a, Controller>, ReadStorage<'a, Is>, ReadStorage<'a, Is>, WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, ReadStorage<'a, Body>, ReadStorage<'a, Scale>, ReadStorage<'a, Collider>, ); const NAME: &'static str = "mount"; const ORIGIN: Origin = Origin::Common; const PHASE: Phase = Phase::Create; fn run( _job: &mut Job, ( id_maps, terrain, entities, mut controllers, is_mounts, is_volume_riders, mut positions, mut velocities, mut orientations, bodies, scales, colliders, ): Self::SystemData, ) { // For each mount... for (entity, is_mount, body) in (&entities, &is_mounts, bodies.maybe()).join() { // ...find the rider... let Some((inputs_and_actions, rider)) = id_maps.uid_entity(is_mount.rider).and_then(|rider| { controllers.get_mut(rider).map(|c| { ( // Only take inputs and actions from the rider if the mount is not // intelligent (TODO: expand the definition of 'intelligent'). if !matches!(body, Some(Body::Humanoid(_))) { let actions = c .actions .extract_if(|action| match action { ControlAction::StartInput { input: i, .. } | ControlAction::CancelInput(i) => matches!( i, InputKind::Jump | InputKind::Fly | InputKind::Roll ), _ => false, }) .collect(); Some((c.inputs.clone(), actions)) } else { None }, rider, ) }) }) else { continue; }; // ...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) * scales.get(entity).map_or(1.0, |s| s.0) + mounter_body.map_or(Vec3::zero(), Body::rider_offset) * scales.get(rider).map_or(1.0, |s| s.0); 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((inputs, actions)) = inputs_and_actions && let Some(controller) = controllers.get_mut(entity) { controller.inputs = inputs; controller.actions = actions; } } // For each volume rider. for (entity, is_volume_rider) in (&entities, &is_volume_riders).join() { if let Some((mut mat, volume_ori, _)) = is_volume_rider.pos.get_block_and_transform( &terrain, &id_maps, |e| positions.get(e).copied().zip(orientations.get(e).copied()), &colliders, ) { let Some((mount_offset, mount_dir)) = is_volume_rider.block.mount_offset() else { error!("Mounted on unmountable block"); continue; }; let mount_block_ori = if let Some(ori) = is_volume_rider.block.get_ori() { mat *= Mat4::identity() .translated_3d(mount_offset) .rotated_z(std::f32::consts::PI * 0.25 * ori as f32) .translated_3d(Vec3::new(0.5, 0.5, 0.0)); ori } else { mat *= Mat4::identity().translated_3d(mount_offset + Vec3::new(0.5, 0.5, 0.0)); 0 }; if let Some(pos) = positions.get_mut(entity) { pos.0 = mat.mul_point(Vec3::zero()); } if let Some(ori) = orientations.get_mut(entity) { *ori = volume_ori.rotated( Ori::from_unnormalized_vec(mount_dir) .unwrap_or_default() .to_quat() .rotated_z(std::f32::consts::PI * 0.25 * mount_block_ori as f32), ); } } let v = match is_volume_rider.pos.kind { common::mounting::Volume::Terrain => Vec3::zero(), common::mounting::Volume::Entity(uid) => { if let Some(v) = id_maps.uid_entity(uid).and_then(|e| velocities.get(e)) { v.0 } else { Vec3::zero() } }, }; if let Some(vel) = velocities.get_mut(entity) { vel.0 = v; } let inputs = controllers.get_mut(entity).map(|c| { let actions: Vec<_> = c .actions .extract_if(|action| match action { ControlAction::StartInput { input: i, .. } | ControlAction::CancelInput(i) => { matches!(i, InputKind::Jump | InputKind::Fly | InputKind::Roll) }, _ => false, }) .collect(); let inputs = c.inputs.clone(); (actions, inputs) }); if is_volume_rider.block.is_controller() { if let Some((actions, inputs)) = inputs { match is_volume_rider.pos.kind { common::mounting::Volume::Entity(uid) => { if let Some(controller) = id_maps.uid_entity(uid).and_then(|e| controllers.get_mut(e)) { controller.inputs = inputs; controller.actions = actions; } }, common::mounting::Volume::Terrain => {}, } } } } } }