use crate::{ comp::{ ActionState::*, CharacterState, Controller, MovementState::*, Ori, PhysicsState, Pos, Stats, Vel, }, state::DeltaTime, terrain::TerrainMap, }; use specs::{Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; use std::time::Duration; use vek::*; pub const ROLL_DURATION: Duration = Duration::from_millis(600); const HUMANOID_ACCEL: f32 = 70.0; const HUMANOID_SPEED: f32 = 120.0; const HUMANOID_AIR_ACCEL: f32 = 10.0; const HUMANOID_AIR_SPEED: f32 = 100.0; const ROLL_SPEED: f32 = 13.0; const GLIDE_ACCEL: f32 = 15.0; const GLIDE_SPEED: f32 = 45.0; const BLOCK_ACCEL: f32 = 30.0; const BLOCK_SPEED: f32 = 75.0; // Gravity is 9.81 * 4, so this makes gravity equal to .15 const GLIDE_ANTIGRAV: f32 = 9.81 * 3.95; pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0; /// This system applies forces and calculates new positions and velocities. pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( ReadExpect<'a, TerrainMap>, Read<'a, DeltaTime>, ReadStorage<'a, Stats>, ReadStorage<'a, Controller>, ReadStorage<'a, PhysicsState>, WriteStorage<'a, CharacterState>, WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, WriteStorage<'a, Ori>, ); fn run( &mut self, ( _terrain, dt, stats, controllers, physics_states, mut character_states, mut positions, mut velocities, mut orientations, ): Self::SystemData, ) { // Apply movement inputs for (stats, controller, physics, mut character, mut _pos, mut vel, mut ori) in ( &stats, &controllers, &physics_states, &mut character_states, &mut positions, &mut velocities, &mut orientations, ) .join() { if stats.is_dead { continue; } if character.movement.is_roll() { vel.0 = Vec3::new(0.0, 0.0, vel.0.z) + controller .move_dir .try_normalized() .unwrap_or(Vec2::from(vel.0).try_normalized().unwrap_or_default()) * ROLL_SPEED } if character.action.is_block() || character.action.is_attack() { vel.0 += Vec2::broadcast(dt.0) * controller.move_dir * match physics.on_ground { true if vel.0.magnitude_squared() < BLOCK_SPEED.powf(2.0) => BLOCK_ACCEL, _ => 0.0, } } else { // Move player according to move_dir vel.0 += Vec2::broadcast(dt.0) * controller.move_dir * match (physics.on_ground, &character.movement) { (true, Run) if vel.0.magnitude_squared() < HUMANOID_SPEED.powf(2.0) => { HUMANOID_ACCEL } (false, Glide) if vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) => { GLIDE_ACCEL } (false, Jump) if vel.0.magnitude_squared() < HUMANOID_AIR_SPEED.powf(2.0) => { HUMANOID_AIR_ACCEL } _ => 0.0, }; } // Set direction based on move direction when on the ground let ori_dir = if character.action.is_wield() || character.action.is_attack() || character.action.is_block() { Vec2::from(controller.look_dir).normalized() } else { Vec2::from(vel.0) }; if ori_dir.magnitude_squared() > 0.0001 && (ori.0.normalized() - Vec3::from(ori_dir).normalized()).magnitude_squared() > 0.001 { ori.0 = vek::ops::Slerp::slerp( ori.0, ori_dir.into(), if physics.on_ground { 12.0 } else { 2.0 } * dt.0, ); } // Glide if character.movement == Glide && vel.0.magnitude_squared() < GLIDE_SPEED.powf(2.0) && vel.0.z < 0.0 { character.action = Idle; let lift = GLIDE_ANTIGRAV + vel.0.z.powf(2.0) * 0.2; vel.0.z += dt.0 * lift * Vec2::::from(vel.0 * 0.15).magnitude().min(1.0); } // Roll if let Roll { time_left } = &mut character.movement { character.action = Idle; if *time_left == Duration::default() || vel.0.magnitude_squared() < 10.0 { character.movement = Run; } else { *time_left = time_left .checked_sub(Duration::from_secs_f32(dt.0)) .unwrap_or_default(); } } if physics.on_ground && (character.movement == Jump || character.movement == Glide) { character.movement = Stand; } if !physics.on_ground && (character.movement == Stand || character.movement.is_roll() || character.movement == Run) { character.movement = Jump; } } } }