blackout/src/navigation.rs

611 lines
20 KiB
Rust
Raw Normal View History

2021-05-26 21:46:20 +00:00
use std::{collections::HashMap, error::Error, fmt::Debug, hash::Hash, marker::PhantomData};
2021-05-13 17:25:45 +00:00
use bevy::prelude::*;
use bevy_input_actionmap::InputMap;
2021-06-02 20:50:38 +00:00
use bevy_rapier2d::prelude::*;
2021-05-13 17:25:45 +00:00
use bevy_tts::Tts;
use derive_more::{Deref, DerefMut};
use crate::{
core::{Angle, CardinalDirection, Coordinates, Player, PointLike},
error::error_handler,
exploration::{ExplorationFocused, Exploring},
map::{ITileType, Map},
pathfinding::Destination,
};
#[derive(Clone, Copy, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct BlocksMotion;
#[derive(Clone, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct CollisionsMonitored(pub Vec<bool>);
#[derive(Clone, Copy, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct MaxSpeed(pub f32);
impl Default for MaxSpeed {
fn default() -> Self {
MaxSpeed(2.)
}
}
#[derive(Clone, Copy, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct MonitorsCollisions;
#[derive(Clone, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct MotionBlocked(pub Vec<bool>);
#[derive(Clone, Copy, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct RotationSpeed(pub Angle);
impl Default for RotationSpeed {
fn default() -> Self {
Self(Angle::Radians(0.))
}
}
#[derive(Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct Speed(pub f32);
#[derive(Clone, Copy, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct Sprinting;
#[derive(Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct Velocity(pub Vec2);
#[derive(Clone, Copy, Debug)]
pub struct Collision {
pub entity: Entity,
pub coordinates: (f32, f32),
pub index: usize,
}
2021-06-02 20:50:38 +00:00
fn add_map_colliders(mut commands: Commands, maps: Query<(Entity, &Map), Added<Map>>) {
for (map_entity, map) in maps.iter() {
let rigid_body_entity = commands
.entity(map_entity)
.insert_bundle(RigidBodyBundle {
body_type: RigidBodyType::Static,
..Default::default()
})
.id();
for x in 0..map.width() {
for y in 0..map.height() {
let tile = map.base.at(x, y);
if tile.blocks_motion() {
let collider = ColliderBundle {
shape: ColliderShape::cuboid(1., 1.),
..Default::default()
};
let collider_parent = ColliderParent {
handle: rigid_body_entity.handle(),
pos_wrt_parent: Vec2::new(x as f32 + 0.5, y as f32 + 0.5).into(),
};
commands
.spawn()
.insert_bundle(collider)
.insert(collider_parent);
}
}
}
}
}
2021-05-26 21:46:20 +00:00
fn movement_controls<S, A: 'static>(
2021-05-13 17:25:45 +00:00
mut commands: Commands,
2021-05-26 21:46:20 +00:00
config: Res<NavigationConfig<S, A>>,
input: Res<InputMap<A>>,
2021-05-13 17:25:45 +00:00
time: Res<Time>,
mut query: Query<(
Entity,
&Player,
&mut Velocity,
&mut Speed,
&MaxSpeed,
Option<&RotationSpeed>,
&mut Transform,
Option<&Destination>,
)>,
exploration_focused: Query<(Entity, &ExplorationFocused)>,
2021-05-26 21:46:20 +00:00
) where
S: bevy::ecs::component::Component + Clone + Debug + Eq + Hash,
A: Hash + Eq + Clone + Send + Sync,
{
2021-05-13 17:25:45 +00:00
for (
entity,
_,
mut velocity,
mut speed,
max_speed,
rotation_speed,
mut transform,
destination,
) in query.iter_mut()
{
2021-05-26 21:46:20 +00:00
let sprinting = if let Some(action) = config.action_sprint.clone() {
input.active(action)
} else {
false
};
2021-05-13 17:25:45 +00:00
if sprinting {
commands.entity(entity).insert(Sprinting::default());
} else {
commands.entity(entity).remove::<Sprinting>();
}
let mut direction = Vec3::default();
2021-05-26 21:46:20 +00:00
if let Some(action) = config.action_forward.clone() {
if input.active(action) {
direction.x += 1.;
}
2021-05-13 17:25:45 +00:00
}
2021-05-26 21:46:20 +00:00
if let Some(action) = config.action_backward.clone() {
if input.active(action) {
direction.x -= 1.;
}
2021-05-13 17:25:45 +00:00
}
2021-05-26 21:46:20 +00:00
if let Some(action) = config.action_left.clone() {
if input.active(action) {
direction.y += 1.;
}
2021-05-13 17:25:45 +00:00
}
2021-05-26 21:46:20 +00:00
if let Some(action) = config.action_right.clone() {
if input.active(action) {
direction.y -= 1.;
}
2021-05-13 17:25:45 +00:00
}
2021-05-26 21:46:20 +00:00
if let (Some(rotation_speed), Some(rotate_left), Some(rotate_right)) = (
rotation_speed,
config.action_rotate_left.clone(),
config.action_rotate_right.clone(),
) {
2021-05-13 17:25:45 +00:00
let delta = rotation_speed.radians() * time.delta_seconds();
2021-05-26 21:46:20 +00:00
if input.active(rotate_left) {
2021-05-13 17:25:45 +00:00
transform.rotate(Quat::from_rotation_z(delta));
}
2021-05-26 21:46:20 +00:00
if input.active(rotate_right) {
2021-05-13 17:25:45 +00:00
transform.rotate(Quat::from_rotation_z(-delta));
}
}
if direction.length_squared() != 0. {
direction = direction.normalize();
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;
}
2021-05-26 21:46:20 +00:00
let strength = if let (Some(forward), Some(backward), Some(left), Some(right)) = (
config.action_forward.clone(),
config.action_backward.clone(),
config.action_left.clone(),
config.action_right.clone(),
) {
let forward_x = input.strength(forward).abs();
let backward_x = input.strength(backward).abs();
let x = if forward_x > backward_x {
forward_x
} else {
backward_x
};
let right_y = input.strength(right).abs();
let left_y = input.strength(left).abs();
let y = if right_y > left_y { right_y } else { left_y };
Some(Vec3::new(x, y, 0.))
2021-05-13 17:25:45 +00:00
} else {
2021-05-26 21:46:20 +00:00
None
2021-05-13 17:25:45 +00:00
};
let s = if sprinting {
**max_speed
} else {
**max_speed / config.sprint_movement_factor
2021-05-13 17:25:45 +00:00
};
speed.0 = s;
direction *= s;
2021-05-26 21:46:20 +00:00
if let Some(strength) = strength {
direction *= strength;
}
2021-05-13 17:25:45 +00:00
commands.entity(entity).remove::<Destination>();
commands.entity(entity).remove::<Exploring>();
for (entity, _) in exploration_focused.iter() {
commands.entity(entity).remove::<ExplorationFocused>();
}
direction = transform.compute_matrix().transform_vector3(direction);
let direction = Vec2::new(direction.x, direction.y);
**velocity = direction;
} else if destination.is_none() {
**velocity = Vec2::ZERO;
speed.0 = 0.;
} else if sprinting {
speed.0 = max_speed.0;
} else {
speed.0 = max_speed.0 / 3.;
}
}
}
fn movement(
time: Res<Time>,
mut collision_events: EventWriter<Collision>,
map: Query<(&Map, &MotionBlocked, &CollisionsMonitored)>,
mut entities: Query<(Entity, &Velocity, &mut Coordinates, Option<&BlocksMotion>)>,
) {
for (entity, velocity, mut coordinates, blocks_motion) in entities.iter_mut() {
if **velocity != Vec2::ZERO {
let displacement = **velocity * time.delta_seconds();
let mut point = **coordinates;
point.0 += displacement.x;
point.1 += displacement.y;
if let Ok((map, motion_blocked, collisions_monitored)) = map.single() {
let idx = point.to_index(map.width());
if idx < map.base.tiles.len() {
let current_entities = &map.entities[idx];
if blocks_motion.is_some()
&& motion_blocked[idx]
&& !current_entities.contains(&entity)
{
collision_events.send(Collision {
entity,
coordinates: point,
index: idx,
});
} else {
**coordinates = point;
let current_entities = &map.entities[idx];
if collisions_monitored[idx] && !current_entities.contains(&entity) {
collision_events.send(Collision {
entity,
coordinates: point,
index: idx,
});
}
}
}
} else {
**coordinates = point;
}
}
}
}
pub(crate) fn set_motion_blocked(
map: &Map,
index: usize,
motion_blockers: &Query<&BlocksMotion>,
motion_blocked: &mut MotionBlocked,
) {
let mut new_motion_blocked = map.base.tiles[index].blocks_motion();
if !new_motion_blocked {
for e in &map.entities[index] {
if motion_blockers.get(*e).is_ok() {
new_motion_blocked = true;
break;
}
}
}
motion_blocked[index] = new_motion_blocked;
}
2021-05-13 17:25:45 +00:00
pub const UPDATE_COLLISION_INDEX_LABEL: &str = "UPDATE_COLLISION_INDEX";
#[derive(Default, Deref, DerefMut)]
struct PreviousBlocksMotionIndex(HashMap<Entity, usize>);
fn blocks_motion_indexing(
mut map: Query<(&Map, &mut MotionBlocked)>,
mut prev_index: ResMut<PreviousBlocksMotionIndex>,
query: Query<
(Entity, &Coordinates, &BlocksMotion),
Or<(Changed<Coordinates>, Changed<BlocksMotion>)>,
>,
motion_blockers: Query<&BlocksMotion>,
) {
for (entity, coordinates, _) in query.iter() {
for (map, mut motion_blocked) in map.iter_mut() {
let idx = coordinates.to_index(map.width());
if let Some(prev_idx) = prev_index.get(&entity) {
if *prev_idx == idx {
continue;
}
set_motion_blocked(map, *prev_idx, &motion_blockers, &mut motion_blocked);
2021-05-13 17:25:45 +00:00
}
motion_blocked[idx] = true;
prev_index.insert(entity, idx);
}
}
}
fn remove_blocks_motion(
mut prev_index: ResMut<PreviousBlocksMotionIndex>,
mut map: Query<(&Map, &mut MotionBlocked)>,
removed: RemovedComponents<BlocksMotion>,
coordinates: Query<&Coordinates>,
blocks_motion: Query<&BlocksMotion>,
) {
for entity in removed.iter() {
for (map, mut motion_blocked) in map.iter_mut() {
let prev = prev_index.get(&entity).cloned();
2021-05-13 17:25:45 +00:00
prev_index.remove(&entity);
if let Some(prev) = prev {
set_motion_blocked(&map, prev, &blocks_motion, &mut motion_blocked)
}
if let Ok(coordinates) = coordinates.get(entity) {
let idx = coordinates.to_index(map.width());
set_motion_blocked(&map, idx, &blocks_motion, &mut motion_blocked)
2021-05-13 17:25:45 +00:00
}
}
}
}
pub(crate) fn set_monitors_collisions(
map: &Map,
index: usize,
collision_monitors: &Query<&MonitorsCollisions>,
collisions_monitored: &mut CollisionsMonitored,
) {
let mut new_collisions_monitored = false;
for e in &map.entities[index] {
if collision_monitors.get(*e).is_ok() {
new_collisions_monitored = true;
break;
}
}
collisions_monitored[index] = new_collisions_monitored;
}
2021-05-13 17:25:45 +00:00
#[derive(Default, Deref, DerefMut)]
struct PreviousMonitorsCollisionsIndex(HashMap<Entity, usize>);
fn monitors_collisions_indexing(
mut map: Query<(&Map, &mut CollisionsMonitored)>,
mut prev_index: ResMut<PreviousMonitorsCollisionsIndex>,
query: Query<
(Entity, &Coordinates, &MonitorsCollisions),
Or<(Changed<Coordinates>, Changed<MonitorsCollisions>)>,
>,
collision_monitors: Query<&MonitorsCollisions>,
) {
for (entity, coordinates, _) in query.iter() {
for (map, mut collisions_monitored) in map.iter_mut() {
let idx = coordinates.to_index(map.width());
if let Some(prev_idx) = prev_index.get(&entity) {
if *prev_idx == idx {
continue;
}
set_monitors_collisions(
&map,
*prev_idx,
&collision_monitors,
&mut collisions_monitored,
);
2021-05-13 17:25:45 +00:00
}
collisions_monitored[idx] = true;
prev_index.insert(entity, idx);
}
}
}
fn remove_monitors_collisions(
mut prev_index: ResMut<PreviousMonitorsCollisionsIndex>,
mut map: Query<(&Map, &mut CollisionsMonitored)>,
removed: RemovedComponents<MonitorsCollisions>,
coordinates: Query<&Coordinates>,
monitors_collisions: Query<&MonitorsCollisions>,
) {
for entity in removed.iter() {
for (map, mut collisions_monitored) in map.iter_mut() {
let prev = prev_index.get(&entity).cloned();
prev_index.remove(&entity);
if let Some(prev) = prev {
set_monitors_collisions(
&map,
prev,
&monitors_collisions,
&mut collisions_monitored,
);
}
if let Ok(coordinates) = coordinates.get_component::<Coordinates>(entity) {
let idx = coordinates.to_index(map.width());
set_monitors_collisions(&map, idx, &monitors_collisions, &mut collisions_monitored);
2021-05-13 17:25:45 +00:00
}
}
}
}
fn add_collision_indices(
mut commands: Commands,
query: Query<
(Entity, &Map),
(
Added<Map>,
Without<MotionBlocked>,
Without<CollisionsMonitored>,
),
>,
) {
for (entity, map) in query.iter() {
let mut v = vec![];
for tile in &map.base.tiles {
v.push(tile.blocks_motion());
}
commands.entity(entity).insert(MotionBlocked(v));
let count = (map.width() * map.height()) as usize;
commands
.entity(entity)
.insert(CollisionsMonitored(vec![false; count]));
}
}
fn speak_direction(
mut tts: ResMut<Tts>,
mut cache: Local<HashMap<Entity, CardinalDirection>>,
player: Query<(Entity, &Player, &Transform), Changed<Transform>>,
) -> Result<(), Box<dyn Error>> {
if let Ok((entity, _, transform)) = player.single() {
let forward = transform.local_x();
let yaw = Angle::Radians(forward.y.atan2(forward.x));
if let Some(old_direction) = cache.get(&entity) {
let old_direction = *old_direction;
let direction: CardinalDirection = yaw.into();
if old_direction != direction {
let direction: String = direction.into();
tts.speak(direction, true)?;
2021-05-13 17:25:45 +00:00
}
cache.insert(entity, direction);
} else {
cache.insert(entity, yaw.into());
}
}
Ok(())
}
pub const MOVEMENT_LABEL: &str = "MOVEMENT";
#[derive(Clone, Debug)]
2021-05-26 21:46:20 +00:00
pub struct NavigationConfig<S, A> {
pub action_backward: Option<A>,
pub action_forward: Option<A>,
pub action_left: Option<A>,
pub action_right: Option<A>,
pub action_rotate_left: Option<A>,
pub action_rotate_right: Option<A>,
pub action_sprint: Option<A>,
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_states: Vec<S>,
pub movement_control_states: Vec<S>,
}
2021-05-26 21:46:20 +00:00
impl<S, A> Default for NavigationConfig<S, A> {
2021-05-13 17:25:45 +00:00
fn default() -> Self {
Self {
2021-05-26 21:46:20 +00:00
action_backward: None,
action_forward: None,
action_left: None,
action_right: None,
action_rotate_left: None,
action_rotate_right: None,
action_sprint: None,
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_states: vec![],
movement_control_states: vec![],
}
}
}
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
}
}
2021-05-26 21:46:20 +00:00
impl<'a, S, A> Plugin for NavigationPlugin<'a, S, A>
2021-05-13 17:25:45 +00:00
where
S: bevy::ecs::component::Component + Clone + Debug + Eq + Hash,
2021-05-26 21:46:20 +00:00
A: Hash + Eq + Clone + Send + Sync,
2021-05-13 17:25:45 +00:00
'a: 'static,
{
fn build(&self, app: &mut AppBuilder) {
2021-05-26 21:46:20 +00:00
if !app.world().contains_resource::<NavigationConfig<S, A>>() {
app.insert_resource(NavigationConfig::<S, A>::default());
2021-05-13 17:25:45 +00:00
}
let config = app
.world()
2021-05-26 21:46:20 +00:00
.get_resource::<NavigationConfig<S, A>>()
2021-05-13 17:25:45 +00:00
.unwrap()
.clone();
app.register_type::<MaxSpeed>()
.register_type::<RotationSpeed>()
.register_type::<Sprinting>()
.add_event::<Collision>()
.insert_resource(PreviousBlocksMotionIndex::default())
2021-06-02 20:50:38 +00:00
.add_system(add_map_colliders.system())
2021-05-13 17:25:45 +00:00
.add_system_to_stage(
CoreStage::PostUpdate,
blocks_motion_indexing
.system()
.after(crate::map::UPDATE_ENTITY_INDEX_LABEL)
.label(UPDATE_COLLISION_INDEX_LABEL),
)
.add_system_to_stage(
CoreStage::PostUpdate,
remove_blocks_motion
.system()
2021-05-23 17:23:56 +00:00
.after(crate::map::UPDATE_ENTITY_INDEX_LABEL)
2021-05-13 17:25:45 +00:00
.before(UPDATE_COLLISION_INDEX_LABEL),
)
.insert_resource(PreviousMonitorsCollisionsIndex::default())
.add_system_to_stage(
CoreStage::PostUpdate,
monitors_collisions_indexing
.system()
.after(crate::map::UPDATE_ENTITY_INDEX_LABEL)
.label(UPDATE_COLLISION_INDEX_LABEL),
)
.add_system_to_stage(
CoreStage::PostUpdate,
remove_monitors_collisions
.system()
2021-05-23 17:23:56 +00:00
.after(crate::map::UPDATE_ENTITY_INDEX_LABEL)
2021-05-13 17:25:45 +00:00
.before(UPDATE_COLLISION_INDEX_LABEL),
)
.add_system_to_stage(
CoreStage::PostUpdate,
monitors_collisions_indexing
.system()
.after(crate::map::UPDATE_ENTITY_INDEX_LABEL)
.label(UPDATE_COLLISION_INDEX_LABEL),
)
.add_system(add_collision_indices.system())
.add_system(speak_direction.system().chain(error_handler.system()))
.add_system_to_stage(CoreStage::PostUpdate, add_collision_indices.system());
if config.movement_states.is_empty() {
app.add_system(
movement
.system()
.label(MOVEMENT_LABEL)
.before(crate::map::UPDATE_ENTITY_INDEX_LABEL),
);
} else {
let states = config.movement_states;
for state in states {
app.add_system_set(
SystemSet::on_update(state).with_system(
movement
.system()
.label(MOVEMENT_LABEL)
.before(crate::map::UPDATE_ENTITY_INDEX_LABEL),
),
);
}
}
if config.movement_control_states.is_empty() {
2021-05-26 21:46:20 +00:00
app.add_system(movement_controls::<S, A>.system().before(MOVEMENT_LABEL));
2021-05-13 17:25:45 +00:00
} else {
let states = config.movement_control_states;
for state in states {
app.add_system_set(
SystemSet::on_update(state)
2021-05-26 21:46:20 +00:00
.with_system(movement_controls::<S, A>.system().before(MOVEMENT_LABEL)),
2021-05-13 17:25:45 +00:00
);
}
}
}
}