#![allow(dead_code)]
use bevy::{ecs::query::QueryData, prelude::*};
use bevy_ecs_tiled::prelude::*;
use bevy_northstar::prelude::*;
#[derive(Resource, Debug, Default)]
pub struct Config {
pub mode: PathfindMode,
pub random_rebuild: bool,
pub paused: bool,
}
#[derive(Clone, Debug, Default, Hash, Eq, States, PartialEq)]
pub enum State {
#[default]
Loading,
Playing,
}
#[derive(QueryData)]
#[query_data(derive(Debug))]
pub struct MapQuery {
pub grid_size: &'static TilemapGridSize,
pub map_size: &'static TilemapSize,
pub tile_size: &'static TilemapTileSize,
pub map_type: &'static TilemapType,
pub anchor: &'static TilemapAnchor,
}
pub struct SharedPlugin<N: Neighborhood + 'static> {
_marker: std::marker::PhantomData<N>,
}
impl<N: Neighborhood + 'static> Default for SharedPlugin<N> {
fn default() -> Self {
SharedPlugin {
_marker: std::marker::PhantomData,
}
}
}
impl<N: Neighborhood + 'static> Plugin for SharedPlugin<N> {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup_hud)
.add_systems(
Update,
(
input::<N>,
tick.run_if(in_state(State::Playing)),
update_stat_text.run_if(in_state(State::Playing)),
update_pathfind_type_text.run_if(in_state(State::Playing)),
update_collision_text::<N>.run_if(in_state(State::Playing)),
update_debug_cursor.run_if(in_state(State::Playing)),
),
)
.add_message::<Tick>()
.insert_state(State::Loading)
.insert_resource(Walkable::default())
.insert_resource(Stats::default())
.insert_resource(Config::default());
}
}
#[derive(Resource, Debug, Default)]
pub struct Walkable {
pub tiles: Vec<Vec3>,
}
#[derive(Message, Default)]
pub struct Tick;
pub fn tick(time: Res<Time>, mut tick_writer: MessageWriter<Tick>, config: Res<Config>) {
if config.paused {
return;
}
if time.elapsed_secs() % 0.25 < time.delta_secs() {
tick_writer.write_default();
}
}
#[derive(Component, Debug)]
pub struct StatText;
#[derive(Component, Debug)]
struct CollisionText;
#[derive(Component, Debug)]
pub struct PathfindTypeText;
pub fn setup_hud(mut commands: Commands) {
commands
.spawn((
Text::new("Key [p]| Algorithm: "),
TextFont {
font_size: 24.0,
..default()
},
Node {
position_type: PositionType::Absolute,
bottom: Val::Px(50.0),
left: Val::Px(0.0),
..default()
},
))
.with_child((
TextSpan::default(),
TextFont {
font_size: 24.0,
..default()
},
PathfindTypeText,
));
commands
.spawn((
Text::new("Avg Path Time: "),
TextFont {
font_size: 24.0,
..default()
},
Node {
position_type: PositionType::Absolute,
bottom: Val::Px(100.0),
left: Val::Px(0.0),
..default()
},
))
.with_child((
TextSpan::default(),
TextFont {
font_size: 24.0,
..default()
},
StatText,
));
commands
.spawn((
Text::new("Key [c]| Collision: "),
TextFont {
font_size: 24.0,
..default()
},
Node {
position_type: PositionType::Absolute,
bottom: Val::Px(150.0),
left: Val::Px(0.0),
..default()
},
))
.with_child((
TextSpan::default(),
TextFont {
font_size: 24.0,
..default()
},
CollisionText,
));
}
pub fn update_stat_text(stats: Res<Stats>, mut query: Query<&mut TextSpan, With<StatText>>) {
for mut span in &mut query {
**span = format!("{:.2}ms", stats.pathfinding.average_time * 1000.0);
}
}
fn update_collision_text<N: Neighborhood + 'static>(
stats: Res<Stats>,
mut query: Query<&mut TextSpan, With<CollisionText>>,
grid: Query<&Grid<N>>,
) {
let grid = if let Ok(grid) = grid.single() {
grid
} else {
return;
};
for mut span in &mut query {
if grid.collision() {
**span = format!("{:.2}ms", stats.collision.average_time * 1000.0);
} else {
**span = "Off".to_string();
}
}
}
pub fn update_pathfind_type_text(
config: Res<Config>,
mut query: Query<&mut TextSpan, With<PathfindTypeText>>,
) {
for mut span in &mut query {
**span = match config.mode {
PathfindMode::Refined => "HPA* Refined".to_string(),
PathfindMode::Coarse => "HPA* Coarse".to_string(),
PathfindMode::AStar => "A*".to_string(),
PathfindMode::Waypoints => "Waypoints".to_string(),
PathfindMode::ThetaStar => "Theta*".to_string(),
};
}
}
#[allow(clippy::too_many_arguments)]
pub fn input<N: Neighborhood + 'static>(
time: Res<Time>,
keyboard_input: Res<ButtonInput<KeyCode>>,
camera: Single<(&mut Transform, &mut Projection), With<Camera>>,
mut pathfinders: Query<(Entity, &Pathfind)>,
mut config: ResMut<Config>,
mut stats: ResMut<Stats>,
grid: Single<&mut Grid<N>>,
mut commands: Commands,
) {
let mut grid = grid.into_inner();
let (mut transform, mut projection) = camera.into_inner();
if let Projection::Orthographic(ref mut ortho) = &mut *projection {
let mut direction = Vec3::ZERO;
if keyboard_input.pressed(KeyCode::KeyA) {
direction -= Vec3::new(1.0, 0.0, 0.0);
}
if keyboard_input.pressed(KeyCode::KeyD) {
direction += Vec3::new(1.0, 0.0, 0.0);
}
if keyboard_input.pressed(KeyCode::KeyW) {
direction += Vec3::new(0.0, 1.0, 0.0);
}
if keyboard_input.pressed(KeyCode::KeyS) {
direction -= Vec3::new(0.0, 1.0, 0.0);
}
if keyboard_input.pressed(KeyCode::KeyZ) {
ortho.scale += 0.1;
}
if keyboard_input.pressed(KeyCode::KeyX) {
ortho.scale -= 0.1;
}
if keyboard_input.just_pressed(KeyCode::Space) {
config.paused = !config.paused;
}
if keyboard_input.just_pressed(KeyCode::KeyR) {
config.random_rebuild = !config.random_rebuild;
}
if keyboard_input.just_pressed(KeyCode::KeyP) {
config.mode = match config.mode {
PathfindMode::Refined => PathfindMode::Coarse,
PathfindMode::Coarse => PathfindMode::AStar,
PathfindMode::AStar => PathfindMode::Waypoints,
PathfindMode::Waypoints => PathfindMode::ThetaStar,
PathfindMode::ThetaStar => PathfindMode::Refined,
};
stats.reset_pathfinding();
stats.reset_collision();
for (entity, _) in pathfinders.iter_mut() {
commands
.entity(entity)
.remove::<Pathfind>()
.remove::<NextPos>()
.remove::<Path>();
}
}
if keyboard_input.just_pressed(KeyCode::KeyC) {
let current_collision = grid.collision();
grid.set_collision(!current_collision);
stats.reset_collision();
for (entity, _) in pathfinders.iter_mut() {
commands
.entity(entity)
.remove::<Pathfind>()
.remove::<NextPos>()
.remove::<Path>();
}
}
if ortho.scale < 0.3 {
ortho.scale = 0.3;
}
let z = transform.translation.z;
transform.translation += time.delta_secs() * direction * 500.;
transform.translation.z = z;
}
}
pub fn update_debug_cursor(
window: Single<&Window>,
camera: Single<(&Camera, &GlobalTransform)>,
debug_cursor: Single<&mut DebugCursor>,
) {
let window = window.into_inner();
let (camera, camera_transform) = camera.into_inner();
let mut debug_cursor = debug_cursor.into_inner();
if let Some(cursor_position) = window
.cursor_position()
.and_then(|cursor| camera.viewport_to_world_2d(camera_transform, cursor).ok())
{
debug_cursor.0 = Some(cursor_position);
}
}