diff --git a/Cargo.lock b/Cargo.lock index a820783..42968c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1409,7 +1409,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn", ] @@ -1813,7 +1813,7 @@ dependencies = [ "log", "rangemap", "rayon", - "rustc-hash", + "rustc-hash 1.1.0", "rustybuzz", "self_cell", "swash", @@ -1925,6 +1925,18 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "deprecate-until" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3767f826efbbe5a5ae093920b58b43b01734202be697e1354914e862e8e704" +dependencies = [ + "proc-macro2", + "quote", + "semver", + "syn", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -2644,15 +2656,16 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hexlab" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d2fbc6c41965686841aa5ea0e1af448730d0902274e49251c7d1fb7c78fffb9" +checksum = "e247b21d6885136e4a910e1e6fac755d8dc56112c40ebf1062582f0644d62c62" dependencies = [ "bevy", "bevy_reflect", "bevy_utils", "glam", "hexx", + "pathfinding", "rand", "thiserror 2.0.6", ] @@ -2894,6 +2907,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "io-kit-sys" version = "0.4.1" @@ -3228,7 +3250,7 @@ dependencies = [ "indexmap", "log", "pp-rs", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "termcolor", "thiserror 1.0.69", @@ -3249,7 +3271,7 @@ dependencies = [ "once_cell", "regex", "regex-syntax 0.8.5", - "rustc-hash", + "rustc-hash 1.1.0", "thiserror 1.0.69", "tracing", "unicode-ident", @@ -3763,6 +3785,20 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathfinding" +version = "4.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301ad6aa19104eeb9af172b3d6a4ab8a5ea26234890baf2fcb1cbbc3f05f674b" +dependencies = [ + "deprecate-until", + "indexmap", + "integer-sqrt", + "num-traits", + "rustc-hash 2.1.0", + "thiserror 2.0.6", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -4184,6 +4220,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "rustc_version" version = "0.4.1" @@ -5224,7 +5266,7 @@ dependencies = [ "parking_lot", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror 1.0.69", "wgpu-hal", @@ -5266,7 +5308,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror 1.0.69", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index fecb335..234db65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ tracing = { version = "0.1", features = [ "release_max_level_warn", ] } hexx = { version = "0.19", features = ["bevy_reflect", "grid"] } -hexlab = { version = "0.5", features = ["bevy"] } +hexlab = { version = "0.6", features = ["bevy", "pathfinding"] } bevy-inspector-egui = { version = "0.28", optional = true } bevy_egui = { version = "0.31", optional = true } thiserror = "2.0" diff --git a/justfile b/justfile index eea0280..49c0731 100644 --- a/justfile +++ b/justfile @@ -8,7 +8,7 @@ native-dev: # Run native release native-release: - RUSTC_WRAPPER=sccache cargo run --release --no-default-features + RUSTC_WRAPPER=sccache cargo run --release --no-default-features # Run web dev web-dev: diff --git a/src/constants.rs b/src/constants.rs index c79e214..e960a29 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -3,3 +3,12 @@ pub const WALL_OVERLAP_MODIFIER: f32 = 1.25; pub const FLOOR_Y_OFFSET: u8 = 200; pub const MOVEMENT_COOLDOWN: f32 = 1.0; // one second cooldown pub const TITLE: &str = "Maze Ascension: The Labyrinth of Echoes"; + +// Base score constants +pub const BASE_FLOOR_SCORE: usize = 1000; +pub const BASE_TIME_SCORE: usize = 100; + +// Floor progression constants +pub const FLOOR_DIFFICULTY_MULTIPLIER: f32 = 1.2; // Higher floors are exponentially harder +pub const MIN_TIME_MULTIPLIER: f32 = 0.1; // Minimum score multiplier for time +pub const TIME_REFERENCE_SECONDS: f32 = 60.0; // Reference time for score calculation diff --git a/src/hint/mod.rs b/src/hint/mod.rs index 371febd..08ea313 100644 --- a/src/hint/mod.rs +++ b/src/hint/mod.rs @@ -3,9 +3,11 @@ pub mod components; mod systems; use bevy::{ecs::system::RunSystemOnce, prelude::*}; +use components::IdleTimer; pub(super) fn plugin(app: &mut App) { - app.add_plugins(systems::plugin); + app.register_type::() + .add_plugins(systems::plugin); } pub fn spawn_hint_command(world: &mut World) { diff --git a/src/lib.rs b/src/lib.rs index 10921fe..e634cde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ pub mod hint; pub mod maze; pub mod player; pub mod screens; +pub mod stats; pub mod theme; use bevy::{ @@ -69,6 +70,7 @@ impl Plugin for AppPlugin { floor::plugin, player::plugin, hint::plugin, + stats::plugin, )); // Enable dev tools for dev builds. diff --git a/src/maze/components.rs b/src/maze/components.rs index 48cd62b..349bd62 100644 --- a/src/maze/components.rs +++ b/src/maze/components.rs @@ -95,14 +95,6 @@ impl MazeConfig { } } -// TO -// 3928551514041614914 -// (4, 0) - -// FROM -// 7365371276044996661 -// () - impl Default for MazeConfig { fn default() -> Self { Self::new( diff --git a/src/screens/gameplay.rs b/src/screens/gameplay.rs index 7374933..b41de50 100644 --- a/src/screens/gameplay.rs +++ b/src/screens/gameplay.rs @@ -1,8 +1,9 @@ //! The screen state for the main gameplay. -use crate::player::spawn_player_command; -use crate::screens::Screen; -use crate::{hint::spawn_hint_command, maze::spawn_level_command}; +use crate::{ + hint::spawn_hint_command, maze::spawn_level_command, player::spawn_player_command, + screens::Screen, stats::spawn_stats_command, +}; use bevy::{input::common_conditions::input_just_pressed, prelude::*}; @@ -13,6 +14,7 @@ pub(super) fn plugin(app: &mut App) { spawn_level_command, spawn_player_command, spawn_hint_command, + spawn_stats_command, ) .chain(), ); diff --git a/src/stats/components.rs b/src/stats/components.rs new file mode 100644 index 0000000..f37e09b --- /dev/null +++ b/src/stats/components.rs @@ -0,0 +1,21 @@ +use bevy::prelude::*; + +#[derive(Debug, Clone, Reflect, Component)] +#[reflect(Component)] +pub struct FloorDisplay; + +#[derive(Debug, Clone, Reflect, Component)] +#[reflect(Component)] +pub struct HighestFloorDisplay; + +#[derive(Debug, Clone, Reflect, Component)] +#[reflect(Component)] +pub struct ScoreDisplay; + +#[derive(Debug, Clone, Reflect, Component)] +#[reflect(Component)] +pub struct FloorTimerDisplay; + +#[derive(Debug, Clone, Reflect, Component)] +#[reflect(Component)] +pub struct TotalTimerDisplay; diff --git a/src/stats/container.rs b/src/stats/container.rs new file mode 100644 index 0000000..95289b1 --- /dev/null +++ b/src/stats/container.rs @@ -0,0 +1,22 @@ +use bevy::prelude::*; + +pub trait StatsContainer { + fn ui_stats(&mut self) -> EntityCommands; +} + +impl StatsContainer for Commands<'_, '_> { + fn ui_stats(&mut self) -> EntityCommands { + self.spawn(( + Name::new("Stats Root"), + Node { + position_type: PositionType::Absolute, + top: Val::Px(10.), + right: Val::Px(10.), + row_gap: Val::Px(8.), + align_items: AlignItems::End, + flex_direction: FlexDirection::Column, + ..default() + }, + )) + } +} diff --git a/src/stats/mod.rs b/src/stats/mod.rs new file mode 100644 index 0000000..7693d14 --- /dev/null +++ b/src/stats/mod.rs @@ -0,0 +1,18 @@ +pub mod components; +pub mod container; +pub mod resources; +mod systems; + +use bevy::{ecs::system::RunSystemOnce, prelude::*}; +use resources::{FloorTimer, Score, TotalTimer}; + +pub(super) fn plugin(app: &mut App) { + app.init_resource::() + .init_resource::() + .init_resource::() + .add_plugins(systems::plugin); +} + +pub fn spawn_stats_command(world: &mut World) { + let _ = world.run_system_once(systems::spawn::spawn_stats); +} diff --git a/src/stats/resources.rs b/src/stats/resources.rs new file mode 100644 index 0000000..2da7f8b --- /dev/null +++ b/src/stats/resources.rs @@ -0,0 +1,31 @@ +use std::time::Duration; + +use bevy::prelude::*; + +#[derive(Debug, Default, Reflect, Resource, Deref, DerefMut)] +#[reflect(Resource)] +pub struct Score(pub usize); + +#[derive(Debug, Reflect, Resource, Deref, DerefMut)] +#[reflect(Resource)] +pub struct TotalTimer(pub Timer); + +#[derive(Debug, Reflect, Resource, Deref, DerefMut)] +#[reflect(Resource)] +pub struct FloorTimer(pub Timer); + +impl Default for TotalTimer { + fn default() -> Self { + Self(init_timer()) + } +} + +impl Default for FloorTimer { + fn default() -> Self { + Self(init_timer()) + } +} + +fn init_timer() -> Timer { + Timer::new(Duration::MAX, TimerMode::Once) +} diff --git a/src/stats/systems/common.rs b/src/stats/systems/common.rs new file mode 100644 index 0000000..e769e8b --- /dev/null +++ b/src/stats/systems/common.rs @@ -0,0 +1,28 @@ +pub fn format_duration_adaptive(seconds: f32) -> String { + let total_millis = (seconds * 1000.0) as u64; + let millis = total_millis % 1000; + let total_seconds = total_millis / 1000; + let seconds = total_seconds % 60; + let total_minutes = total_seconds / 60; + let minutes = total_minutes % 60; + let total_hours = total_minutes / 60; + let hours = total_hours % 24; + let days = total_hours / 24; + + let mut result = String::new(); + + if days > 0 { + result.push_str(&format!("{}d ", days)); + } + if hours > 0 || days > 0 { + result.push_str(&format!("{:02}:", hours)); + } + if minutes > 0 || hours > 0 || days > 0 { + result.push_str(&format!("{:02}:", minutes)); + } + + // Always show at least seconds and milliseconds + result.push_str(&format!("{:02}.{:03}", seconds, millis)); + + result +} diff --git a/src/stats/systems/floor.rs b/src/stats/systems/floor.rs new file mode 100644 index 0000000..463abef --- /dev/null +++ b/src/stats/systems/floor.rs @@ -0,0 +1,39 @@ +use bevy::prelude::*; + +use crate::{ + floor::{ + components::{CurrentFloor, Floor}, + resources::HighestFloor, + }, + stats::components::{FloorDisplay, HighestFloorDisplay}, +}; + +pub fn update_floor_display( + floor_query: Query<&Floor, With>, + mut text_query: Query<&mut Text, With>, +) { + let Ok(floor) = floor_query.get_single() else { + return; + }; + + let Ok(mut text) = text_query.get_single_mut() else { + return; + }; + + text.0 = format!("Floor: {}", floor.0); +} + +pub fn update_highest_floor_display( + hightes_floor: Res, + mut text_query: Query<&mut Text, With>, +) { + if !hightes_floor.is_changed() { + return; + } + + let Ok(mut text) = text_query.get_single_mut() else { + return; + }; + + text.0 = format!("Highest Floor: {}", hightes_floor.0); +} diff --git a/src/stats/systems/floor_timer.rs b/src/stats/systems/floor_timer.rs new file mode 100644 index 0000000..c5aec96 --- /dev/null +++ b/src/stats/systems/floor_timer.rs @@ -0,0 +1,33 @@ +use bevy::prelude::*; + +use crate::{ + floor::resources::HighestFloor, + stats::{components::FloorTimerDisplay, resources::FloorTimer}, +}; + +use super::common::format_duration_adaptive; + +pub fn update_floor_timer( + mut floor_timer: ResMut, + time: Res