use glam::Vec3; use kurbo::ParamCurve; use rapier3d::math::Vector; use crate::entity::EntityHandle; use crate::physics::PhysicsManager; use crate::state::State; use crate::utility::time::Time; use crate::world::World; pub struct PlayerFallingState { pub entity: EntityHandle, } impl State for PlayerFallingState { fn get_state_name(&self) -> &'static str { "PlayerFallingState" } fn on_state_enter(&mut self, _world: &mut World) { println!("entered falling"); } fn on_state_exit(&mut self, _world: &mut World) {} fn on_state_update(&mut self, _world: &mut World, _delta: f32) {} fn on_state_physics_update(&mut self, world: &mut World, delta: f32) { const GRAVITY: f32 = -9.81 * 5.0; const GROUND_CHECK_DISTANCE: f32 = 0.6; let (current_pos, velocity) = world .physics .with(self.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(self.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(self.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(self.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(self.entity, |movement| { movement.movement_context.is_floored = is_grounded; }); } } pub struct PlayerIdleState { pub entity: EntityHandle, } impl State for PlayerIdleState { fn get_state_name(&self) -> &'static str { "PlayerIdleState" } fn on_state_enter(&mut self, world: &mut World) { println!("entered idle"); world.physics.with(self.entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { let current_velocity = *rigidbody.linvel(); let idle_damping = world .movements .with(self.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_state_exit(&mut self, _world: &mut World) {} fn on_state_update(&mut self, _world: &mut World, _delta: f32) {} fn on_state_physics_update(&mut self, world: &mut World, _delta: f32) { const GROUND_CHECK_DISTANCE: f32 = 0.6; let current_translation = world .physics .with(self.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(self.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(self.entity, |movement| { movement.movement_context.is_floored = true; }); } } } } pub struct PlayerWalkingState { pub entity: EntityHandle, pub enter_time_stamp: f32, } impl State for PlayerWalkingState { fn get_state_name(&self) -> &'static str { "PlayerWalkingState" } fn on_state_enter(&mut self, _world: &mut World) { self.enter_time_stamp = Time::get_time_elapsed(); println!("entered walking"); } fn on_state_exit(&mut self, _world: &mut World) {} fn on_state_update(&mut self, _world: &mut World, _delta: f32) {} fn on_state_physics_update(&mut self, world: &mut World, delta: f32) { let (movement_input, walking_config) = world .movements .with(self.entity, |movement| { let input = world .inputs .with(self.entity, |input| input.move_direction) .unwrap_or(Vec3::ZERO); (input, movement.clone()) }) .unwrap(); let current_time = Time::get_time_elapsed(); let elapsed_time = current_time - self.enter_time_stamp; let t = (elapsed_time / 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(self.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(self.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(self.entity, |movement| { movement.movement_context.is_floored = terrain_height.is_some(); }); if movement_input.length_squared() > 0.1 { world.transforms.with_mut(self.entity, |transform| { let target_rotation = f32::atan2(movement_input.x, movement_input.z); transform.rotation.y = target_rotation; }); } } } pub struct PlayerJumpingState { pub entity: EntityHandle, pub enter_time_stamp: f32, } impl State for PlayerJumpingState { fn get_state_name(&self) -> &'static str { "PlayerJumpingState" } fn on_state_enter(&mut self, world: &mut World) { self.enter_time_stamp = Time::get_time_elapsed(); let current_position = world.transforms.get(self.entity).unwrap().position; world.jumps.with_mut(self.entity, |jump| { jump.jump_context.in_progress = true; jump.jump_context.execution_time = self.enter_time_stamp; jump.jump_context.origin_height = current_position.y; jump.jump_context.duration = 0.0; jump.jump_context.normal = Vec3::Y; }); println!("entered jumping"); } fn on_state_exit(&mut self, world: &mut World) { world.jumps.with_mut(self.entity, |jump| { jump.jump_context.in_progress = false; jump.jump_context.duration = 0.0; }); println!("exited jumping"); } fn on_state_update(&mut self, _world: &mut World, _delta: f32) {} fn on_state_physics_update(&mut self, world: &mut World, delta: f32) { let current_time = Time::get_time_elapsed(); world.jumps.with_mut(self.entity, |jump| { jump.jump_context.duration = current_time - jump.jump_context.execution_time; }); let jump = world .jumps .with(self.entity, |jump| jump.clone()) .unwrap(); let elapsed_time = jump.jump_context.duration; let normalized_time = (elapsed_time / 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(self.entity, |physics| { PhysicsManager::with_rigidbody_mut(physics.rigidbody, |rigidbody| { *rigidbody.translation() }) }) .flatten() .unwrap(); let current_y = current_translation.y; let height_diff = target_height - current_y; let required_velocity = height_diff / delta; world.physics.with(self.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(self.entity, |movement| { movement.movement_context.is_floored = false; }); } }