use std::collections::HashMap; use bevy::prelude::*; use bevy_rapier3d::prelude::*; use rand::{Rng, seq::IteratorRandom}; use crate::{ GameState, asset_loading::{GltfAssets, ImageAssets}, interaction::{ Interact, objects::{KeyCardId, OpenedByKey, PickUpAble}, }, player::{Player, PlayerAction, toolbar::ItemIcon}, }; pub fn map_plugin(app: &mut App) { app.add_systems( OnEnter(GameState::Playing), (spawn_level, spawn_objects, spawn_doors).chain(), ); app.add_systems(Update, handle_door_pos); } fn spawn_level( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, models: Res>, gltf_assets: Res, ) { println!("LIBRARY: {:?}", gltf_assets.library); let mesh_names = [ "corner_inside", "corner_outside", "wall", "door", "round_door", "round_hole", ]; let shapes = mesh_names.map(|mesh_name| { let collider: Vec<(Collider, Transform)> = match mesh_name { "corner_inside" => vec![( Collider::cuboid(0.2, 1.0, 0.2), Transform::from_xyz(1.0, 1.0, 1.0) .with_rotation(Quat::from_rotation_y(45.0_f32.to_radians())), )], "corner_outside" => vec![ ( Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 1.0, -1.0), ), ( Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(-1.0, 1.0, 0.0) .with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), ), ], "wall" => vec![( Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 1.0, -1.0), )], "door" => vec![( Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 1.0, -1.0), )], "round_door" => vec![( Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 1.0, -1.0), )], "round_hole" => vec![ ( Collider::cuboid(0.2, 1.0, 0.2), Transform::from_xyz(1.0, 1.0, -1.0) .with_rotation(Quat::from_rotation_y(45.0_f32.to_radians())), ), ( Collider::cuboid(0.2, 1.0, 0.2), Transform::from_xyz(-1.0, 1.0, -1.0) .with_rotation(Quat::from_rotation_y(45.0_f32.to_radians())), ), ], _ => vec![( Collider::cuboid(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0), )], }; let path = format!("meshes/library/space_{}.glb", mesh_name); let handle = gltf_assets .library .get(&path) .expect(&format!("Couldn't find {} in library", mesh_name)); let gltf = models .get(handle) .expect(&format!("No model for {}", mesh_name)); let asset = gltf .default_scene .as_ref() .expect(&format!("No scene in {}", mesh_name)); (SceneRoot(asset.clone()), collider) }); let [ corner_inside, corner_outside, wall, door, round_door, round_hole, ] = shapes.clone(); // huge floor commands.spawn(( RigidBody::Fixed, Collider::cuboid(1000.0, 0.1, 1000.0), Transform::from_xyz(-500.0, 0.0, -500.0), )); // huge roof commands.spawn(( RigidBody::Fixed, Collider::cuboid(1000.0, 0.1, 1000.0), Transform::from_xyz(-500.0, 3.0, -500.0), )); let levels = create_levels(3); for level in &levels { for ((x, z), node) in level.nodes.iter() { let values = { // corners are handled later, for now just a wall to test let node = node.clone(); let pos = Transform::from_xyz(2.0 * (*x) as f32, 0.0, -2.0 * (*z) as f32); use Side::{Closed, Connection}; match (node.north, node.east, node.south, node.west) { // hallway horizontal (Closed, Connection, Closed, Connection) => { vec![ (wall.clone(), pos), ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), ), ] } // hallway vertical (Connection, Closed, Connection, Closed) => { vec![ ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), ), ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), ), ] } // dead ends (Connection, Closed, Closed, Closed) => { vec![ ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), ), ( door.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), ), ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), ), ] } (Closed, Closed, Connection, Closed) => { vec![ ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), ), ( door.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians())), ), ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), ), ] } (Closed, Connection, Closed, Closed) => { vec![ (wall.clone(), pos), ( door.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), ), ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), ), ] } (Closed, Closed, Closed, Connection) => { vec![ (wall.clone(), pos), ( door.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), ), ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), ), ] } // T hallways (Closed, Connection, Connection, Connection) => { vec![ (wall.clone(), pos), ( round_hole.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), ), ] } (Connection, Closed, Connection, Connection) => { vec![ ( round_hole.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), ), ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), ), ] } (Connection, Connection, Closed, Connection) => { vec![ (round_hole.clone(), pos), ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), ), ] } (Connection, Connection, Connection, Closed) => { vec![ ( wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), ), ( round_hole.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), ), ] } // fourway (Connection, Connection, Connection, Connection) => { vec![ ( corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians())), ), ( corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), ), ( corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), ), ( corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), ), ] } // corners (Connection, Connection, Closed, Closed) => { vec![ ( corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), ), ( corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), ), ] } (Closed, Connection, Connection, Closed) => { vec![ ( corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians())), ), ( corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians())), ), ] } (Closed, Closed, Connection, Connection) => { vec![ ( corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), ), ( corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), ), ] } (Connection, Closed, Closed, Connection) => { vec![ ( corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), ), ( corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), ), ] } _ => vec![], } }; for ((scene_root, colliders), pos) in values { commands .spawn((RigidBody::Fixed, scene_root, pos)) .with_children(|parent| { for (collider, transform) in colliders { parent.spawn((collider, transform)); } }); } } } commands.insert_resource(GameLevels { levels }); } fn create_levels(n: i32) -> Vec { let mut maps = Vec::new(); let mut initial_node = LevelNode::new(); initial_node.east = Side::Closed; initial_node.south = Side::Closed; initial_node.west = Side::Closed; initial_node.north = Side::Connection; let pos = (0, 0); let map = GameLevel::new(pos, initial_node.clone(), 5); maps.push(map); for _ in 0..n - 1 { let map = maps.last().unwrap(); let mut pos = map.end_node.clone(); let mut next_node = LevelNode::new(); next_node.east = Side::Connection; next_node.south = Side::Connection; next_node.west = Side::Connection; next_node.north = Side::Connection; let node = maps.last().unwrap().nodes.get(&maps.last().unwrap().end_node).unwrap(); let map = GameLevel::new(pos, node.clone(), 5); maps.push(map); } maps } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Side { Empty, Connection, Closed, } #[derive(Clone, Debug)] struct LevelNode { north: Side, south: Side, east: Side, west: Side, } impl LevelNode { fn new() -> Self { LevelNode { north: Side::Empty, south: Side::Empty, east: Side::Empty, west: Side::Empty, } } } #[derive(Resource)] struct GameLevels { pub levels: Vec, } struct GameLevel { nodes: HashMap<(i32, i32), LevelNode>, end_node: (i32, i32), grid_size: i32, initial_point: (i32, i32), } impl GameLevel { fn new(initial_point: (i32, i32), node: LevelNode, grid_size: i32) -> Self { let mut nodes = HashMap::new(); nodes.insert(initial_point, node); let mut m = GameLevel { initial_point, nodes, grid_size, end_node: initial_point, }; m.generate_map(); m } fn generate_map(&mut self) { let mut first_point = self.initial_point.clone(); first_point.1 += 1; self.create_node(first_point); // North } fn pos_within_boundaries(&self, pos: (i32, i32)) -> bool { pos.0 > self.grid_size + self.initial_point.0 || pos.1 > self.grid_size + self.initial_point.1 || pos.0 < self.initial_point.0 || pos.1 < self.initial_point.1 } fn choose_side(&self, pos: (i32, i32)) -> Side { if self.pos_within_boundaries(pos) { Side::Closed } else { if rand::rng().random_bool(0.5) { Side::Connection } else { Side::Closed } } } fn ensure_connection(&self, node: &mut LevelNode, (x, y): (i32, i32)) { let am_connections = (node.north == Side::Connection) as u8 + (node.south == Side::Connection) as u8 + (node.east == Side::Connection) as u8 + (node.west == Side::Connection) as u8; if am_connections <= 2 { if node.north != Side::Connection && !self.nodes.contains_key(&(x, y + 1)) { node.north = Side::Connection } else if node.south != Side::Connection && !self.nodes.contains_key(&(x, y - 1)) { node.south = Side::Connection } else if node.east != Side::Connection && !self.nodes.contains_key(&(x + 1, y)) { node.east = Side::Connection } else if node.west != Side::Connection && !self.nodes.contains_key(&(x - 1, y)) { node.west = Side::Connection } } } fn create_node(&mut self, idx: (i32, i32)) { let mut nodes_to_process = vec![idx]; while let Some(current_idx) = nodes_to_process.pop() { if self.nodes.contains_key(¤t_idx) { continue; } let (x, y) = current_idx; let mut new_node = LevelNode::new(); if x >= self.end_node.0 && y >= self.end_node.1 { self.end_node = (x, y); } let north = self.nodes.get(&(x, y + 1)); let south = self.nodes.get(&(x, y - 1)); let east = self.nodes.get(&(x + 1, y)); let west = self.nodes.get(&(x - 1, y)); new_node.north = north.map_or_else(|| self.choose_side(current_idx), |n| n.south); new_node.south = south.map_or_else(|| self.choose_side(current_idx), |s| s.north); new_node.east = east.map_or_else(|| self.choose_side(current_idx), |e| e.west); new_node.west = west.map_or_else(|| self.choose_side(current_idx), |w| w.east); if !self.pos_within_boundaries(current_idx) { self.ensure_connection(&mut new_node, current_idx); } if new_node.north == Side::Connection && !self.nodes.contains_key(&(x, y + 1)) { nodes_to_process.push((x, y + 1)); } if new_node.south == Side::Connection && !self.nodes.contains_key(&(x, y - 1)) { nodes_to_process.push((x, y - 1)); } if new_node.east == Side::Connection && !self.nodes.contains_key(&(x + 1, y)) { nodes_to_process.push((x + 1, y)); } if new_node.west == Side::Connection && !self.nodes.contains_key(&(x - 1, y)) { nodes_to_process.push((x - 1, y)); } self.nodes.insert(current_idx, new_node); } let end_node = self.nodes.get_mut(&self.end_node).unwrap(); if self.end_node.1 >= self.end_node.0 { end_node.north = Side::Connection; } else { end_node.east = Side::Connection; } } fn print_map(&self) { let mut min_x = 0; let mut max_x = 0; let mut min_y = 0; let mut max_y = 0; for &(x, y) in self.nodes.keys() { min_x = min_x.min(x); max_x = max_x.max(x); min_y = min_y.min(y); max_y = max_y.max(y); } for y in (min_y..=max_y).rev() { for x in min_x..=max_x { match self.nodes.get(&(x, y)) { Some(node) => print!("[+]"), None => print!(" "), } } println!(); } } } fn spawn_objects( mut commands: Commands, levels: Res, models: Res>, gltf_assets: Res, image_assets: Res, ) { // id card let card = models.get(&gltf_assets.card).unwrap(); let asset = card.default_scene.as_ref().unwrap(); for (i, level) in levels.levels.iter().enumerate() { let begin_node = level.initial_point; let end_node = level.end_node; // take a random position that is not the beginning or end_node let (x, z) = loop { let positions = level.nodes.keys(); let random_pos = positions.choose(&mut rand::rng()).unwrap().clone(); if random_pos != begin_node && random_pos != end_node { break random_pos; } }; let transform = if x <= z { Transform::from_xyz(2.0 * x as f32, 0.5, -2.0 * z as f32) .with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())) } else { Transform::from_xyz(2.0 * x as f32, 0.5, -2.0 * z as f32) }; // Correct Key Card commands .spawn(( transform, Interact, PickUpAble, RigidBody::Dynamic, KeyCardId(i), Name::new(format!("Id Card {i}")), Visibility::Visible, ItemIcon::new(image_assets.id_card.clone()), SceneRoot(asset.clone()), )) .with_children(|parent| { parent.spawn(( ColliderMassProperties::Mass(10.0), Collider::cuboid(0.05, 0.05, 0.01), Transform::from_rotation(Quat::from_euler( EulerRot::XYZ, -10.0f32.to_radians(), // X-axis rotation (tilt) -5.0f32.to_radians(), // Y-axis rotation 10.0f32.to_radians(), // Z-axis rotation )), )); parent.spawn(( ActiveEvents::COLLISION_EVENTS, Transform::default(), Collider::ball(0.5), // Interaction radius Sensor, )); }); // wrong key card let (x, z) = loop { let positions = level.nodes.keys(); let random_pos = positions.choose(&mut rand::rng()).unwrap().clone(); if random_pos != begin_node && random_pos != end_node { break random_pos; } }; let transform = if x <= z { Transform::from_xyz(2.0 * x as f32, 0.5, -2.0 * z as f32) .with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())) } else { Transform::from_xyz(2.0 * x as f32, 0.5, -2.0 * z as f32) }; commands .spawn(( transform, Interact, PickUpAble, RigidBody::Dynamic, KeyCardId(usize::MAX), Name::new(format!("Id Card {i}")), Visibility::Visible, ItemIcon::new(image_assets.id_card.clone()), SceneRoot(asset.clone()), )) .with_children(|parent| { parent.spawn(( ColliderMassProperties::Mass(10.0), Collider::cuboid(0.05, 0.05, 0.01), Transform::from_rotation(Quat::from_euler( EulerRot::XYZ, -10.0f32.to_radians(), // X-axis rotation (tilt) -5.0f32.to_radians(), // Y-axis rotation 10.0f32.to_radians(), // Z-axis rotation )), )); parent.spawn(( ActiveEvents::COLLISION_EVENTS, Transform::default(), Collider::ball(0.5), // Interaction radius Sensor, )); }); } } #[derive(Component)] pub struct Door { pub is_open: bool, pub open_direction: (i32, i32), pub position: (i32, i32), } fn spawn_doors( mut commands: Commands, levels: Res, models: Res>, gltf_assets: Res, ) { let collider: Vec<(Collider, Transform)> = vec![( Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 1.0, -1.0), )]; let path = format!("meshes/library/space_round_door.glb",); let handle = gltf_assets.library.get(&path).unwrap(); let gltf = models.get(handle).unwrap(); let asset = gltf.default_scene.as_ref().unwrap(); let scene_root = SceneRoot(asset.clone()); for (i, level) in levels.levels.iter().enumerate() { let (x, z) = level.end_node; let (direction, transform) = if x >= z { ( (0, 1), Transform::from_xyz(2.0 * x as f32, 0.0, -2.0 * z as f32) .with_rotation(Quat::from_rotation_y(-90.0_f32.to_radians())), ) } else { ( (1, 0), Transform::from_xyz(2.0 * x as f32, 0.0, -2.0 * z as f32), ) }; commands .spawn(( // very scuff but need this OpenedByKey(KeyCardId(i)), Interact, RigidBody::Fixed, Name::new("Door"), Door { is_open: false, open_direction: direction, position: (x, z), }, transform, scene_root.clone(), )) .with_children(|parent| { for (collider, transform) in collider.clone() { parent.spawn((collider, transform)); } // make it "interactable" in the world parent.spawn(( ActiveEvents::COLLISION_EVENTS, Transform::default(), Collider::ball(0.5), // Interaction radius Sensor, )); }); } } fn handle_door_pos(mut query: Query<(&Door, &mut Transform)>) { for (door, mut transform) in query.iter_mut() { if door.is_open { transform.translation.y = 2.0 } else { transform.translation.y = 0.0; } } }