#![cfg(feature = "kcc_movement")]
use std::f32::consts::FRAC_PI_2;
use avian3d::math::{AdjustPrecision, Scalar, Vector, Vector2};
use bevy::prelude::*;
use crate::{
kcc::{JumpedRecently, Kcc, KccVelocity},
system_sets::IchunSystemSet,
};
#[derive(Component)]
#[require(Kcc)]
pub struct KccMovementConfig {
pub movement_acceleration: Scalar,
pub running_acceleration: Scalar,
pub air_control_factor: Scalar,
pub air_acceleration_factor: Scalar,
pub air_max_speed_multiplier: Scalar,
pub camera_sensitivity: Vector2,
pub jump_impulse: Scalar,
pub is_running: bool,
pub is_floating: bool,
pub floating_gravity: Vector,
}
impl Default for KccMovementConfig {
fn default() -> Self {
Self {
movement_acceleration: 30.0,
running_acceleration: 60.0,
air_control_factor: 0.5,
air_acceleration_factor: 1.5,
air_max_speed_multiplier: 1.1,
camera_sensitivity: Vector2::new(0.003, 0.002),
jump_impulse: 7.0,
is_running: false,
is_floating: false,
floating_gravity: Vector::NEG_Y * 4.0,
}
}
}
pub struct IchunMovementPlugin;
impl Plugin for IchunMovementPlugin {
fn build(&self, app: &mut bevy::app::App) {
app.add_event::<IchunRotateEvent>()
.add_event::<IchunMoveEvent>()
.add_event::<IchunRunEvent>()
.add_event::<IchunJumpEvent>()
.add_event::<IchunFloatEvent>()
.add_systems(
Update,
(
apply_rotation_sys,
apply_run_sys,
apply_velocity_sys,
apply_jump_sys,
apply_float_sys,
cap_gravity_sys,
)
.chain()
.in_set(IchunSystemSet::MovementSet),
);
}
}
#[derive(Event)]
pub struct IchunMoveEvent {
pub entity: Entity,
pub direction: Vector2,
}
#[derive(Event)]
pub struct IchunRunEvent {
pub entity: Entity,
pub is_running: Option<bool>,
}
#[derive(Event)]
pub struct IchunRotateEvent {
pub entity: Entity,
pub rotation: Vector2,
}
#[derive(Event)]
pub struct IchunJumpEvent {
pub entity: Entity,
}
#[derive(Event)]
pub struct IchunFloatEvent {
pub entity: Entity,
pub is_floating: Option<bool>,
}
fn apply_velocity_sys(
time: Res<Time>,
mut movement_event_reader: EventReader<IchunMoveEvent>,
mut controllers_qry: Query<(
Entity,
&KccMovementConfig,
&mut KccVelocity,
&Transform,
&Kcc,
)>,
) {
let delta_time = time.delta_secs_f64().adjust_precision();
for event in movement_event_reader.read() {
let Some((_, kcc_movement, mut kcc_vel, transform, kcc)) =
controllers_qry.iter_mut().find(|c| c.0 == event.entity)
else {
continue;
};
let base_acceleration = if kcc_movement.is_running {
kcc_movement.running_acceleration
} else {
kcc_movement.movement_acceleration
};
let adjusted_acceleration = if kcc.is_grounded {
base_acceleration
} else {
base_acceleration
* kcc_movement.air_control_factor
* kcc_movement.air_acceleration_factor
};
let rotated_velocity = transform.rotation.mul_vec3(Vec3::new(
event.direction.x * adjusted_acceleration * delta_time,
0.0,
event.direction.y * adjusted_acceleration * delta_time,
));
kcc_vel.x += rotated_velocity.x;
kcc_vel.z += rotated_velocity.z;
if !kcc.is_grounded {
let max_ground_speed = if kcc_movement.is_running {
kcc_movement.running_acceleration
} else {
kcc_movement.movement_acceleration
};
let max_air_speed = max_ground_speed * kcc_movement.air_max_speed_multiplier;
let current_horizontal_speed = Vec2::new(kcc_vel.x, kcc_vel.z).length();
if current_horizontal_speed > max_air_speed {
let normalized = Vec2::new(kcc_vel.x, kcc_vel.z).normalize() * max_air_speed;
kcc_vel.x = normalized.x;
kcc_vel.z = normalized.y;
}
}
}
}
fn apply_rotation_sys(
mut movement_event_reader: EventReader<IchunRotateEvent>,
mut controllers_qry: Query<(Entity, &KccMovementConfig, &mut Transform), With<Kcc>>,
) {
for event in movement_event_reader.read() {
let Some((_, kcc, mut transform)) =
controllers_qry.iter_mut().find(|c| c.0 == event.entity)
else {
continue;
};
let camera_sensitivity = kcc.camera_sensitivity;
let delta_yaw = -event.rotation.x * camera_sensitivity.x;
let delta_pitch = 0.0;
let (yaw, pitch, roll) = transform.rotation.to_euler(EulerRot::YXZ);
let yaw = yaw + delta_yaw;
const PITCH_LIMIT: f32 = FRAC_PI_2 - 0.01;
let pitch = (pitch + delta_pitch).clamp(-PITCH_LIMIT, PITCH_LIMIT);
transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, roll);
}
}
fn apply_jump_sys(
mut cmd: Commands,
mut movement_event_reader: EventReader<IchunJumpEvent>,
mut controllers_qry: Query<(Entity, &KccMovementConfig, &mut KccVelocity, &mut Kcc)>,
) {
for event in movement_event_reader.read() {
let Some((entity, kcc_movement, mut kcc_vel, mut kcc)) =
controllers_qry.iter_mut().find(|c| c.0 == event.entity)
else {
continue;
};
if !kcc.is_grounded {
continue;
}
if kcc.external_force.y < 0.0 {
kcc.external_force.y *= 0.3;
}
kcc_vel.0.y = kcc_movement.jump_impulse;
cmd.entity(entity).insert(JumpedRecently::default());
}
}
fn apply_run_sys(
mut movement_event_reader: EventReader<IchunRunEvent>,
mut controllers_qry: Query<(Entity, &mut KccMovementConfig), With<Kcc>>,
) {
for event in movement_event_reader.read() {
let Some((_, mut kcc)) = controllers_qry.iter_mut().find(|c| c.0 == event.entity) else {
continue;
};
kcc.is_running = match event.is_running {
Some(running) => running,
None => !kcc.is_running,
};
}
}
fn apply_float_sys(
mut movement_event_reader: EventReader<IchunFloatEvent>,
mut controllers_qry: Query<(Entity, &mut KccMovementConfig), With<Kcc>>,
) {
for event in movement_event_reader.read() {
let Some((_, mut movement)) = controllers_qry.iter_mut().find(|c| c.0 == event.entity)
else {
continue;
};
movement.is_floating = match event.is_floating {
Some(floating) => floating,
None => !movement.is_floating,
};
}
}
fn cap_gravity_sys(mut controllers_qry: Query<(&mut KccVelocity, &KccMovementConfig)>) {
for (mut kcc, movement) in controllers_qry.iter_mut() {
if movement.is_floating {
kcc.0.y = kcc.0.y.max(movement.floating_gravity.y);
}
}
}