use std::marker::PhantomData; use bevy::prelude::*; use bevy_rapier2d::{ na::{Isometry2, Vector2}, prelude::*, }; pub use here_be_dragons::Map as MapgenMap; use here_be_dragons::{geometry::Rect as MRect, MapFilter, Tile}; use maze_generator::{prelude::*, recursive_backtracking::RbGenerator}; use rand::prelude::StdRng; use crate::{ core::{Area, PointLike}, exploration::Mappable, visibility::Visible, }; #[derive(Component, Clone, Default, Deref, DerefMut)] pub struct Map(pub MapgenMap); impl From> for Map { fn from(v: MapgenMap) -> Self { Self(v) } } #[derive(Component, Clone, Debug, Default, Reflect)] #[reflect(Component)] pub struct MapObstruction; #[derive(Component, Clone, Debug, Default, Reflect)] #[reflect(Component)] pub struct Portal; #[derive(Component, Clone, Debug, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct SpawnColliders(pub bool); impl Default for SpawnColliders { fn default() -> Self { Self(true) } } impl From for SpawnColliders { fn from(v: bool) -> Self { Self(v) } } #[derive(Component, Clone, Debug, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct SpawnPortals(pub bool); impl Default for SpawnPortals { fn default() -> Self { Self(true) } } #[derive(Default, Component, Clone, Debug, Reflect)] #[reflect(Component)] pub struct Zone; pub trait ITileType { fn blocks_motion(&self) -> bool; fn blocks_visibility(&self) -> bool; } impl ITileType for Tile { fn blocks_motion(&self) -> bool { self.is_blocked() } fn blocks_visibility(&self) -> bool { self.is_blocked() } } #[derive(Clone, Debug)] pub struct MapConfig { pub start_revealed: bool, } impl Default for MapConfig { fn default() -> Self { Self { start_revealed: false, } } } #[derive(Bundle, Default)] pub struct PortalBundle { pub portal: Portal, pub mappable: Mappable, pub transform: Transform, pub global_transform: GlobalTransform, } #[derive(Bundle, Clone, Default)] pub struct MapBundle { pub map: Map, pub spawn_colliders: SpawnColliders, pub spawn_portals: SpawnPortals, pub transform: Transform, pub global_transform: GlobalTransform, } pub struct GridBuilder { width_in_rooms: usize, height_in_rooms: usize, room_width: usize, room_height: usize, } impl GridBuilder { pub fn new( width_in_rooms: usize, height_in_rooms: usize, room_width: usize, room_height: usize, ) -> Box { Box::new(GridBuilder { width_in_rooms, height_in_rooms, room_width, room_height, }) } } impl MapFilter for GridBuilder { fn modify_map(&self, _rng: &mut StdRng, map: &MapgenMap) -> MapgenMap { let mut map = map.clone(); let mut generator = RbGenerator::new(None); if let Ok(maze) = generator.generate(self.width_in_rooms as i32, self.height_in_rooms as i32) { let total_height = (self.room_height + 1) * self.height_in_rooms + 1; let half_width = self.room_width / 2; let half_height = self.room_height / 2; for y in 0..self.height_in_rooms { for x in 0..self.width_in_rooms { let x_offset = x * (self.room_width + 1); let y_offset = total_height - (y * (self.room_height + 1)) - self.room_height - 2; let room = MRect::new_i32( x_offset as i32 + 1, y_offset as i32 + 1, self.room_width as i32, self.room_height as i32, ); map.add_room(room); let coords = maze_generator::prelude::Coordinates::new(x as i32, y as i32); if let Some(field) = maze.get_field(&coords) { use maze_generator::prelude::Direction::*; if field.has_passage(&North) { let x = x_offset + half_width; let y = y_offset + self.room_height; map.set_tile(x as usize, y as usize, Tile::floor()); } if field.has_passage(&South) { let x = x_offset + half_width; let y = y_offset; map.set_tile(x as usize, y as usize, Tile::floor()); } if field.has_passage(&East) { let x = x_offset + self.room_width; let y = y_offset + half_height; map.set_tile(x as usize, y as usize, Tile::floor()); } if field.has_passage(&West) { let x = x_offset; let y = y_offset + half_height; map.set_tile(x as usize, y as usize, Tile::floor()); } } } } map.starting_point = Some(here_be_dragons::geometry::Point::new( maze.start.x as usize + 1, maze.start.y as usize + 1, )); map.exit_point = Some(here_be_dragons::geometry::Point::new( (maze.goal.x as usize) * self.room_width + half_width, (maze.goal.y as usize) * self.room_height + half_height, )); } map } } fn spawn_colliders( mut commands: Commands, maps: Query<(Entity, &Map, &SpawnColliders), Changed>, ) { for (map_entity, map, spawn_colliders) in maps.iter() { if **spawn_colliders { commands .entity(map_entity) .remove::() .insert(RigidBody::Fixed); for y in 0..map.height { for x in 0..map.width { let tile = map.at(x, y); if tile.blocks_motion() { let id = commands .spawn() .insert(Collider::cuboid(0.5, 0.5)) .insert(Transform::from_xyz(x as f32 + 0.5, y as f32 + 0.5, 0.)) .insert(GlobalTransform::default()) .insert(MapObstruction) .id(); if tile.blocks_visibility() { commands.entity(id).insert(Visible::opaque()); } commands.entity(map_entity).push_children(&[id]); } } } for room in &map.rooms { let shape = Collider::cuboid((room.width() / 2) as f32, (room.height() / 2) as f32); let position = Isometry2::new(Vector2::new(room.center().x(), room.center().y()), 0.); let aabb = shape.raw.compute_aabb(&position); let id = commands .spawn() .insert(shape) .insert(Sensor) .insert(ActiveEvents::COLLISION_EVENTS) .insert_bundle(TransformBundle::from_transform(Transform::from_xyz( position.translation.x, position.translation.y, 0., ))) .insert(Area(aabb)) .insert(Zone) .id(); commands.entity(map_entity).push_children(&[id]); } } } } fn spawn_portals( mut commands: Commands, map: Query<(Entity, &Map, &SpawnPortals), Changed>, ) { for (map_entity, map, spawn_portals) in map.iter() { if **spawn_portals { commands.entity(map_entity).remove::(); let mut portals: Vec<(f32, f32)> = vec![]; for x in 1..map.width { for y in 1..map.height { let mut spawn_portal = false; if map.get_available_exits(x, y).len() > 2 { let idx = (x, y).to_index(map.width); if map.tiles[idx].is_walkable() && (x > 1 && map.tiles[idx - 1].is_walkable()) && (x < map.width - 2 && map.tiles[idx + 1].is_walkable()) && (y > 1 && map.tiles[idx - map.width].is_blocked()) && (y < map.height - 2 && map.tiles[idx + map.width].is_blocked()) { spawn_portal = true; } if map.tiles[idx].is_walkable() && (x > 1 && map.tiles[idx - 1].is_blocked()) && (x < map.width - 2 && map.tiles[idx + 1].is_blocked()) && (y > 1 && map.tiles[idx - map.width].is_walkable()) && (y < map.height - 2 && map.tiles[idx + map.width].is_walkable()) { spawn_portal = true; } } if spawn_portal { let x = x as f32 + 0.5; let y = y as f32 + 0.5; if !portals.contains(&(x, y)) { portals.push((x, y)); } } } } for (x, y) in portals { let portal = commands .spawn_bundle(PortalBundle { transform: Transform::from_translation(Vec3::new(x, y, 0.)), ..default() }) .id(); commands.entity(map_entity).push_children(&[portal]); } } } } fn spawn_portal_colliders( mut commands: Commands, map: Query<(Entity, &SpawnColliders), With>>, portals: Query, Without)>, ) { for (entity, spawn_colliders) in map.iter() { if **spawn_colliders { for portal_entity in portals.iter() { commands .entity(portal_entity) .insert(Collider::cuboid(0.5, 0.5)) .insert(Sensor); } commands.entity(entity).remove::(); } } } pub struct MapPlugin(PhantomData); impl Default for MapPlugin { fn default() -> Self { Self(Default::default()) } } impl Plugin for MapPlugin { fn build(&self, app: &mut App) { if !app.world.contains_resource::() { app.insert_resource(MapConfig::default()); } app.register_type::() .add_system_to_stage(CoreStage::PreUpdate, spawn_colliders::) .add_system_to_stage(CoreStage::PreUpdate, spawn_portals::) .add_system_to_stage(CoreStage::PreUpdate, spawn_portal_colliders::); } }