diff --git a/Cargo.lock b/Cargo.lock index cbd3b59..9162696 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,6 +176,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" + [[package]] name = "approx" version = "0.5.1" @@ -3079,6 +3085,7 @@ dependencies = [ name = "maze-ascension" version = "0.2.0" dependencies = [ + "anyhow", "bevy", "bevy-inspector-egui", "bevy_egui", diff --git a/Cargo.toml b/Cargo.toml index 5059061..199dd56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ hexlab = { version = "0.3", features = ["bevy"] } bevy-inspector-egui = { version = "0.28", optional = true } bevy_egui = { version = "0.31", optional = true } thiserror = "2.0" +anyhow = "1" [features] diff --git a/src/dev_tools/ui/maze_controls.rs b/src/dev_tools/ui/maze_controls.rs index f611a2c..5767ca9 100644 --- a/src/dev_tools/ui/maze_controls.rs +++ b/src/dev_tools/ui/maze_controls.rs @@ -1,6 +1,7 @@ use crate::{ - maze::{events::RecreateMazeEvent, MazeConfig, MazePluginLoaded}, - player::events::RespawnPlayer, + floor::components::{CurrentFloor, Floor}, + maze::{components::MazeConfig, events::MazeEvent, GlobalMazeConfig, MazePluginLoaded}, + player::events::PlayerEvent, }; use bevy::{prelude::*, window::PrimaryWindow}; use bevy_egui::{ @@ -22,42 +23,50 @@ pub(crate) fn maze_controls_ui(world: &mut World) { else { return; }; - let mut egui_context = egui_context.clone(); + let Ok((maze_config, floor)) = world + .query_filtered::<(&MazeConfig, &Floor), With>() + .get_single(world) + else { + return; + }; + let mut maze_config = maze_config.clone(); + let floor_value = floor.0; + + let mut changed = false; + egui::Window::new("Maze Controls").show(egui_context.get_mut(), |ui| { - if let Some(mut maze_config) = world.get_resource_mut::() { - let mut changed = false; + if let Some(mut global_config) = world.get_resource_mut::() { ui.heading("Maze Configuration"); changed |= add_seed_control(ui, &mut maze_config.seed); - changed |= add_drag_value_control(ui, "Radius:", &mut maze_config.radius, 1.0, 1..=100); changed |= - add_drag_value_control(ui, "Height:", &mut maze_config.height, 0.5, 1.0..=50.0); + add_drag_value_control(ui, "Height:", &mut global_config.height, 0.5, 1.0..=50.0); changed |= add_drag_value_control( ui, "Hex Size:", - &mut maze_config.hex_size, + &mut global_config.hex_size, 1.0, 1.0..=100.0, ); - changed |= add_orientation_control(ui, &mut maze_config.layout.orientation); - changed |= add_position_control(ui, "Start Position:", &mut maze_config.start_pos); changed |= add_position_control(ui, "End Position:", &mut maze_config.end_pos); - // Trigger recreation if any value changed + // Handle updates if changed { - maze_config.update(); - if let Some(mut event_writer) = - world.get_resource_mut::>() - { - event_writer.send(RecreateMazeEvent { floor: 1 }); + maze_config.update(&global_config); + + if let Some(mut event_writer) = world.get_resource_mut::>() { + event_writer.send(MazeEvent::Recreate { + floor: floor_value, + config: maze_config, + }); } - if let Some(mut event_writer) = world.get_resource_mut::>() { - event_writer.send(RespawnPlayer); + if let Some(mut event_writer) = world.get_resource_mut::>() { + event_writer.send(PlayerEvent::Respawn); } } } diff --git a/src/floor/components.rs b/src/floor/components.rs new file mode 100644 index 0000000..de73815 --- /dev/null +++ b/src/floor/components.rs @@ -0,0 +1,19 @@ +use bevy::prelude::*; + +#[derive(Debug, Reflect, Component)] +#[reflect(Component)] +pub struct Floor(pub u8); + +#[derive(Debug, Reflect, Component)] +#[reflect(Component)] +pub struct TargetFloor(pub u8); + +#[derive(Debug, Reflect, Component)] +#[reflect(Component)] +pub struct CurrentFloor; + +impl Default for Floor { + fn default() -> Self { + Self(1) + } +} diff --git a/src/floor/mod.rs b/src/floor/mod.rs new file mode 100644 index 0000000..f6e6b6a --- /dev/null +++ b/src/floor/mod.rs @@ -0,0 +1,6 @@ +pub mod components; +mod systems; + +use bevy::prelude::*; + +pub(super) fn plugin(app: &mut App) {} diff --git a/src/floor/systems/mod.rs b/src/floor/systems/mod.rs new file mode 100644 index 0000000..38d4d38 --- /dev/null +++ b/src/floor/systems/mod.rs @@ -0,0 +1 @@ +mod spawn; diff --git a/src/floor/systems/spawn.rs b/src/floor/systems/spawn.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index 2ca0c9e..98a0536 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,9 @@ mod asset_tracking; pub mod audio; #[cfg(feature = "dev")] mod dev_tools; -mod maze; -mod player; +pub mod floor; +pub mod maze; +pub mod player; mod screens; pub mod theme; diff --git a/src/maze/assets.rs b/src/maze/assets.rs index 9d56b1f..011d406 100644 --- a/src/maze/assets.rs +++ b/src/maze/assets.rs @@ -1,4 +1,4 @@ -use super::MazeConfig; +use super::resources::GlobalMazeConfig; use bevy::prelude::*; use std::f32::consts::FRAC_PI_2; @@ -17,13 +17,16 @@ impl MazeAssets { pub(crate) fn new( meshes: &mut ResMut>, materials: &mut ResMut>, - config: &MazeConfig, + global_config: &GlobalMazeConfig, ) -> MazeAssets { MazeAssets { - hex_mesh: meshes.add(generate_hex_mesh(config.hex_size, config.height)), + hex_mesh: meshes.add(generate_hex_mesh( + global_config.hex_size, + global_config.height, + )), wall_mesh: meshes.add(generate_square_mesh( - config.hex_size + config.wall_size() / WALL_OVERLAP_MODIFIER, - config.wall_size(), + global_config.hex_size + global_config.wall_size() / WALL_OVERLAP_MODIFIER, + global_config.wall_size(), )), hex_material: materials.add(white_material()), wall_material: materials.add(Color::BLACK), diff --git a/src/maze/components.rs b/src/maze/components.rs index badb2b0..fccb97b 100644 --- a/src/maze/components.rs +++ b/src/maze/components.rs @@ -1,18 +1,90 @@ +use crate::floor::components::Floor; + +use super::{errors::MazeConfigError, GlobalMazeConfig}; use bevy::prelude::*; use hexlab::HexMaze; +use hexx::{Hex, HexLayout, HexOrientation}; +use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng}; #[derive(Debug, Reflect, Component)] #[reflect(Component)] -pub(crate) struct Maze(pub(crate) HexMaze); +#[require(MazeConfig, Floor)] +pub struct Maze(pub HexMaze); #[derive(Debug, Reflect, Component)] #[reflect(Component)] -pub(crate) struct Floor(pub(crate) u8); +pub struct Tile; #[derive(Debug, Reflect, Component)] #[reflect(Component)] -pub(crate) struct Tile; +pub struct Wall; -#[derive(Debug, Reflect, Component)] +#[derive(Debug, Reflect, Component, Clone)] #[reflect(Component)] -pub(crate) struct Wall; +pub struct MazeConfig { + pub radius: u32, + pub start_pos: Hex, + pub end_pos: Hex, + pub seed: u64, + pub layout: HexLayout, +} + +impl MazeConfig { + fn new( + radius: u32, + orientation: HexOrientation, + seed: Option, + global_conig: &GlobalMazeConfig, + ) -> Result { + let seed = seed.unwrap_or_else(|| thread_rng().gen()); + let mut rng = StdRng::seed_from_u64(seed); + + let start_pos = generate_pos(radius, &mut rng)?; + let end_pos = generate_pos(radius, &mut rng)?; + + info!("Start pos: (q={}, r={})", start_pos.x, start_pos.y); + info!("End pos: (q={}, r={})", end_pos.x, end_pos.y); + + let layout = HexLayout { + orientation, + hex_size: Vec2::splat(global_conig.hex_size), + ..default() + }; + + Ok(Self { + radius, + start_pos, + end_pos, + seed, + layout, + }) + } + + pub fn new_unchecked( + radius: u32, + orientation: HexOrientation, + seed: Option, + global_conig: &GlobalMazeConfig, + ) -> Self { + Self::new(radius, orientation, seed, global_conig) + .expect("Failed to create MazeConfig with supposedly safe values") + } + + pub fn update(&mut self, global_conig: &GlobalMazeConfig) { + self.layout.hex_size = Vec2::splat(global_conig.hex_size); + } +} + +impl Default for MazeConfig { + fn default() -> Self { + Self::new_unchecked(7, HexOrientation::Flat, None, &GlobalMazeConfig::default()) + } +} + +fn generate_pos(radius: u32, rng: &mut R) -> Result { + let radius = i32::try_from(radius)?; + Ok(Hex::new( + rng.gen_range(-radius..radius), + rng.gen_range(-radius..radius), + )) +} diff --git a/src/maze/errors.rs b/src/maze/errors.rs new file mode 100644 index 0000000..005ff83 --- /dev/null +++ b/src/maze/errors.rs @@ -0,0 +1,38 @@ +use std::num::TryFromIntError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum MazeConfigError { + #[error("Failed to convert radius from u32 to i32: {0}")] + RadiusConverions(#[from] TryFromIntError), + #[error("Invalid maze configuration: {0}")] + InvalidConfig(String), +} + +#[derive(Debug, Error)] +pub enum MazeError { + #[error("Floor {0} not found")] + FloorNotFound(u8), + #[error("Failed to generate maze with config: {radius}, seed: {seed}")] + GenerationFailed { radius: u32, seed: u64 }, + #[error("Invalid tile entity: {0:?}")] + TileNotFound(bevy::prelude::Entity), + #[error("Failed to create maze assets")] + AssetCreationFailed, + #[error("Invalid maze configuration: {0}")] + ConfigurationError(String), + #[error(transparent)] + Other(#[from] anyhow::Error), +} + +pub type MazeResult = Result; + +impl MazeError { + pub fn config_error(msg: impl Into) -> Self { + Self::ConfigurationError(msg.into()) + } + + pub fn generation_failed(radius: u32, seed: u64) -> Self { + Self::GenerationFailed { radius, seed } + } +} diff --git a/src/maze/events.rs b/src/maze/events.rs index 2a10115..a860e0d 100644 --- a/src/maze/events.rs +++ b/src/maze/events.rs @@ -1,6 +1,10 @@ use bevy::prelude::*; +use super::components::MazeConfig; + #[derive(Debug, Event)] -pub(crate) struct RecreateMazeEvent { - pub(crate) floor: u8, +pub enum MazeEvent { + Create { floor: u8, config: MazeConfig }, + Recreate { floor: u8, config: MazeConfig }, + Remove { floor: u8 }, } diff --git a/src/maze/mod.rs b/src/maze/mod.rs index db58ab1..3731cb3 100644 --- a/src/maze/mod.rs +++ b/src/maze/mod.rs @@ -1,20 +1,23 @@ -use bevy::{ecs::system::RunSystemOnce, prelude::*}; -use events::RecreateMazeEvent; mod assets; pub mod components; +pub mod errors; pub mod events; -mod resources; +pub mod resources; mod systems; -pub use resources::{MazeConfig, MazePluginLoaded}; +use bevy::{ecs::system::RunSystemOnce, prelude::*}; +use components::Maze; +use events::MazeEvent; +pub use resources::{GlobalMazeConfig, MazePluginLoaded}; pub(super) fn plugin(app: &mut App) { - app.init_resource::() - .add_event::() + app.init_resource::() + .add_event::() + .register_type::() .add_plugins(systems::plugin); } pub fn spawn_level_command(world: &mut World) { - world.insert_resource(MazePluginLoaded); let _ = world.run_system_once(systems::setup::setup); + world.insert_resource(MazePluginLoaded); } diff --git a/src/maze/resources.rs b/src/maze/resources.rs index 0f24456..7aeee09 100644 --- a/src/maze/resources.rs +++ b/src/maze/resources.rs @@ -1,76 +1,18 @@ use bevy::prelude::*; -use hexx::{Hex, HexLayout, HexOrientation}; -use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng}; -use std::num::TryFromIntError; -use thiserror::Error; #[derive(Debug, Default, Reflect, Resource)] #[reflect(Resource)] pub struct MazePluginLoaded; -#[derive(Debug, Error)] -pub enum MazeConfigError { - #[error("Failed to convert radius from u32 to i32: {0}")] - RadiusConverions(#[from] TryFromIntError), -} - -#[derive(Debug, Reflect, Resource)] +#[derive(Debug, Reflect, Resource, Clone)] #[reflect(Resource)] -pub struct MazeConfig { - pub radius: u32, - pub height: f32, +pub struct GlobalMazeConfig { pub hex_size: f32, - pub start_pos: Hex, - pub end_pos: Hex, - pub seed: u64, - pub layout: HexLayout, + pub wall_thickness: f32, + pub height: f32, } -impl MazeConfig { - fn new( - radius: u32, - height: f32, - hex_size: f32, - orientation: HexOrientation, - seed: Option, - ) -> Result { - let seed = seed.unwrap_or_else(|| thread_rng().gen()); - let mut rng = StdRng::seed_from_u64(seed); - - let start_pos = generate_pos(radius, &mut rng)?; - let end_pos = generate_pos(radius, &mut rng)?; - - info!("Start pos: (q={}, r={})", start_pos.x, start_pos.y); - info!("End pos: (q={}, r={})", end_pos.x, end_pos.y); - - let layout = HexLayout { - orientation, - hex_size: Vec2::splat(hex_size), - ..default() - }; - - Ok(Self { - radius, - height, - hex_size, - start_pos, - end_pos, - seed, - layout, - }) - } - - pub fn new_unchecked( - radius: u32, - height: f32, - hex_size: f32, - orientation: HexOrientation, - seed: Option, - ) -> Self { - Self::new(radius, height, hex_size, orientation, seed) - .expect("Failed to create MazeConfig with supposedly safe values") - } - +impl GlobalMazeConfig { pub fn wall_size(&self) -> f32 { self.hex_size / 6. } @@ -78,22 +20,14 @@ impl MazeConfig { pub fn wall_offset(&self) -> f32 { self.hex_size - self.wall_size() } - - pub fn update(&mut self) { - self.layout.hex_size = Vec2::splat(self.hex_size); - } } -impl Default for MazeConfig { +impl Default for GlobalMazeConfig { fn default() -> Self { - Self::new_unchecked(7, 20., 6., HexOrientation::Flat, None) + Self { + hex_size: 6., + wall_thickness: 1., + height: 20., + } } } - -fn generate_pos(radius: u32, rng: &mut R) -> Result { - let radius = i32::try_from(radius)?; - Ok(Hex::new( - rng.gen_range(-radius..radius), - rng.gen_range(-radius..radius), - )) -} diff --git a/src/maze/systems/common.rs b/src/maze/systems/common.rs new file mode 100644 index 0000000..457f0e0 --- /dev/null +++ b/src/maze/systems/common.rs @@ -0,0 +1,14 @@ +use crate::maze::{ + components::MazeConfig, + errors::{MazeError, MazeResult}, +}; +use hexlab::{GeneratorType, HexMaze, MazeBuilder}; + +pub(super) fn generate_maze(config: &MazeConfig) -> MazeResult { + MazeBuilder::new() + .with_radius(config.radius) + .with_seed(config.seed) + .with_generator(GeneratorType::RecursiveBacktracking) + .build() + .map_err(|_| MazeError::generation_failed(config.radius, config.seed)) +} diff --git a/src/maze/systems/despawn.rs b/src/maze/systems/despawn.rs deleted file mode 100644 index 16385f1..0000000 --- a/src/maze/systems/despawn.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::maze::components::Floor; -use bevy::prelude::*; - -pub(crate) fn despawn_floor( - commands: &mut Commands, - query: &Query<(Entity, &Floor)>, - floor_num: u8, -) { - for (entity, floor) in query.iter() { - if floor.0 == floor_num { - commands.entity(entity).despawn_recursive(); - } - } -} diff --git a/src/maze/systems/event_handler.rs b/src/maze/systems/event_handler.rs new file mode 100644 index 0000000..65f0430 --- /dev/null +++ b/src/maze/systems/event_handler.rs @@ -0,0 +1,54 @@ +use super::{spawn::spawn_floor, update::update_floor}; +use crate::{ + floor::components::Floor, + maze::{components::Maze, events::MazeEvent, GlobalMazeConfig}, +}; +use bevy::prelude::*; + +pub(crate) fn handle_maze_events( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + mut event_reader: EventReader, + mut maze_query: Query<(Entity, &Floor, &mut Maze)>, + global_config: Res, +) { + for event in event_reader.read() { + match event { + MazeEvent::Create { floor, config } => { + if maze_query.iter().any(|(_, f, _)| f.0 == *floor) { + warn!("Floor {} already exists, skipping creation", floor); + continue; + } + spawn_floor( + &mut commands, + &mut meshes, + &mut materials, + *floor, + config, + &global_config, + ); + } + MazeEvent::Recreate { floor, config } => { + let result = update_floor( + &mut commands, + &mut meshes, + &mut materials, + &mut maze_query, + *floor, + config, + &global_config, + ); + if let Err(e) = result { + warn!("Failed to update floor {}: {}", floor, e); + } + } + MazeEvent::Remove { floor } => { + match maze_query.iter().find(|(_, f, _)| f.0 == *floor) { + Some((entity, _, _)) => commands.entity(entity).despawn_recursive(), + _ => warn!("Floor {} not found for removal", floor), + } + } + } + } +} diff --git a/src/maze/systems/mod.rs b/src/maze/systems/mod.rs index 8965842..a67e90a 100644 --- a/src/maze/systems/mod.rs +++ b/src/maze/systems/mod.rs @@ -1,11 +1,12 @@ -pub mod despawn; -pub mod recreation; +pub mod common; +pub mod event_handler; pub mod setup; pub mod spawn; +pub mod update; use bevy::prelude::*; -use recreation::recreate_maze; +use event_handler::handle_maze_events; pub(super) fn plugin(app: &mut App) { - app.add_systems(Update, recreate_maze); + app.add_systems(Update, handle_maze_events); } diff --git a/src/maze/systems/recreation.rs b/src/maze/systems/recreation.rs deleted file mode 100644 index 358e458..0000000 --- a/src/maze/systems/recreation.rs +++ /dev/null @@ -1,19 +0,0 @@ -use bevy::prelude::*; - -use crate::maze::{components::Floor, events::RecreateMazeEvent, MazeConfig}; - -use super::{despawn::despawn_floor, spawn::spawn_floor}; - -pub(crate) fn recreate_maze( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, - config: Res, - query: Query<(Entity, &Floor)>, - mut event_reader: EventReader, -) { - for event in event_reader.read() { - despawn_floor(&mut commands, &query, event.floor); - spawn_floor(&mut commands, &mut meshes, &mut materials, &config); - } -} diff --git a/src/maze/systems/setup.rs b/src/maze/systems/setup.rs index 113cf89..bde8c12 100644 --- a/src/maze/systems/setup.rs +++ b/src/maze/systems/setup.rs @@ -1,12 +1,7 @@ -use super::spawn::spawn_floor; -use crate::maze::MazeConfig; +use crate::maze::{components::MazeConfig, events::MazeEvent}; use bevy::prelude::*; -pub(crate) fn setup( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, - config: Res, -) { - spawn_floor(&mut commands, &mut meshes, &mut materials, &config); +pub(crate) fn setup(mut event_writer: EventWriter) { + let config = MazeConfig::default(); + event_writer.send(MazeEvent::Create { floor: 1, config }); } diff --git a/src/maze/systems/spawn.rs b/src/maze/systems/spawn.rs index 8cb229c..a69c69d 100644 --- a/src/maze/systems/spawn.rs +++ b/src/maze/systems/spawn.rs @@ -1,50 +1,68 @@ -use crate::maze::{ - assets::MazeAssets, - components::{Floor, Maze, Tile, Wall}, - MazeConfig, +use crate::{ + floor::components::{CurrentFloor, Floor}, + maze::{ + assets::MazeAssets, + components::{Maze, MazeConfig, Tile, Wall}, + resources::GlobalMazeConfig, + }, }; use bevy::prelude::*; use hexlab::prelude::*; use hexx::HexOrientation; use std::f32::consts::{FRAC_PI_2, FRAC_PI_3, FRAC_PI_6}; +use super::common::generate_maze; + pub(super) fn spawn_floor( commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, - config: &MazeConfig, + floor: u8, + maze_config: &MazeConfig, + global_config: &GlobalMazeConfig, ) { - let maze = MazeBuilder::new() - .with_radius(config.radius) - .with_seed(config.seed) - .with_generator(GeneratorType::RecursiveBacktracking) - .build() - .expect("Something went wrong while creating maze"); + let maze = generate_maze(maze_config).expect("Failed to generate maze during spawn"); - let assets = MazeAssets::new(meshes, materials, config); - commands + let entity = commands .spawn(( - Name::new("Floor"), + Name::new(format!("Floor {}", floor)), Maze(maze.clone()), - Floor(1), + Floor(floor), + CurrentFloor, // TODO: remove + maze_config.clone(), Transform::from_translation(Vec3::ZERO), Visibility::Visible, )) - .with_children(|parent| { - for tile in maze.values() { - spawn_single_hex_tile(parent, &assets, tile, config) - } - }); + .id(); + + let assets = MazeAssets::new(meshes, materials, global_config); + spawn_maze_tiles(commands, entity, &maze, &assets, maze_config, global_config); +} + +pub(super) fn spawn_maze_tiles( + commands: &mut Commands, + parent_entity: Entity, + maze: &HexMaze, + assets: &MazeAssets, + maze_config: &MazeConfig, + global_config: &GlobalMazeConfig, +) { + commands.entity(parent_entity).with_children(|parent| { + for tile in maze.values() { + spawn_single_hex_tile(parent, assets, tile, maze_config, global_config); + } + }); } pub(super) fn spawn_single_hex_tile( parent: &mut ChildBuilder, assets: &MazeAssets, tile: &HexTile, - config: &MazeConfig, + maze_config: &MazeConfig, + global_config: &GlobalMazeConfig, ) { - let world_pos = tile.to_vec3(&config.layout); - let rotation = match config.layout.orientation { + let world_pos = tile.to_vec3(&maze_config.layout); + let rotation = match maze_config.layout.orientation { HexOrientation::Pointy => Quat::from_rotation_y(0.0), HexOrientation::Flat => Quat::from_rotation_y(FRAC_PI_6), // 30 degrees rotation }; @@ -57,12 +75,17 @@ pub(super) fn spawn_single_hex_tile( MeshMaterial3d(assets.hex_material.clone()), Transform::from_translation(world_pos).with_rotation(rotation), )) - .with_children(|parent| spawn_walls(parent, assets, config, tile.walls())); + .with_children(|parent| spawn_walls(parent, assets, tile.walls(), global_config)); } -fn spawn_walls(parent: &mut ChildBuilder, assets: &MazeAssets, config: &MazeConfig, walls: &Walls) { +fn spawn_walls( + parent: &mut ChildBuilder, + assets: &MazeAssets, + walls: &Walls, + global_config: &GlobalMazeConfig, +) { let z_rotation = Quat::from_rotation_z(-FRAC_PI_2); - let y_offset = config.height / 2.; + let y_offset = global_config.height / 2.; for i in 0..6 { if !walls.contains(i) { @@ -71,8 +94,8 @@ fn spawn_walls(parent: &mut ChildBuilder, assets: &MazeAssets, config: &MazeConf let wall_angle = -FRAC_PI_3 * i as f32; - let x_offset = config.wall_offset() * f32::cos(wall_angle); - let z_offset = config.wall_offset() * f32::sin(wall_angle); + let x_offset = global_config.wall_offset() * f32::cos(wall_angle); + let z_offset = global_config.wall_offset() * f32::sin(wall_angle); let pos = Vec3::new(x_offset, y_offset, z_offset); let x_rotation = Quat::from_rotation_x(wall_angle + FRAC_PI_2); diff --git a/src/maze/systems/update.rs b/src/maze/systems/update.rs new file mode 100644 index 0000000..415887a --- /dev/null +++ b/src/maze/systems/update.rs @@ -0,0 +1,43 @@ +use super::{common::generate_maze, spawn::spawn_maze_tiles}; +use crate::{ + floor::components::Floor, + maze::{ + assets::MazeAssets, + components::{Maze, MazeConfig}, + errors::{MazeError, MazeResult}, + GlobalMazeConfig, + }, +}; +use bevy::prelude::*; + +pub(super) fn update_floor( + commands: &mut Commands, + meshes: &mut ResMut>, + materials: &mut ResMut>, + maze_query: &mut Query<(Entity, &Floor, &mut Maze)>, + floor: u8, + maze_config: &MazeConfig, + global_config: &GlobalMazeConfig, +) -> MazeResult<()> { + let (entity, _, mut maze) = maze_query + .iter_mut() + .find(|(_, f, _)| f.0 == floor) + .ok_or(MazeError::FloorNotFound(floor))?; + + maze.0 = generate_maze(maze_config)?; + + commands.entity(entity).despawn_descendants(); + let assets = MazeAssets::new(meshes, materials, global_config); + spawn_maze_tiles( + commands, + entity, + &maze.0, + &assets, + maze_config, + global_config, + ); + + commands.entity(entity).insert(maze_config.clone()); + + Ok(()) +} diff --git a/src/player/events.rs b/src/player/events.rs index edd056e..7bcda24 100644 --- a/src/player/events.rs +++ b/src/player/events.rs @@ -1,4 +1,8 @@ use bevy::prelude::*; #[derive(Debug, Event)] -pub(crate) struct RespawnPlayer; +pub enum PlayerEvent { + Spawn, + Respawn, + Despawn, +} diff --git a/src/player/mod.rs b/src/player/mod.rs index 5db0cf0..53d6129 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -5,11 +5,11 @@ mod systems; use bevy::{ecs::system::RunSystemOnce, prelude::*}; use components::Player; -use events::RespawnPlayer; +use events::PlayerEvent; pub(super) fn plugin(app: &mut App) { app.register_type::() - .add_event::() + .add_event::() .add_plugins(systems::plugin); } diff --git a/src/player/systems/despawn.rs b/src/player/systems/despawn.rs index 24dc9f4..f1ad992 100644 --- a/src/player/systems/despawn.rs +++ b/src/player/systems/despawn.rs @@ -2,7 +2,7 @@ use bevy::prelude::*; use crate::player::components::Player; -pub(crate) fn despawn_players(commands: &mut Commands, query: &Query>) { +pub(super) fn despawn_players(commands: &mut Commands, query: &Query>) { for entity in query.iter() { commands.entity(entity).despawn_recursive(); } diff --git a/src/player/systems/event_handler.rs b/src/player/systems/event_handler.rs new file mode 100644 index 0000000..cc7de49 --- /dev/null +++ b/src/player/systems/event_handler.rs @@ -0,0 +1,53 @@ +use crate::{ + floor::components::CurrentFloor, + maze::{components::MazeConfig, GlobalMazeConfig}, + player::{components::Player, events::PlayerEvent}, +}; +use bevy::prelude::*; + +use super::{despawn::despawn_players, respawn::respawn_player, spawn::spawn_player}; + +pub(crate) fn handle_player_events( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + mut event_reader: EventReader, + maze_config_query: Query<&MazeConfig, With>, + player_query: Query>, + global_config: Res, +) { + for event in event_reader.read() { + match event { + PlayerEvent::Spawn => { + let Ok(maze_config) = maze_config_query.get_single() else { + warn!( + "Failed to get maze configuration for current floor - cannot spawn player" + ); + return; + }; + spawn_player( + &mut commands, + &mut meshes, + &mut materials, + maze_config, + &global_config, + ); + } + PlayerEvent::Respawn => { + let Ok(maze_config) = maze_config_query.get_single() else { + warn!("Failed to get maze configuration for current floor - cannot respawn player"); + return; + }; + respawn_player( + &mut commands, + &mut meshes, + &mut materials, + &player_query, + maze_config, + &global_config, + ); + } + PlayerEvent::Despawn => despawn_players(&mut commands, &player_query), + } + } +} diff --git a/src/player/systems/input.rs b/src/player/systems/input.rs index 6163700..f536e76 100644 --- a/src/player/systems/input.rs +++ b/src/player/systems/input.rs @@ -1,13 +1,42 @@ use crate::{ - maze::{ - components::{Floor, Maze}, - MazeConfig, - }, + floor::components::CurrentFloor, + maze::components::{Maze, MazeConfig}, player::components::{CurrentPosition, MovementTarget, Player}, }; use bevy::prelude::*; use hexx::{EdgeDirection, HexOrientation}; +pub(super) fn player_input( + input: Res>, + mut player_query: Query<(&mut MovementTarget, &CurrentPosition), With>, + maze_query: Query<(&Maze, &MazeConfig), With>, +) { + let Ok((maze, maze_config)) = maze_query.get_single() else { + return; + }; + + for (mut target_pos, current_pos) in player_query.iter_mut() { + if target_pos.is_some() { + continue; + } + + let Some(direction) = create_direction(&input, &maze_config.layout.orientation) else { + continue; + }; + + let Some(tile) = maze.0.get_tile(current_pos) else { + continue; + }; + + if tile.walls().contains(direction) { + continue; + } + + let next_hex = current_pos.0.neighbor(direction); + target_pos.0 = Some(next_hex); + } +} + fn create_direction( input: &ButtonInput, orientation: &HexOrientation, @@ -43,35 +72,3 @@ fn create_direction( }?; Some(direction.rotate_cw(0)) } - -pub(super) fn player_input( - input: Res>, - mut player_query: Query<(&mut MovementTarget, &CurrentPosition), With>, - maze_query: Query<(&Maze, &Floor)>, - maze_config: Res, -) { - let Ok((maze, _floor)) = maze_query.get_single() else { - return; - }; - - for (mut target_pos, current_pos) in player_query.iter_mut() { - if target_pos.is_some() { - continue; - } - - let Some(direction) = create_direction(&input, &maze_config.layout.orientation) else { - continue; - }; - - let Some(tile) = maze.0.get_tile(¤t_pos) else { - continue; - }; - - if tile.walls().contains(direction) { - continue; - } - - let next_hex = current_pos.0.neighbor(direction); - target_pos.0 = Some(next_hex); - } -} diff --git a/src/player/systems/mod.rs b/src/player/systems/mod.rs index ea066cd..aeb03be 100644 --- a/src/player/systems/mod.rs +++ b/src/player/systems/mod.rs @@ -1,14 +1,15 @@ -pub mod despawn; +mod despawn; +mod event_handler; mod input; mod movement; -pub mod respawn; +mod respawn; pub mod setup; -pub mod spawn; +mod spawn; use bevy::prelude::*; +use event_handler::handle_player_events; use input::player_input; use movement::player_movement; -use respawn::respawn_player; pub(super) fn plugin(app: &mut App) { app.add_systems( @@ -16,7 +17,7 @@ pub(super) fn plugin(app: &mut App) { ( player_input, player_movement.after(player_input), - respawn_player, + handle_player_events, ), ); } diff --git a/src/player/systems/movement.rs b/src/player/systems/movement.rs index 26ec3d2..6815064 100644 --- a/src/player/systems/movement.rs +++ b/src/player/systems/movement.rs @@ -1,5 +1,6 @@ use crate::{ - maze::MazeConfig, + floor::components::CurrentFloor, + maze::components::MazeConfig, player::components::{CurrentPosition, MovementSpeed, MovementTarget, Player}, }; use bevy::prelude::*; @@ -16,12 +17,17 @@ pub(super) fn player_movement( ), With, >, - maze_config: Res, + maze_config_query: Query<&MazeConfig, With>, ) { + let Ok(maze_config) = maze_config_query.get_single() else { + warn!("Failed to get maze configuration for current floor - cannot move player"); + return; + }; + for (mut target, speed, mut current_hex, mut transform) in query.iter_mut() { if let Some(target_hex) = target.0 { let current_pos = transform.translation; - let target_pos = calculate_target_position(&maze_config, target_hex, current_pos.y); + let target_pos = calculate_target_position(maze_config, target_hex, current_pos.y); if should_complete_movement(current_pos, target_pos) { transform.translation = target_pos; diff --git a/src/player/systems/respawn.rs b/src/player/systems/respawn.rs index af2446a..002eca5 100644 --- a/src/player/systems/respawn.rs +++ b/src/player/systems/respawn.rs @@ -1,21 +1,18 @@ +use super::{despawn::despawn_players, spawn::spawn_player}; use crate::{ - maze::MazeConfig, - player::{components::Player, events::RespawnPlayer}, + maze::{components::MazeConfig, GlobalMazeConfig}, + player::components::Player, }; use bevy::prelude::*; -use super::{despawn::despawn_players, spawn::spawn_player}; - -pub(crate) fn respawn_player( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, - config: Res, - query: Query>, - mut event_reader: EventReader, +pub(super) fn respawn_player( + commands: &mut Commands, + meshes: &mut ResMut>, + materials: &mut ResMut>, + query: &Query>, + maze_config: &MazeConfig, + global_config: &GlobalMazeConfig, ) { - for _ in event_reader.read() { - despawn_players(&mut commands, &query); - spawn_player(&mut commands, &mut meshes, &mut materials, &config); - } + despawn_players(commands, query); + spawn_player(commands, meshes, materials, maze_config, global_config); } diff --git a/src/player/systems/setup.rs b/src/player/systems/setup.rs index 4092d52..94287fa 100644 --- a/src/player/systems/setup.rs +++ b/src/player/systems/setup.rs @@ -1,14 +1,6 @@ +use crate::player::events::PlayerEvent; use bevy::prelude::*; -use crate::maze::MazeConfig; - -use super::spawn::spawn_player; - -pub(crate) fn setup( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, - config: Res, -) { - spawn_player(&mut commands, &mut meshes, &mut materials, &config); +pub(crate) fn setup(mut event_writer: EventWriter) { + event_writer.send(PlayerEvent::Spawn); } diff --git a/src/player/systems/spawn.rs b/src/player/systems/spawn.rs index 19a5a85..e28b667 100644 --- a/src/player/systems/spawn.rs +++ b/src/player/systems/spawn.rs @@ -1,5 +1,5 @@ use crate::{ - maze::MazeConfig, + maze::{components::MazeConfig, GlobalMazeConfig}, player::{ assets::{blue_material, generate_pill_mesh}, components::{CurrentPosition, Player}, @@ -11,19 +11,20 @@ pub(super) fn spawn_player( commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, - config: &MazeConfig, + maze_config: &MazeConfig, + global_config: &GlobalMazeConfig, ) { - let player_radius = config.hex_size * 0.5; + let player_radius = global_config.hex_size * 0.5; let player_height = player_radius * 3.5; - let y_offset = config.height / 2. + player_height / 1.3; + let y_offset = global_config.height / 2. + player_height / 1.3; - let start_pos = config.layout.hex_to_world_pos(config.start_pos); + let start_pos = maze_config.layout.hex_to_world_pos(maze_config.start_pos); commands.spawn(( Name::new("Player"), Player, - CurrentPosition(config.start_pos), + CurrentPosition(maze_config.start_pos), Mesh3d(meshes.add(generate_pill_mesh(player_radius, player_height / 2.))), MeshMaterial3d(materials.add(blue_material())), Transform::from_xyz(start_pos.x, y_offset, start_pos.y),