blackout/src/navigation.rs

422 lines
14 KiB
Rust
Raw Normal View History

2022-08-04 16:25:09 +00:00
use std::{
collections::HashMap, error::Error, f32::consts::PI, fmt::Debug, hash::Hash,
marker::PhantomData,
};
2021-05-13 17:25:45 +00:00
use bevy::prelude::*;
2022-05-06 16:07:59 +00:00
use bevy_rapier2d::prelude::*;
2021-05-13 17:25:45 +00:00
use bevy_tts::Tts;
use leafwing_input_manager::{axislike::DualAxisData, prelude::*};
2021-05-13 17:25:45 +00:00
use crate::{
commands::RunIfExistsExt,
2022-07-19 16:56:41 +00:00
core::{Angle, Area, CardinalDirection, GlobalTransformExt, Player},
2021-05-13 17:25:45 +00:00
error::error_handler,
exploration::{ExplorationFocused, Exploring},
2022-07-19 16:56:41 +00:00
log::Log,
2021-05-13 17:25:45 +00:00
pathfinding::Destination,
2022-07-19 16:56:41 +00:00
utils::target_and_other,
2021-05-13 17:25:45 +00:00
};
2022-08-02 22:15:22 +00:00
#[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug)]
pub enum NavigationAction {
Move,
Rotate,
SetLinearVelocity,
SetAngularVelocity,
2022-08-02 22:15:22 +00:00
SnapLeft,
SnapRight,
SnapCardinal,
SnapReverse,
Sprint,
}
2022-01-10 19:50:52 +00:00
#[derive(Component, Clone, Copy, Debug, Deref, DerefMut, Reflect)]
2021-05-13 17:25:45 +00:00
#[reflect(Component)]
pub struct MaxSpeed(pub f32);
impl Default for MaxSpeed {
fn default() -> Self {
MaxSpeed(2.)
}
}
#[derive(Component, Clone, Copy, Default, Debug, Deref, DerefMut, Reflect)]
2021-05-13 17:25:45 +00:00
#[reflect(Component)]
pub struct RotationSpeed(pub Angle);
2022-01-10 19:50:52 +00:00
#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)]
2021-05-13 17:25:45 +00:00
#[reflect(Component)]
pub struct Speed(pub f32);
2022-12-19 20:08:31 +00:00
#[derive(Deref, DerefMut)]
2022-08-04 16:25:09 +00:00
struct SnapTimer(Timer);
2021-05-13 17:25:45 +00:00
2022-08-04 16:25:09 +00:00
impl Default for SnapTimer {
fn default() -> Self {
2022-12-19 20:08:31 +00:00
Self(Timer::from_seconds(0.2, TimerMode::Once))
2022-08-04 16:25:09 +00:00
}
}
2022-03-22 02:58:34 +00:00
2022-12-19 20:08:31 +00:00
#[derive(Resource, Default, Deref, DerefMut)]
struct SnapTimers(HashMap<Entity, SnapTimer>);
2022-08-02 22:15:22 +00:00
fn movement_controls<S>(
2021-05-13 17:25:45 +00:00
mut commands: Commands,
2022-08-02 22:15:22 +00:00
config: Res<NavigationConfig<S>>,
2022-12-19 20:08:31 +00:00
snap_timers: Res<SnapTimers>,
2022-08-04 16:25:09 +00:00
mut query: Query<(
Entity,
&mut ActionState<NavigationAction>,
2022-08-04 16:25:09 +00:00
&mut Velocity,
&mut Speed,
&MaxSpeed,
Option<&RotationSpeed>,
&Transform,
2022-08-04 16:25:09 +00:00
)>,
exploration_focused: Query<Entity, With<ExplorationFocused>>,
2021-05-26 21:46:20 +00:00
) where
2022-04-04 15:00:57 +00:00
S: 'static + Clone + Debug + Eq + Hash + Send + Sync,
2021-05-26 21:46:20 +00:00
{
for (entity, mut actions, mut velocity, mut speed, max_speed, rotation_speed, transform) in
&mut query
2021-05-13 17:25:45 +00:00
{
2022-08-04 16:25:09 +00:00
let sprinting = actions.pressed(NavigationAction::Sprint);
let mut cleanup = false;
if actions.pressed(NavigationAction::Move) {
if let Some(pair) = actions.clamped_axis_pair(NavigationAction::Move) {
cleanup = true;
2022-08-04 16:49:17 +00:00
let direction = pair.xy();
2022-08-04 16:25:09 +00:00
let forward_backward_movement_factor = if direction.x > 0. {
config.forward_movement_factor
} else if direction.x < 0. {
config.backward_movement_factor
} else {
0.
};
let movement_factor = if direction.x != 0. && direction.y != 0. {
config
.strafe_movement_factor
.min(forward_backward_movement_factor)
} else if direction.y != 0. {
config.strafe_movement_factor
} else {
forward_backward_movement_factor
};
let mut s = if sprinting {
**max_speed
} else {
**max_speed / config.sprint_movement_factor
};
s *= movement_factor;
**speed = s;
let mut v = direction * **speed;
v = transform
.compute_matrix()
.transform_vector3(v.extend(0.))
.truncate();
actions.press(NavigationAction::SetLinearVelocity);
actions
.action_data_mut(NavigationAction::SetLinearVelocity)
.axis_pair = Some(DualAxisData::from_xy(v));
}
}
2022-12-19 20:08:31 +00:00
if actions.released(NavigationAction::Move)
&& actions.axis_pair(NavigationAction::Move).is_some()
{
actions.press(NavigationAction::SetLinearVelocity);
actions
.action_data_mut(NavigationAction::SetLinearVelocity)
.axis_pair = None;
}
if actions.pressed(NavigationAction::SetLinearVelocity) {
if let Some(pair) = actions.axis_pair(NavigationAction::SetLinearVelocity) {
velocity.linvel = pair.into();
} else {
velocity.linvel = Vec2::ZERO;
}
2022-08-04 16:25:09 +00:00
}
if !snap_timers.contains_key(&entity) {
2022-08-02 22:15:22 +00:00
if let Some(rotation_speed) = rotation_speed {
if actions.pressed(NavigationAction::Rotate) {
cleanup = true;
let delta =
rotation_speed.radians() * actions.clamped_value(NavigationAction::Rotate);
actions.press(NavigationAction::SetAngularVelocity);
actions
.action_data_mut(NavigationAction::SetAngularVelocity)
.value = delta;
2022-03-22 02:58:34 +00:00
}
}
}
if actions.released(NavigationAction::Rotate) {
actions.press(NavigationAction::SetAngularVelocity);
actions
.action_data_mut(NavigationAction::SetAngularVelocity)
.value = 0.;
}
if actions.pressed(NavigationAction::SetAngularVelocity) {
velocity.angvel = actions.value(NavigationAction::SetAngularVelocity);
2022-08-04 16:25:09 +00:00
}
if cleanup {
commands.entity(entity).remove::<Destination>();
commands.entity(entity).remove::<Exploring>();
for entity in exploration_focused.iter() {
commands.entity(entity).remove::<ExplorationFocused>();
2021-05-26 21:46:20 +00:00
}
2021-05-13 17:25:45 +00:00
}
}
}
2022-08-04 16:25:09 +00:00
fn snap(
2022-03-22 02:58:34 +00:00
mut tts: ResMut<Tts>,
2022-12-19 20:08:31 +00:00
mut snap_timers: ResMut<SnapTimers>,
2022-08-02 22:15:22 +00:00
mut query: Query<
(
2022-08-04 16:25:09 +00:00
Entity,
2022-08-02 22:15:22 +00:00
&ActionState<NavigationAction>,
&mut Transform,
&CardinalDirection,
),
With<Player>,
>,
2022-08-04 16:25:09 +00:00
) -> Result<(), Box<dyn Error>> {
for (entity, actions, mut transform, direction) in &mut query {
if snap_timers.contains_key(&entity) {
continue;
} else if actions.pressed(NavigationAction::SnapLeft) {
2022-08-04 16:25:09 +00:00
snap_timers.insert(entity, SnapTimer::default());
2022-08-02 22:15:22 +00:00
transform.rotation = Quat::from_rotation_z(match direction {
CardinalDirection::North => PI,
CardinalDirection::East => PI / 2.,
CardinalDirection::South => 0.,
2022-08-04 16:25:09 +00:00
CardinalDirection::West => -PI / 2.,
2022-08-02 22:15:22 +00:00
});
} else if actions.pressed(NavigationAction::SnapRight) {
2022-08-04 16:25:09 +00:00
snap_timers.insert(entity, SnapTimer::default());
2022-08-02 22:15:22 +00:00
transform.rotation = Quat::from_rotation_z(match direction {
CardinalDirection::North => 0.,
2022-08-04 16:25:09 +00:00
CardinalDirection::East => -PI / 2.,
2022-08-02 22:15:22 +00:00
CardinalDirection::South => PI,
CardinalDirection::West => PI / 2.,
});
} else if actions.pressed(NavigationAction::SnapReverse) {
2022-08-04 16:25:09 +00:00
snap_timers.insert(entity, SnapTimer::default());
transform.rotate(Quat::from_rotation_z(PI));
} else if actions.pressed(NavigationAction::SnapCardinal) {
2022-08-02 22:15:22 +00:00
let yaw: Angle = direction.into();
let yaw = yaw.radians();
transform.rotation = Quat::from_rotation_z(yaw);
tts.speak(direction.to_string(), true)?;
2022-03-22 02:58:34 +00:00
}
}
Ok(())
}
2022-12-19 20:08:31 +00:00
fn tick_snap_timers(time: Res<Time>, mut snap_timers: ResMut<SnapTimers>) {
2022-08-04 16:25:09 +00:00
for timer in snap_timers.values_mut() {
timer.tick(time.delta());
}
snap_timers.retain(|_, v| !v.finished());
}
2021-07-26 20:50:25 +00:00
fn update_direction(
mut commands: Commands,
mut query: Query<
(Entity, &GlobalTransform, Option<&mut CardinalDirection>),
2021-07-26 20:50:25 +00:00
(With<Player>, Changed<Transform>),
>,
) {
for (entity, transform, direction) in query.iter_mut() {
let yaw = transform.yaw();
2021-07-26 20:50:25 +00:00
let new_direction: CardinalDirection = yaw.into();
if let Some(mut direction) = direction {
if *direction != new_direction {
*direction = new_direction;
}
} else {
2021-07-26 20:50:25 +00:00
commands.entity(entity).insert(new_direction);
2021-05-13 17:25:45 +00:00
}
}
2021-07-26 20:50:25 +00:00
}
fn remove_direction(
mut commands: Commands,
removed: RemovedComponents<Transform>,
directions: Query<&CardinalDirection>,
) {
for entity in removed.iter() {
if directions.contains(entity) {
commands.run_if_exists(entity, |mut entity| {
entity.remove::<CardinalDirection>();
});
}
}
}
2021-07-26 20:50:25 +00:00
fn speak_direction(
mut tts: ResMut<Tts>,
player: Query<
(&CardinalDirection, ChangeTrackers<CardinalDirection>),
(With<Player>, Changed<CardinalDirection>),
>,
2021-07-26 20:50:25 +00:00
) -> Result<(), Box<dyn Error>> {
if let Ok((direction, change)) = player.get_single() {
if !change.is_added() {
let direction: String = (*direction).into();
tts.speak(direction, true)?;
}
2022-01-13 20:43:02 +00:00
}
2021-05-13 17:25:45 +00:00
Ok(())
}
fn add_speed(mut commands: Commands, query: Query<Entity, (Added<Speed>, Without<Velocity>)>) {
for entity in query.iter() {
commands.entity(entity).insert(Velocity {
linvel: Vec2::ZERO,
..default()
});
}
}
pub(crate) fn limit_speed(mut query: Query<(&mut Speed, &MaxSpeed)>) {
2022-08-04 16:49:17 +00:00
for (mut speed, max_speed) in &mut query {
if **speed > **max_speed {
**speed = **max_speed;
}
}
}
2022-05-06 16:07:59 +00:00
fn remove_speed(removed: RemovedComponents<Speed>, mut query: Query<&mut Velocity>) {
for entity in removed.iter() {
if let Ok(mut velocity) = query.get_mut(entity) {
2022-05-06 16:07:59 +00:00
velocity.linvel = Vec2::ZERO;
}
}
}
2022-07-19 16:56:41 +00:00
fn log_area_descriptions<S, A>(
mut events: EventReader<CollisionEvent>,
areas: Query<(&Area, Option<&Name>)>,
players: Query<&Player>,
2022-08-02 22:15:22 +00:00
config: Res<NavigationConfig<S>>,
2022-07-19 16:56:41 +00:00
mut log: Query<&mut Log>,
) where
S: 'static + Send + Sync,
A: 'static + Send + Sync,
{
if !config.log_area_descriptions {
return;
}
for event in events.iter() {
let (entity1, entity2, started) = match event {
CollisionEvent::Started(collider1, collider2, _) => (collider1, collider2, true),
CollisionEvent::Stopped(collider1, collider2, _) => (collider1, collider2, false),
};
if let Some((area, other)) = target_and_other(*entity1, *entity2, &|v| areas.get(v).is_ok())
{
if players.get(other).is_ok() {
if let Ok((aabb, area_name)) = areas.get(area) {
let name = if let Some(name) = area_name {
Some(name.to_string())
} else if config.describe_undescribed_areas {
Some(format!("{}-by-{} area", aabb.extents().x, aabb.extents().y))
} else {
None
};
if let Some(name) = name {
if let Ok(mut log) = log.get_single_mut() {
if started {
log.push(format!("Entering {name}."));
} else {
log.push(format!("Leaving {name}."));
}
}
}
}
}
}
}
}
2022-12-19 20:08:31 +00:00
#[derive(Resource, Clone, Debug)]
2022-08-02 22:15:22 +00:00
pub struct NavigationConfig<S> {
pub forward_movement_factor: f32,
pub backward_movement_factor: f32,
pub strafe_movement_factor: f32,
pub sprint_movement_factor: f32,
2021-05-13 17:25:45 +00:00
pub movement_control_states: Vec<S>,
2022-07-19 16:56:41 +00:00
pub describe_undescribed_areas: bool,
pub log_area_descriptions: bool,
2021-05-13 17:25:45 +00:00
}
2022-08-02 22:15:22 +00:00
impl<S> Default for NavigationConfig<S> {
2021-05-13 17:25:45 +00:00
fn default() -> Self {
Self {
forward_movement_factor: 1.,
backward_movement_factor: 1.,
strafe_movement_factor: 1.,
sprint_movement_factor: 3.,
2021-05-13 17:25:45 +00:00
movement_control_states: vec![],
2022-07-19 16:56:41 +00:00
describe_undescribed_areas: false,
log_area_descriptions: true,
2021-05-13 17:25:45 +00:00
}
}
}
2021-05-26 22:30:03 +00:00
pub struct NavigationPlugin<'a, S, A>(PhantomData<&'a S>, PhantomData<&'a A>);
2021-05-13 17:25:45 +00:00
2021-05-26 21:46:20 +00:00
impl<'a, S, A> Default for NavigationPlugin<'a, S, A> {
2021-05-13 17:25:45 +00:00
fn default() -> Self {
2021-05-26 21:46:20 +00:00
Self(PhantomData, PhantomData)
2021-05-13 17:25:45 +00:00
}
}
2022-07-06 15:13:59 +00:00
impl<S, A> Plugin for NavigationPlugin<'static, S, A>
2021-05-13 17:25:45 +00:00
where
2022-05-06 16:07:59 +00:00
S: 'static + Clone + Copy + Debug + Eq + Hash + Send + Sync,
A: Hash + Eq + Copy + Send + Sync,
2021-05-13 17:25:45 +00:00
{
2022-01-10 19:50:52 +00:00
fn build(&self, app: &mut App) {
2022-08-02 22:15:22 +00:00
if !app.world.contains_resource::<NavigationConfig<S>>() {
app.insert_resource(NavigationConfig::<S>::default());
2021-05-13 17:25:45 +00:00
}
let config = app
2022-01-11 05:05:51 +00:00
.world
2022-08-02 22:15:22 +00:00
.get_resource::<NavigationConfig<S>>()
2021-05-13 17:25:45 +00:00
.unwrap()
.clone();
2022-12-19 20:08:31 +00:00
app.init_resource::<SnapTimers>()
2022-03-22 02:58:34 +00:00
.register_type::<MaxSpeed>()
2021-05-13 17:25:45 +00:00
.register_type::<RotationSpeed>()
2022-12-19 20:08:31 +00:00
.register_type::<Speed>()
2022-08-02 22:15:22 +00:00
.add_plugin(InputManagerPlugin::<NavigationAction>::default())
2022-08-04 19:30:40 +00:00
.add_system_to_stage(CoreStage::PreUpdate, update_direction)
.add_system_to_stage(CoreStage::PostUpdate, remove_direction)
2022-08-04 16:25:09 +00:00
.add_system(tick_snap_timers)
2022-12-19 20:08:31 +00:00
.add_system(speak_direction.pipe(error_handler))
.add_system(add_speed)
2022-08-04 16:49:17 +00:00
.add_system(limit_speed)
2022-07-19 16:56:41 +00:00
.add_system_to_stage(CoreStage::PostUpdate, remove_speed)
.add_system_to_stage(CoreStage::PostUpdate, log_area_descriptions::<S, A>);
const MOVEMENT_CONTROLS: &str = "MOVEMENT_CONTROLS";
2021-05-13 17:25:45 +00:00
if config.movement_control_states.is_empty() {
app.add_system(
movement_controls::<S>
.label(MOVEMENT_CONTROLS)
.after(limit_speed),
)
2022-12-19 20:08:31 +00:00
.add_system(snap.pipe(error_handler).before(MOVEMENT_CONTROLS));
2021-05-13 17:25:45 +00:00
} else {
let states = config.movement_control_states;
for state in states {
app.add_system_set(
2022-03-22 02:58:34 +00:00
SystemSet::on_update(state)
2022-08-02 22:15:22 +00:00
.with_system(movement_controls::<S>.label(MOVEMENT_CONTROLS))
2022-12-19 20:08:31 +00:00
.with_system(snap.pipe(error_handler).before(MOVEMENT_CONTROLS)),
2021-05-13 17:25:45 +00:00
);
}
}
}
}