use avian2d::prelude::*; use bevy::{prelude::*, utils::HashMap}; use leafwing_input_manager::{plugin::InputManagerSystem, prelude::*}; use pathfinding::prelude::*; use crate::{ core::{GlobalTransformExt, Obstacle}, map::Map, 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) } } pub fn find_path( start: IVec2, destination: IVec2, map: &Map, cost_map: &Option, ) -> Option<(Vec, u32)> { astar( &start, |p| { let mut successors: Vec<(IVec2, u32)> = vec![]; if p.x >= 0 && p.y >= 0 { if let Some(tile) = map.at(p.x as usize, p.y as usize) { if tile.is_walkable() { for tile in map.get_available_exits(p.x as usize, p.y as usize) { let mut cost = tile.2 * 100.; if let Some(cost_map) = cost_map { if let Some(modifier) = cost_map.get(p) { cost *= modifier; } } successors .push((IVec2::new(tile.0 as i32, tile.1 as i32), cost as u32)); } } } } successors }, |p| (p.distance_squared(destination) * 100) as u32, |p| *p == destination, ) } fn calculate_path( mut commands: Commands, spatial_query: SpatialQuery, mut query: Query< ( Entity, &Destination, &GlobalTransform, &Collider, Option<&CostMap>, Option<&mut ActionState>, ), Changed, >, obstructions: Query>, sensors: Query>, ) { for (entity, destination, transform, collider, cost_map, actions) in &mut query { trace!("{entity}: destination: {destination:?}"); if transform.translation().truncate().as_ivec2() == **destination { commands .entity(entity) .remove::() .remove::() .remove::(); continue; } commands.entity(entity).remove::().remove::(); let path = astar( &transform.translation().truncate().as_ivec2(), |p| { 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 should_push = true; 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 hits = spatial_query.shape_intersections( collider, check, transform.yaw().as_radians(), &SpatialQueryFilter::from_excluded_entities(&sensors), ); for entity in &hits { if obstructions.contains(*entity) { should_push = false; break; } } if should_push { 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