use avian2d::prelude::*; use bevy::{prelude::*, utils::HashMap}; use leafwing_input_manager::{plugin::InputManagerSystem, prelude::*}; use pathfinding::prelude::*; use crate::{ core::{GlobalTransformExt, Obstacle}, navigation::{NavigationAction, RotationSpeed, Speed}, }; #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug, Reflect)] pub struct NegotiatePathAction; impl Actionlike for NegotiatePathAction { fn input_control_kind(&self) -> InputControlKind { InputControlKind::DualAxis } } #[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Eq, Hash, PartialEq, Reflect)] #[reflect(Component)] pub struct Destination(pub IVec2); #[derive(Component, Clone, Debug, Default, Reflect)] #[reflect(Component)] pub struct NoPath; #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct Path(pub Vec); #[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)] #[reflect(Component)] pub struct CostMap(pub HashMap); #[derive(Bundle, Deref, DerefMut)] pub struct PathfindingControlsBundle(pub ActionState); impl Default for PathfindingControlsBundle { fn default() -> Self { let mut input: ActionState = Default::default(); input.disable(); Self(input) } } fn calculate_path( mut commands: Commands, spatial_query: SpatialQuery, mut query: Query< ( Entity, &Destination, &GlobalTransform, &Collider, Option<&CostMap>, Option<&mut ActionState>, ), Changed, >, obstacles: Query>, sensors: Query>, ) { for (entity, destination, transform, collider, cost_map, actions) in &mut query { trace!("{entity}: destination: {destination:?}"); commands.entity(entity).remove::().remove::(); if transform.translation().truncate().as_ivec2() == **destination { commands.entity(entity).remove::(); continue; } let path = astar( &transform.translation().truncate().as_ivec2(), |p| { let mut start = Vec2::new(p.x as f32, p.y as f32); if start.x >= 0. { start.x += 0.5; } else { start.x -= 0.5; } if start.y >= 0. { start.y += 0.5; } else { start.y -= 0.5; } let mut successors: Vec<(IVec2, u32)> = vec![]; let x = p.x; let y = p.y; let exits = vec![ (IVec2::new(x - 1, y), 1.), (IVec2::new(x + 1, y), 1.), (IVec2::new(x, y - 1), 1.), (IVec2::new(x, y + 1), 1.), (IVec2::new(x - 1, y - 1), 1.5), (IVec2::new(x + 1, y - 1), 1.5), (IVec2::new(x - 1, y + 1), 1.5), (IVec2::new(x + 1, y + 1), 1.5), ]; for exit in &exits { let mut check = exit.0.as_vec2(); if check.x >= 0. { check.x += 0.5; } else { check.x -= 0.5; } if check.y >= 0. { check.y += 0.5; } else { check.y -= 0.5; } let dir = (check - start).normalize(); let dir = Dir2::new_unchecked(dir); let delta = (check - start).length(); let hits = spatial_query.cast_shape_predicate( collider, start, transform.yaw().as_radians(), dir, &ShapeCastConfig { max_distance: delta, ignore_origin_penetration: true, ..default() }, &SpatialQueryFilter::from_excluded_entities(&sensors), &|entity| obstacles.contains(entity), ); if hits.is_none() { let mut cost = exit.1 * 100.; if let Some(cost_map) = cost_map { if let Some(modifier) = cost_map.get(&exit.0) { cost *= modifier; } } successors.push((exit.0, cost as u32)); } } successors }, |p| (p.distance_squared(**destination) * 100) as u32, |p| *p == **destination, ); if let Some(path) = path { commands.entity(entity).insert(Path(path.0)); } else { commands.entity(entity).insert(NoPath); if let Some(mut actions) = actions { trace!("{entity:?}: Disabling and resetting because no path"); actions.disable(); actions.reset_all(); } } } } fn remove_destination(mut commands: Commands, mut removed: RemovedComponents) { for entity in removed.read() { if let Some(mut commands) = commands.get_entity(entity) { commands.remove::().remove::(); } } } fn negotiate_path( mut commands: Commands, time: Res