blackout/src/navigation.rs

403 lines
13 KiB
Rust
Raw Normal View History

use std::{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;
2022-08-02 22:15:22 +00:00
use leafwing_input_manager::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,
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.)
}
}
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 RotationSpeed(pub Angle);
impl Default for RotationSpeed {
fn default() -> Self {
Self(Angle::Radians(0.))
}
}
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-01-10 19:50:52 +00:00
#[derive(Component, Clone, Copy, Debug, Default, Reflect)]
2021-05-13 17:25:45 +00:00
#[reflect(Component)]
pub struct Sprinting;
2022-03-22 02:58:34 +00:00
#[derive(Default, Deref, DerefMut)]
struct Snapping(bool);
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>>,
2021-05-13 17:25:45 +00:00
time: Res<Time>,
2022-03-22 02:58:34 +00:00
mut snapping: ResMut<Snapping>,
mut query: Query<
(
Entity,
2022-08-02 22:15:22 +00:00
&ActionState<NavigationAction>,
2022-05-06 16:07:59 +00:00
&mut Velocity,
&mut Speed,
&MaxSpeed,
Option<&RotationSpeed>,
2022-05-06 16:07:59 +00:00
&mut Transform,
Option<&Destination>,
),
With<Player>,
>,
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
{
2022-08-02 22:15:22 +00:00
for (
entity,
actions,
mut velocity,
mut speed,
max_speed,
rotation_speed,
mut transform,
destination,
) in &mut query
2021-05-13 17:25:45 +00:00
{
2022-03-22 02:58:34 +00:00
if **snapping {
2022-08-02 22:15:22 +00:00
if actions.pressed(NavigationAction::Rotate) {
continue;
2021-05-13 17:25:45 +00:00
}
2022-03-22 02:58:34 +00:00
**snapping = false;
2022-04-05 13:34:53 +00:00
continue;
2022-03-22 02:58:34 +00:00
} else {
2022-08-02 22:15:22 +00:00
let sprinting = actions.pressed(NavigationAction::Sprint);
2022-03-22 02:58:34 +00:00
if sprinting {
2022-08-02 22:15:22 +00:00
commands.entity(entity).insert(Sprinting);
2022-03-22 02:58:34 +00:00
} else {
commands.entity(entity).remove::<Sprinting>();
2021-05-13 17:25:45 +00:00
}
2022-08-02 22:15:22 +00:00
let mut cleanup = false;
if actions.pressed(NavigationAction::Move) {
if let Some(pair) = actions.clamped_axis_pair(NavigationAction::Move) {
cleanup = true;
let mut direction = pair.xy();
let strength = direction.length();
if direction.x > 0. {
direction.x *= config.forward_movement_factor;
} else if direction.x < 0. {
direction.x *= config.backward_movement_factor;
}
if direction.y != 0. {
direction.y *= config.strafe_movement_factor;
}
let s = if sprinting {
**max_speed
} else {
**max_speed / config.sprint_movement_factor
};
**speed = s;
direction *= strength;
let mut v = direction * **speed;
v = transform
.compute_matrix()
.transform_vector3(v.extend(0.))
.truncate();
velocity.linvel = v;
2022-03-22 02:58:34 +00:00
}
2022-08-02 22:15:22 +00:00
} else if destination.is_none() {
velocity.linvel = Vec2::ZERO;
}
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()
* time.delta_seconds()
* actions.clamped_value(NavigationAction::Rotate);
2022-05-06 16:07:59 +00:00
transform.rotate(Quat::from_rotation_z(delta));
2022-03-22 02:58:34 +00:00
}
2021-05-13 17:25:45 +00:00
} else {
2022-03-22 02:58:34 +00:00
velocity.angvel = 0.;
}
2022-08-02 22:15:22 +00:00
if cleanup {
2022-03-22 02:58:34 +00:00
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-02 22:15:22 +00:00
fn snap<S>(
2022-03-22 02:58:34 +00:00
mut tts: ResMut<Tts>,
mut snapping: ResMut<Snapping>,
2022-08-02 22:15:22 +00:00
mut query: Query<
(
&ActionState<NavigationAction>,
&mut Transform,
&mut Velocity,
&CardinalDirection,
),
With<Player>,
>,
2022-03-22 02:58:34 +00:00
) -> Result<(), Box<dyn Error>>
where
2022-05-06 16:07:59 +00:00
S: 'static + Copy + Debug + Eq + Hash + Send + Sync,
2022-03-22 02:58:34 +00:00
{
2022-08-02 22:15:22 +00:00
for (actions, mut transform, mut velocity, direction) in query.iter_mut() {
if actions.just_pressed(NavigationAction::SnapLeft) {
**snapping = true;
transform.rotation = Quat::from_rotation_z(match direction {
CardinalDirection::North => PI,
CardinalDirection::East => PI / 2.,
CardinalDirection::South => 0.,
CardinalDirection::West => PI * 1.5,
});
velocity.angvel = 0.;
2022-03-22 02:58:34 +00:00
}
2022-08-02 22:15:22 +00:00
if actions.just_pressed(NavigationAction::SnapRight) {
**snapping = true;
transform.rotation = Quat::from_rotation_z(match direction {
CardinalDirection::North => 0.,
CardinalDirection::East => PI * 1.5,
CardinalDirection::South => PI,
CardinalDirection::West => PI / 2.,
});
velocity.angvel = 0.;
2022-03-22 02:58:34 +00:00
}
2022-08-02 22:15:22 +00:00
if actions.just_pressed(NavigationAction::SnapCardinal) {
**snapping = true;
let yaw: Angle = direction.into();
let yaw = yaw.radians();
transform.rotation = Quat::from_rotation_z(yaw);
velocity.angvel = 0.;
tts.speak(direction.to_string(), true)?;
2022-03-22 02:58:34 +00:00
}
2022-08-02 22:15:22 +00:00
if actions.just_pressed(NavigationAction::SnapReverse) {
**snapping = true;
transform.rotate(Quat::from_rotation_z(PI));
velocity.angvel = 0.;
2022-03-22 02:58:34 +00:00
}
}
Ok(())
}
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>) {
for entity in removed.iter() {
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()
});
}
}
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}."));
}
}
}
}
}
}
}
}
2021-05-13 17:25:45 +00:00
#[derive(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();
const SNAP: &str = "SNAP";
2022-03-22 02:58:34 +00:00
app.init_resource::<Snapping>()
.register_type::<MaxSpeed>()
2021-05-13 17:25:45 +00:00
.register_type::<RotationSpeed>()
.register_type::<Sprinting>()
2022-08-02 22:15:22 +00:00
.add_plugin(InputManagerPlugin::<NavigationAction>::default())
.add_system_to_stage(CoreStage::PostUpdate, update_direction)
.add_system_to_stage(CoreStage::PostUpdate, remove_direction)
2022-01-10 19:50:52 +00:00
.add_system(speak_direction.chain(error_handler))
.add_system(add_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() {
2022-08-02 22:15:22 +00:00
app.add_system(movement_controls::<S>.label(MOVEMENT_CONTROLS))
.add_system(
2022-08-02 22:15:22 +00:00
snap::<S>
.chain(error_handler)
.label(SNAP)
.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))
.with_system(
2022-08-02 22:15:22 +00:00
snap::<S>
.chain(error_handler)
.label(SNAP)
.before(MOVEMENT_CONTROLS),
),
2021-05-13 17:25:45 +00:00
);
}
}
}
}