use glam::Vec3; use kurbo::ParamCurve; use rapier3d::math::Vector; use crate::components::player_states::{ FallingState, IdleState, JumpingState, LeapingState, RollingState, WalkingState, }; use crate::entity::EntityHandle; use crate::physics::PhysicsManager; use crate::states::state::State; use crate::world::World; pub const LEAP_DURATION: f32 = 0.18; pub const ROLL_DURATION: f32 = 0.42; const LEAP_SPEED: f32 = 18.0; const ROLL_SPEED: f32 = 14.0; impl State for IdleState { fn tick_time(&mut self, delta: f32) { self.time_in_state += delta; } fn on_enter(&mut self, world: &mut World, entity: EntityHandle) { world.physics.with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { let current_velocity = *rigidbody.linvel(); let idle_damping = world .movements .with(entity, |m| m.idle_damping) .unwrap_or(0.1); let horizontal_velocity = Vec3::new(current_velocity.x, 0.0, current_velocity.z); let new_horizontal_velocity = horizontal_velocity * idle_damping; rigidbody.set_linvel( Vector::new( new_horizontal_velocity.x, current_velocity.y, new_horizontal_velocity.z, ), true, ); }); }); } fn on_physics_update(&mut self, world: &mut World, entity: EntityHandle, _delta: f32) { const GROUND_CHECK_DISTANCE: f32 = 0.6; let current_translation = world .physics .with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { *rigidbody.translation() }) }) .flatten() .unwrap(); let terrain_height = PhysicsManager::get_terrain_height_at(current_translation.x, current_translation.z); if let Some(height) = terrain_height { let target_y = height + 1.0; let distance_to_ground = current_translation.y - target_y; if distance_to_ground.abs() < GROUND_CHECK_DISTANCE { world.physics.with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { let next_translation = Vector::new(current_translation.x, target_y, current_translation.z); rigidbody.set_next_kinematic_translation(next_translation); }); }); world.movements.with_mut(entity, |movement| { movement.movement_context.is_floored = true; }); } } } } impl State for WalkingState { fn tick_time(&mut self, delta: f32) { self.time_in_state += delta; } fn on_physics_update(&mut self, world: &mut World, entity: EntityHandle, delta: f32) { let (movement_input, walking_config) = world .movements .with(entity, |movement| { let input = world .inputs .with(entity, |input| input.move_direction) .unwrap_or(Vec3::ZERO); (input, movement.clone()) }) .unwrap(); let t = (self.time_in_state / walking_config.walking_acceleration_duration).clamp(0.0, 1.0); let acceleration_amount = walking_config.walking_acceleration_curve.eval(t as f64).y as f32; let current_translation = world .physics .with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { *rigidbody.translation() }) }) .flatten() .unwrap(); let terrain_height = PhysicsManager::get_terrain_height_at(current_translation.x, current_translation.z); let target_y = if let Some(height) = terrain_height { height + 1.0 } else { current_translation.y }; let slope_angle = if movement_input.length_squared() > 0.01 { PhysicsManager::get_terrain_slope_in_direction( current_translation.x, current_translation.z, movement_input.x, movement_input.z, ) } else { 0.0 }; let slope_multiplier = { const MAX_SLOPE_ANGLE: f32 = std::f32::consts::PI / 4.0; if slope_angle > 0.0 { let uphill_factor = (slope_angle / MAX_SLOPE_ANGLE).min(1.0); 1.0 - uphill_factor * 0.9 } else { let downhill_factor = (slope_angle.abs() / MAX_SLOPE_ANGLE).min(1.0); 1.0 + downhill_factor * 0.5 } }; world.physics.with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { let current_velocity = *rigidbody.linvel(); let horizontal_velocity = Vec3::new(current_velocity.x, 0.0, current_velocity.z); let walking_force = movement_input * walking_config.walking_acceleration * delta * acceleration_amount; let new_horizontal_velocity = (walking_force + horizontal_velocity * walking_config.walking_damping) .clamp_length_max(walking_config.max_walking_speed * slope_multiplier); let next_translation = Vector::new( current_translation.x + new_horizontal_velocity.x * delta, target_y, current_translation.z + new_horizontal_velocity.z * delta, ); rigidbody.set_linvel( Vector::new(new_horizontal_velocity.x, 0.0, new_horizontal_velocity.z), true, ); rigidbody.set_next_kinematic_translation(next_translation); }); }); world.movements.with_mut(entity, |movement| { movement.movement_context.is_floored = terrain_height.is_some(); }); if movement_input.length_squared() > 0.1 { world.transforms.with_mut(entity, |transform| { let target_rotation = f32::atan2(movement_input.x, movement_input.z); transform.rotation.y = target_rotation; }); } } } impl State for JumpingState { fn tick_time(&mut self, delta: f32) { self.time_in_state += delta; } fn on_enter(&mut self, world: &mut World, entity: EntityHandle) { self.time_in_state = 0.0; let current_position = world.transforms.get(entity).unwrap().position; world.jumps.with_mut(entity, |jump| { jump.jump_context.in_progress = true; jump.jump_context.origin_height = current_position.y; jump.jump_context.duration = 0.0; jump.jump_context.normal = Vec3::Y; }); } fn on_exit(&mut self, world: &mut World, entity: EntityHandle) { world.jumps.with_mut(entity, |jump| { jump.jump_context.in_progress = false; jump.jump_context.duration = 0.0; }); } fn on_physics_update(&mut self, world: &mut World, entity: EntityHandle, delta: f32) { world.jumps.with_mut(entity, |jump| { jump.jump_context.duration = self.time_in_state; }); let jump = world.jumps.with(entity, |jump| jump.clone()).unwrap(); let normalized_time = (self.time_in_state / jump.jump_duration).min(1.0); let height_progress = jump.jump_curve.eval(normalized_time as f64).y as f32; let origin_height = jump.jump_context.origin_height; let target_height = origin_height + height_progress * jump.jump_height; let current_translation = world .physics .with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { *rigidbody.translation() }) }) .flatten() .unwrap(); let height_diff = target_height - current_translation.y; let required_velocity = height_diff / delta; world.physics.with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { let current_velocity = *rigidbody.linvel(); let next_translation = Vector::new( current_translation.x + current_velocity.x * delta, current_translation.y + required_velocity * delta, current_translation.z + current_velocity.z * delta, ); rigidbody.set_linvel( Vector::new(current_velocity.x, required_velocity, current_velocity.z), true, ); rigidbody.set_next_kinematic_translation(next_translation); }); }); world.movements.with_mut(entity, |movement| { movement.movement_context.is_floored = false; }); } } impl State for FallingState { fn tick_time(&mut self, delta: f32) { self.time_in_state += delta; } fn on_physics_update(&mut self, world: &mut World, entity: EntityHandle, delta: f32) { const GRAVITY: f32 = -9.81 * 5.0; const GROUND_CHECK_DISTANCE: f32 = 0.6; let (current_pos, velocity) = world .physics .with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { let mut vel = *rigidbody.linvel(); vel.y += GRAVITY * delta; (*rigidbody.translation(), vel) }) }) .flatten() .unwrap(); let next_pos = current_pos + velocity; let terrain_height = PhysicsManager::get_terrain_height_at(next_pos.x, next_pos.z); let is_grounded = if let Some(height) = terrain_height { let target_y = height + 1.0; let distance_to_ground = current_pos.y - target_y; if distance_to_ground < GROUND_CHECK_DISTANCE && velocity.y <= 0.01 { world.physics.with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { let next_pos = Vector::new(current_pos.x, target_y, current_pos.z); rigidbody.set_next_kinematic_translation(next_pos); rigidbody.set_linvel(Vector::new(velocity.x, 0.0, velocity.z), true); }); }); true } else { world.physics.with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { let next_pos = current_pos + velocity * delta; rigidbody.set_next_kinematic_translation(next_pos); rigidbody.set_linvel(velocity, true); }); }); false } } else { world.physics.with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { let next_pos = current_pos + velocity * delta; rigidbody.set_next_kinematic_translation(next_pos); rigidbody.set_linvel(velocity, true); }); }); false }; world.movements.with_mut(entity, |movement| { movement.movement_context.is_floored = is_grounded; }); } } impl State for LeapingState { fn tick_time(&mut self, delta: f32) { self.time_in_state += delta; } fn on_enter(&mut self, world: &mut World, entity: EntityHandle) { self.time_in_state = 0.0; let facing = world .transforms .with(entity, |t| { let yaw = t.rotation.y; Vec3::new(yaw.sin(), 0.0, yaw.cos()) }) .unwrap_or(Vec3::Z); let move_dir = world .inputs .with(entity, |i| i.move_direction) .unwrap_or(Vec3::ZERO); self.leap_direction = if move_dir.length_squared() > 0.01 { move_dir } else { facing }; } fn on_physics_update(&mut self, world: &mut World, entity: EntityHandle, delta: f32) { let current_translation = world .physics .with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rb| *rb.translation()) }) .flatten() .unwrap(); let terrain_height = PhysicsManager::get_terrain_height_at(current_translation.x, current_translation.z); let target_y = terrain_height .map(|h| h + 1.0) .unwrap_or(current_translation.y); let velocity = self.leap_direction * LEAP_SPEED; world.physics.with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rb| { let next = Vector::new( current_translation.x + velocity.x * delta, target_y, current_translation.z + velocity.z * delta, ); rb.set_linvel(Vector::new(velocity.x, 0.0, velocity.z), true); rb.set_next_kinematic_translation(next); }); }); world.movements.with_mut(entity, |m| { m.movement_context.is_floored = terrain_height.is_some(); }); world.transforms.with_mut(entity, |t| { t.rotation.y = f32::atan2(self.leap_direction.x, self.leap_direction.z); }); } } impl State for RollingState { fn tick_time(&mut self, delta: f32) { self.time_in_state += delta; } fn on_enter(&mut self, world: &mut World, entity: EntityHandle) { self.time_in_state = 0.0; self.roll_direction = world .inputs .with(entity, |i| i.move_direction) .filter(|d| d.length_squared() > 0.01) .unwrap_or_else(|| { world .transforms .with(entity, |t| { let yaw = t.rotation.y; Vec3::new(yaw.sin(), 0.0, yaw.cos()) }) .unwrap_or(Vec3::Z) }); } fn on_physics_update(&mut self, world: &mut World, entity: EntityHandle, delta: f32) { let t = (self.time_in_state / ROLL_DURATION).clamp(0.0, 1.0); let speed = ROLL_SPEED * (1.0 - t * 0.6); let current_translation = world .physics .with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rb| *rb.translation()) }) .flatten() .unwrap(); let terrain_height = PhysicsManager::get_terrain_height_at(current_translation.x, current_translation.z); let target_y = terrain_height .map(|h| h + 1.0) .unwrap_or(current_translation.y); let velocity = self.roll_direction * speed; world.physics.with(entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rb| { let next = Vector::new( current_translation.x + velocity.x * delta, target_y, current_translation.z + velocity.z * delta, ); rb.set_linvel(Vector::new(velocity.x, 0.0, velocity.z), true); rb.set_next_kinematic_translation(next); }); }); world.movements.with_mut(entity, |m| { m.movement_context.is_floored = terrain_height.is_some(); }); } }