mirror of
https://github.com/kristoferssolo/maze-ascension.git
synced 2025-10-21 19:20:34 +00:00
Merge pull request #19 from kristoferssolo/feat/floor-transitions
This commit is contained in:
commit
9198560978
51
Cargo.lock
generated
51
Cargo.lock
generated
@ -2299,21 +2299,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@ -2321,7 +2306,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2330,17 +2314,6 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
@ -2371,12 +2344,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
@ -2395,13 +2362,9 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
@ -2675,9 +2638,9 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "hexlab"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bd7c21f4e2c11d40473d1ae673905f4deae3b12104fa6d70eeef9ef385aceb6"
|
||||
checksum = "7d2fbc6c41965686841aa5ea0e1af448730d0902274e49251c7d1fb7c78fffb9"
|
||||
dependencies = [
|
||||
"bevy",
|
||||
"bevy_reflect",
|
||||
@ -4168,21 +4131,21 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.23.0"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035"
|
||||
checksum = "03e905296805ab93e13c1ec3a03f4b6c4f35e9498a3d5fa96dc626d22c03cd89"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"rstest_macros",
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.23.0"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a"
|
||||
checksum = "ef0053bbffce09062bee4bcc499b0fbe7a57b879f1efe088d6d8d4c7adcdef9b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
|
||||
@ -26,7 +26,7 @@ anyhow = "1"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.23"
|
||||
rstest = "0.24"
|
||||
rstest_reuse = "0.7"
|
||||
test-log = { version = "0.2.16", default-features = false, features = [
|
||||
"trace",
|
||||
|
||||
6
justfile
6
justfile
@ -4,7 +4,7 @@ default:
|
||||
|
||||
# Run native dev
|
||||
native-dev:
|
||||
RUST_BACKTRACE=full cargo run
|
||||
RUSTC_WRAPPER=sccache RUST_BACKTRACE=full cargo run
|
||||
|
||||
# Run native release
|
||||
native-release:
|
||||
@ -17,3 +17,7 @@ web-dev:
|
||||
# Run web release
|
||||
web-release:
|
||||
trunk serve --release --no-default-features
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
RUSTC_WRAPPER=sccache RUST_BACKTRACE=full cargo nextest run --no-default-features --all-targets
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
pub const MOVEMENT_THRESHOLD: f32 = 0.01;
|
||||
pub const WALL_OVERLAP_MODIFIER: f32 = 1.25;
|
||||
pub const FLOOR_Y_OFFSET: u8 = 100;
|
||||
pub const MOVEMENT_COOLDOWN: f32 = 1.0; // one second cooldown
|
||||
|
||||
@ -40,6 +40,16 @@ pub fn maze_controls_ui(world: &mut World) {
|
||||
if let Some(mut global_config) = world.get_resource_mut::<GlobalMazeConfig>() {
|
||||
ui.heading("Maze Configuration");
|
||||
|
||||
// Display current floor as non-editable text
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Current floor:");
|
||||
let mut floor_text = floor_value.to_string();
|
||||
ui.add_enabled(
|
||||
false,
|
||||
TextEdit::singleline(&mut floor_text).desired_width(10.),
|
||||
);
|
||||
});
|
||||
|
||||
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 |=
|
||||
|
||||
@ -8,10 +8,6 @@ pub struct Floor(pub u8);
|
||||
#[reflect(Component)]
|
||||
pub struct CurrentFloor;
|
||||
|
||||
#[derive(Debug, Reflect, Component)]
|
||||
#[reflect(Component)]
|
||||
pub struct NextFloor;
|
||||
|
||||
#[derive(Debug, Reflect, Component, Deref, DerefMut)]
|
||||
#[reflect(Component)]
|
||||
pub struct FloorYTarget(pub f32);
|
||||
|
||||
@ -38,8 +38,8 @@ impl From<TransitionFloor> for f32 {
|
||||
impl From<&TransitionFloor> for f32 {
|
||||
fn from(value: &TransitionFloor) -> Self {
|
||||
match value {
|
||||
TransitionFloor::Ascend => -1.,
|
||||
TransitionFloor::Descend => 1.,
|
||||
TransitionFloor::Ascend => -1., // When ascending, floors move down
|
||||
TransitionFloor::Descend => 1., // When descending, floors move up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
mod clear_events;
|
||||
mod despawn;
|
||||
mod movement;
|
||||
mod spawn;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
constants::{FLOOR_Y_OFFSET, MOVEMENT_THRESHOLD},
|
||||
floor::{
|
||||
components::{CurrentFloor, FloorYTarget, NextFloor},
|
||||
components::{CurrentFloor, Floor, FloorYTarget},
|
||||
events::TransitionFloor,
|
||||
},
|
||||
maze::components::HexMaze,
|
||||
@ -35,45 +35,52 @@ pub fn move_floors(
|
||||
|
||||
pub fn handle_floor_transition_events(
|
||||
mut commands: Commands,
|
||||
mut maze_query: Query<(Entity, &Transform, Option<&FloorYTarget>), With<HexMaze>>,
|
||||
current_query: Query<Entity, With<CurrentFloor>>,
|
||||
next_query: Query<Entity, With<NextFloor>>,
|
||||
mut maze_query: Query<(Entity, &Transform, &Floor, Option<&FloorYTarget>), With<HexMaze>>,
|
||||
current_query: Query<(Entity, &Floor), With<CurrentFloor>>,
|
||||
mut event_reader: EventReader<TransitionFloor>,
|
||||
) {
|
||||
let is_moving = maze_query
|
||||
.iter()
|
||||
.any(|(_, _, movement_state)| movement_state.is_some());
|
||||
.any(|(_, _, _, movement_state)| movement_state.is_some());
|
||||
|
||||
if is_moving {
|
||||
return;
|
||||
}
|
||||
|
||||
for event in event_reader.read() {
|
||||
dbg!(&event);
|
||||
let Some((current_entity, current_floor)) = current_query.get_single().ok() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let target_floor_num = event.next_floor_num(current_floor);
|
||||
|
||||
let target_entity = maze_query
|
||||
.iter()
|
||||
.find(|(_, _, floor, _)| floor.0 == target_floor_num)
|
||||
.map(|(entity, ..)| entity);
|
||||
|
||||
let Some(target_entity) = target_entity else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let direction = event.into();
|
||||
|
||||
let Some(current_entity) = current_query.get_single().ok() else {
|
||||
continue;
|
||||
};
|
||||
let Some(next_entity) = next_query.get_single().ok() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for (entity, transforms, movement_state) in maze_query.iter_mut() {
|
||||
for (entity, transforms, _, movement_state) in maze_query.iter_mut() {
|
||||
let target_y = (FLOOR_Y_OFFSET as f32).mul_add(direction, transforms.translation.y);
|
||||
dbg!(movement_state, target_y);
|
||||
if movement_state.is_none() {
|
||||
commands.entity(entity).insert(FloorYTarget(target_y));
|
||||
}
|
||||
}
|
||||
|
||||
update_current_next_floor(&mut commands, current_entity, next_entity);
|
||||
update_current_next_floor(&mut commands, current_entity, target_entity);
|
||||
break;
|
||||
}
|
||||
event_reader.clear();
|
||||
}
|
||||
|
||||
fn update_current_next_floor(commands: &mut Commands, current_entity: Entity, next_entity: Entity) {
|
||||
commands.entity(current_entity).remove::<CurrentFloor>();
|
||||
commands
|
||||
.entity(next_entity)
|
||||
.remove::<NextFloor>()
|
||||
.insert(CurrentFloor);
|
||||
fn update_current_next_floor(commands: &mut Commands, current: Entity, target: Entity) {
|
||||
commands.entity(current).remove::<CurrentFloor>();
|
||||
commands.entity(target).insert(CurrentFloor);
|
||||
}
|
||||
|
||||
@ -14,22 +14,24 @@ pub(super) fn spawn_floor(
|
||||
mut event_reader: EventReader<TransitionFloor>,
|
||||
mut highest_floor: ResMut<HighestFloor>,
|
||||
) {
|
||||
let Ok(floor) = query.get_single() else {
|
||||
let Ok(current_floor) = query.get_single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
for event in event_reader.read() {
|
||||
let floor = event.next_floor_num(floor);
|
||||
|
||||
if floor == 1 && *event == TransitionFloor::Descend {
|
||||
if current_floor.0 == 0 && *event == TransitionFloor::Descend {
|
||||
warn!("Cannot descend below floor 1");
|
||||
return;
|
||||
}
|
||||
|
||||
highest_floor.0 = highest_floor.0.max(floor);
|
||||
let next_floor = event.next_floor_num(current_floor);
|
||||
highest_floor.0 = highest_floor.0.max(next_floor);
|
||||
|
||||
info!("Creating level for floor {}", floor);
|
||||
info!("Creating level for floor {}", next_floor);
|
||||
|
||||
commands.trigger(SpawnMaze { floor, ..default() });
|
||||
commands.trigger(SpawnMaze {
|
||||
floor: next_floor,
|
||||
..default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,8 +39,18 @@ impl MazeConfig {
|
||||
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);
|
||||
// Generate start and end positions ensuring they're different
|
||||
let mut start_pos;
|
||||
let mut end_pos;
|
||||
|
||||
loop {
|
||||
start_pos = generate_pos(radius, &mut rng);
|
||||
end_pos = generate_pos(radius, &mut rng);
|
||||
|
||||
if start_pos != end_pos {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"Start pos: (q={}, r={}). End pos: (q={}, r={})",
|
||||
@ -75,8 +85,192 @@ impl Default for MazeConfig {
|
||||
|
||||
fn generate_pos<R: Rng>(radius: u16, rng: &mut R) -> Hex {
|
||||
let radius = radius as i32;
|
||||
Hex::new(
|
||||
rng.gen_range(-radius..radius),
|
||||
rng.gen_range(-radius..radius),
|
||||
)
|
||||
loop {
|
||||
let q = rng.gen_range(-radius..=radius);
|
||||
let r = rng.gen_range(-radius..=radius);
|
||||
|
||||
let s = -q - r; // Calculate third coordinate (axial coordinates: q + r + s = 0)
|
||||
|
||||
// Check if the position is within the hexagonal radius
|
||||
// Using the formula: max(abs(q), abs(r), abs(s)) <= radius
|
||||
if q.abs().max(r.abs()).max(s.abs()) <= radius {
|
||||
return Hex::new(q, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rstest::*;
|
||||
|
||||
fn is_within_radius(hex: Hex, radius: u16) -> bool {
|
||||
let q = hex.x;
|
||||
let r = hex.y;
|
||||
let s = -q - r;
|
||||
q.abs().max(r.abs()).max(s.abs()) <= radius as i32
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn test_radius() -> Vec<u16> {
|
||||
vec![1, 2, 5, 8]
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(1)]
|
||||
#[case(2)]
|
||||
#[case(5)]
|
||||
#[case(8)]
|
||||
fn maze_config_new(#[case] radius: u16) {
|
||||
let orientation = HexOrientation::Flat;
|
||||
let seed = Some(12345);
|
||||
let global_config = GlobalMazeConfig::default();
|
||||
|
||||
let config = MazeConfig::new(radius, orientation, seed, &global_config);
|
||||
|
||||
assert_eq!(config.radius, radius);
|
||||
assert_eq!(config.seed, 12345);
|
||||
assert_eq!(config.layout.orientation, orientation);
|
||||
|
||||
assert!(
|
||||
is_within_radius(config.start_pos, radius),
|
||||
"Start pos {:?} outside radius {}",
|
||||
config.start_pos,
|
||||
radius
|
||||
);
|
||||
assert!(
|
||||
is_within_radius(config.end_pos, radius),
|
||||
"End pos {:?} outside radius {}",
|
||||
config.end_pos,
|
||||
radius
|
||||
);
|
||||
assert_ne!(config.start_pos, config.end_pos);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(100)]
|
||||
fn maze_config_default(#[case] iterations: u32) {
|
||||
for _ in 0..iterations {
|
||||
let config = MazeConfig::default();
|
||||
|
||||
assert_eq!(config.radius, 8);
|
||||
assert_eq!(config.layout.orientation, HexOrientation::Flat);
|
||||
assert!(is_within_radius(config.start_pos, 8));
|
||||
assert!(is_within_radius(config.end_pos, 8));
|
||||
assert_ne!(config.start_pos, config.end_pos);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn maze_config_default_with_seeds() {
|
||||
let test_seeds = [
|
||||
None,
|
||||
Some(0),
|
||||
Some(1),
|
||||
Some(12345),
|
||||
Some(u64::MAX),
|
||||
Some(thread_rng().gen()),
|
||||
];
|
||||
|
||||
for seed in test_seeds {
|
||||
let config =
|
||||
MazeConfig::new(8, HexOrientation::Flat, seed, &GlobalMazeConfig::default());
|
||||
|
||||
assert_eq!(config.radius, 8);
|
||||
assert_eq!(config.layout.orientation, HexOrientation::Flat);
|
||||
assert!(is_within_radius(config.start_pos, 8));
|
||||
assert!(is_within_radius(config.end_pos, 8));
|
||||
assert_ne!(config.start_pos, config.end_pos);
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(1.0)]
|
||||
#[case(2.0)]
|
||||
#[case(5.0)]
|
||||
fn maze_config_update(#[case] new_size: f32) {
|
||||
let mut config = MazeConfig::default();
|
||||
let global_config = GlobalMazeConfig {
|
||||
hex_size: new_size,
|
||||
..default()
|
||||
};
|
||||
|
||||
config.update(&global_config);
|
||||
|
||||
assert_eq!(config.layout.hex_size.x, new_size);
|
||||
assert_eq!(config.layout.hex_size.y, new_size);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(5, 1)]
|
||||
#[case(5, 12345)]
|
||||
#[case(8, 67890)]
|
||||
fn generate_pos_with_seed(#[case] radius: u16, #[case] seed: u64) {
|
||||
let mut rng = StdRng::seed_from_u64(seed);
|
||||
|
||||
for _ in 0..10 {
|
||||
let pos = generate_pos(radius, &mut rng);
|
||||
assert!(
|
||||
is_within_radius(pos, radius),
|
||||
"Position {:?} outside radius {}",
|
||||
pos,
|
||||
radius
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_seeds_different_positions() {
|
||||
let config1 = MazeConfig::new(
|
||||
8,
|
||||
HexOrientation::Flat,
|
||||
Some(1),
|
||||
&GlobalMazeConfig::default(),
|
||||
);
|
||||
let config2 = MazeConfig::new(
|
||||
8,
|
||||
HexOrientation::Flat,
|
||||
Some(2),
|
||||
&GlobalMazeConfig::default(),
|
||||
);
|
||||
|
||||
assert_ne!(config1.start_pos, config2.start_pos);
|
||||
assert_ne!(config1.end_pos, config2.end_pos);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_seed_same_positions() {
|
||||
let seed = Some(12345);
|
||||
let config1 = MazeConfig::new(8, HexOrientation::Flat, seed, &GlobalMazeConfig::default());
|
||||
let config2 = MazeConfig::new(8, HexOrientation::Flat, seed, &GlobalMazeConfig::default());
|
||||
|
||||
assert_eq!(config1.start_pos, config2.start_pos);
|
||||
assert_eq!(config1.end_pos, config2.end_pos);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orientation_pointy() {
|
||||
let config = MazeConfig::new(
|
||||
8,
|
||||
HexOrientation::Pointy,
|
||||
None,
|
||||
&GlobalMazeConfig::default(),
|
||||
);
|
||||
assert_eq!(config.layout.orientation, HexOrientation::Pointy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_size_zero() {
|
||||
let config = MazeConfig::new(
|
||||
8,
|
||||
HexOrientation::Flat,
|
||||
None,
|
||||
&GlobalMazeConfig {
|
||||
hex_size: 0.0,
|
||||
..default()
|
||||
},
|
||||
);
|
||||
assert_eq!(config.layout.hex_size.x, 0.0);
|
||||
assert_eq!(config.layout.hex_size.y, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use super::common::generate_maze;
|
||||
use crate::{
|
||||
constants::FLOOR_Y_OFFSET,
|
||||
floor::components::{CurrentFloor, Floor, NextFloor},
|
||||
floor::components::{CurrentFloor, Floor},
|
||||
maze::{
|
||||
assets::MazeAssets,
|
||||
components::{HexMaze, MazeConfig, Tile, Wall},
|
||||
@ -56,7 +56,6 @@ pub(super) fn spawn_maze(
|
||||
Visibility::Visible,
|
||||
))
|
||||
.insert_if(CurrentFloor, || *floor == 1)
|
||||
.insert_if(NextFloor, || *floor != 1)
|
||||
.id();
|
||||
|
||||
let assets = MazeAssets::new(&mut meshes, &mut materials, &global_config);
|
||||
|
||||
@ -22,16 +22,14 @@ pub fn handle_floor_transition(
|
||||
for current_hex in player_query.iter() {
|
||||
// Check for ascending
|
||||
if current_hex.0 == config.end_pos {
|
||||
dbg!("Ascending");
|
||||
info!("Ascending");
|
||||
event_writer.send(TransitionFloor::Ascend);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for descending
|
||||
if current_hex.0 == config.start_pos && floor.0 != 1 {
|
||||
dbg!("Descending");
|
||||
info!("Descending");
|
||||
event_writer.send(TransitionFloor::Descend);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user