2022-03-25 06:00:30 +00:00
|
|
|
use std::marker::PhantomData;
|
2021-05-13 17:25:45 +00:00
|
|
|
|
2024-10-06 16:10:46 +00:00
|
|
|
use avian2d::prelude::*;
|
2021-05-13 17:25:45 +00:00
|
|
|
use bevy::prelude::*;
|
2022-03-18 20:58:27 +00:00
|
|
|
pub use here_be_dragons::Map as MapgenMap;
|
|
|
|
use here_be_dragons::{geometry::Rect as MRect, MapFilter, Tile};
|
2021-05-13 17:25:45 +00:00
|
|
|
use maze_generator::{prelude::*, recursive_backtracking::RbGenerator};
|
|
|
|
use rand::prelude::StdRng;
|
|
|
|
|
2024-10-06 16:10:46 +00:00
|
|
|
use crate::{core::PointLike, exploration::Mappable, visibility::Visible};
|
2021-05-13 17:25:45 +00:00
|
|
|
|
2022-01-11 05:05:51 +00:00
|
|
|
#[derive(Component, Clone, Default, Deref, DerefMut)]
|
2022-03-15 15:37:28 +00:00
|
|
|
pub struct Map<D: 'static + Clone + Default + Send + Sync>(pub MapgenMap<D>);
|
2022-01-10 19:50:52 +00:00
|
|
|
|
2022-03-15 15:37:28 +00:00
|
|
|
impl<D: Clone + Default + Send + Sync> From<MapgenMap<D>> for Map<D> {
|
|
|
|
fn from(v: MapgenMap<D>) -> Self {
|
2022-02-12 14:50:54 +00:00
|
|
|
Self(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-10 19:50:52 +00:00
|
|
|
#[derive(Component, Clone, Debug, Default, Reflect)]
|
2021-06-28 15:04:53 +00:00
|
|
|
#[reflect(Component)]
|
|
|
|
pub struct MapObstruction;
|
|
|
|
|
2022-01-10 19:50:52 +00:00
|
|
|
#[derive(Component, Clone, Debug, Default, Reflect)]
|
2021-05-13 17:25:45 +00:00
|
|
|
#[reflect(Component)]
|
2021-06-07 17:43:20 +00:00
|
|
|
pub struct Portal;
|
2021-05-13 17:25:45 +00:00
|
|
|
|
2022-01-10 19:50:52 +00:00
|
|
|
#[derive(Component, Clone, Debug, Deref, DerefMut, Reflect)]
|
2021-07-14 17:52:03 +00:00
|
|
|
#[reflect(Component)]
|
|
|
|
pub struct SpawnColliders(pub bool);
|
|
|
|
|
|
|
|
impl Default for SpawnColliders {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-12 14:47:59 +00:00
|
|
|
impl From<bool> for SpawnColliders {
|
|
|
|
fn from(v: bool) -> Self {
|
|
|
|
Self(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-10 19:50:52 +00:00
|
|
|
#[derive(Component, Clone, Debug, Deref, DerefMut, Reflect)]
|
2021-07-14 17:52:03 +00:00
|
|
|
#[reflect(Component)]
|
|
|
|
pub struct SpawnPortals(pub bool);
|
|
|
|
|
|
|
|
impl Default for SpawnPortals {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-18 16:52:48 +00:00
|
|
|
#[derive(Default, Component, Clone, Debug, Reflect)]
|
|
|
|
#[reflect(Component)]
|
|
|
|
pub struct Zone;
|
|
|
|
|
2021-05-13 17:25:45 +00:00
|
|
|
pub trait ITileType {
|
|
|
|
fn blocks_motion(&self) -> bool;
|
|
|
|
fn blocks_visibility(&self) -> bool;
|
|
|
|
}
|
|
|
|
|
2021-07-13 17:22:50 +00:00
|
|
|
impl ITileType for Tile {
|
2021-05-13 17:25:45 +00:00
|
|
|
fn blocks_motion(&self) -> bool {
|
2021-07-13 17:22:50 +00:00
|
|
|
self.is_blocked()
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn blocks_visibility(&self) -> bool {
|
2021-07-13 17:22:50 +00:00
|
|
|
self.is_blocked()
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-12 22:28:36 +00:00
|
|
|
#[derive(Bundle, Default)]
|
2021-05-20 19:54:13 +00:00
|
|
|
pub struct PortalBundle {
|
2024-10-06 16:10:46 +00:00
|
|
|
pub transform: TransformBundle,
|
2021-05-20 19:54:13 +00:00
|
|
|
pub portal: Portal,
|
2021-05-13 17:25:45 +00:00
|
|
|
pub mappable: Mappable,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Bundle, Clone, Default)]
|
2022-03-15 15:37:28 +00:00
|
|
|
pub struct MapBundle<D: 'static + Clone + Default + Send + Sync> {
|
|
|
|
pub map: Map<D>,
|
2021-07-14 17:52:03 +00:00
|
|
|
pub spawn_colliders: SpawnColliders,
|
|
|
|
pub spawn_portals: SpawnPortals,
|
2024-08-17 15:51:46 +00:00
|
|
|
pub transform: TransformBundle,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Bundle, Clone, Debug)]
|
|
|
|
pub struct TileBundle {
|
|
|
|
pub transform: TransformBundle,
|
|
|
|
pub collider: Collider,
|
|
|
|
pub rigid_body: RigidBody,
|
|
|
|
pub map_obstruction: MapObstruction,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for TileBundle {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
transform: default(),
|
2024-12-04 22:51:25 +00:00
|
|
|
collider: Collider::rectangle(1., 1.),
|
2024-10-06 16:10:46 +00:00
|
|
|
rigid_body: RigidBody::Static,
|
|
|
|
map_obstruction: default(),
|
2024-08-17 15:51:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TileBundle {
|
|
|
|
pub fn new(x: i32, y: i32) -> Self {
|
|
|
|
Self {
|
|
|
|
transform: Transform::from_xyz(x as f32 + 0.5, y as f32 + 0.5, 0.).into(),
|
|
|
|
..default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Bundle, Clone, Debug)]
|
|
|
|
pub struct ZoneBundle {
|
|
|
|
pub collider: Collider,
|
|
|
|
pub transform: TransformBundle,
|
|
|
|
pub zone: Zone,
|
|
|
|
pub sensor: Sensor,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ZoneBundle {
|
2024-10-06 16:10:46 +00:00
|
|
|
fn new(collider: Collider, position: Vec2) -> Self {
|
2024-08-17 15:51:46 +00:00
|
|
|
Self {
|
|
|
|
collider,
|
2024-10-06 16:10:46 +00:00
|
|
|
transform: Transform::from_translation(position.extend(0.)).into(),
|
2024-08-17 15:51:46 +00:00
|
|
|
zone: default(),
|
|
|
|
sensor: default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&MRect> for ZoneBundle {
|
|
|
|
fn from(rect: &MRect) -> Self {
|
2024-10-06 16:10:46 +00:00
|
|
|
let collider = Collider::rectangle(
|
2024-12-04 22:51:25 +00:00
|
|
|
rect.width() as f32 + 1.,
|
|
|
|
rect.height() as f32 + 0.1,
|
2024-08-17 15:51:46 +00:00
|
|
|
);
|
2024-10-06 16:10:46 +00:00
|
|
|
let position = Vec2::new(rect.center().x(), rect.center().y());
|
2024-08-17 15:51:46 +00:00
|
|
|
Self::new(collider, position)
|
|
|
|
}
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct GridBuilder {
|
2021-08-26 21:00:56 +00:00
|
|
|
width_in_rooms: usize,
|
|
|
|
height_in_rooms: usize,
|
|
|
|
room_width: usize,
|
|
|
|
room_height: usize,
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl GridBuilder {
|
|
|
|
pub fn new(
|
2021-08-26 21:00:56 +00:00
|
|
|
width_in_rooms: usize,
|
|
|
|
height_in_rooms: usize,
|
|
|
|
room_width: usize,
|
|
|
|
room_height: usize,
|
2021-05-13 17:25:45 +00:00
|
|
|
) -> Box<GridBuilder> {
|
|
|
|
Box::new(GridBuilder {
|
|
|
|
width_in_rooms,
|
|
|
|
height_in_rooms,
|
|
|
|
room_width,
|
|
|
|
room_height,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-15 15:37:28 +00:00
|
|
|
impl<D: Clone + Default> MapFilter<D> for GridBuilder {
|
|
|
|
fn modify_map(&self, _rng: &mut StdRng, map: &MapgenMap<D>) -> MapgenMap<D> {
|
2021-05-13 17:25:45 +00:00
|
|
|
let mut map = map.clone();
|
|
|
|
let mut generator = RbGenerator::new(None);
|
2022-03-21 14:14:44 +00:00
|
|
|
if let Ok(maze) =
|
|
|
|
generator.generate(self.width_in_rooms as i32, self.height_in_rooms as i32)
|
|
|
|
{
|
2024-08-17 15:51:46 +00:00
|
|
|
let total_height = self.room_height * self.height_in_rooms + self.height_in_rooms + 1;
|
2022-03-21 14:14:44 +00:00
|
|
|
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);
|
2024-08-17 15:51:46 +00:00
|
|
|
let y_offset = total_height - (y + 1) * (self.room_height + 1) - 1;
|
2022-03-21 14:14:44 +00:00
|
|
|
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;
|
2022-12-19 20:08:31 +00:00
|
|
|
map.set_tile(x, y, Tile::floor());
|
2022-03-21 14:14:44 +00:00
|
|
|
}
|
|
|
|
if field.has_passage(&South) {
|
|
|
|
let x = x_offset + half_width;
|
|
|
|
let y = y_offset;
|
2022-12-19 20:08:31 +00:00
|
|
|
map.set_tile(x, y, Tile::floor());
|
2022-03-21 14:14:44 +00:00
|
|
|
}
|
|
|
|
if field.has_passage(&East) {
|
|
|
|
let x = x_offset + self.room_width;
|
|
|
|
let y = y_offset + half_height;
|
2022-12-19 20:08:31 +00:00
|
|
|
map.set_tile(x, y, Tile::floor());
|
2022-03-21 14:14:44 +00:00
|
|
|
}
|
|
|
|
if field.has_passage(&West) {
|
|
|
|
let x = x_offset;
|
|
|
|
let y = y_offset + half_height;
|
2022-12-19 20:08:31 +00:00
|
|
|
map.set_tile(x, y, Tile::floor());
|
2022-03-21 14:14:44 +00:00
|
|
|
}
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-21 14:14:44 +00:00
|
|
|
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,
|
|
|
|
));
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
|
|
|
map
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-15 15:37:28 +00:00
|
|
|
fn spawn_colliders<D: 'static + Clone + Default + Send + Sync>(
|
2021-07-14 17:52:03 +00:00
|
|
|
mut commands: Commands,
|
2022-06-15 15:28:21 +00:00
|
|
|
maps: Query<(Entity, &Map<D>, &SpawnColliders), Changed<SpawnColliders>>,
|
2021-07-14 17:52:03 +00:00
|
|
|
) {
|
2023-03-28 17:13:23 +00:00
|
|
|
for (map_entity, map, spawn_colliders) in &maps {
|
2021-07-14 17:52:03 +00:00
|
|
|
if **spawn_colliders {
|
2024-08-11 14:08:20 +00:00
|
|
|
commands.entity(map_entity).remove::<SpawnColliders>();
|
2022-06-15 15:28:21 +00:00
|
|
|
for y in 0..map.height {
|
|
|
|
for x in 0..map.width {
|
2022-08-27 14:11:37 +00:00
|
|
|
if let Some(tile) = map.at(x, y) {
|
|
|
|
if tile.blocks_motion() {
|
2024-08-17 15:51:46 +00:00
|
|
|
let id = commands.spawn(TileBundle::new(x as i32, y as i32)).id();
|
2022-08-27 14:11:37 +00:00
|
|
|
if tile.blocks_visibility() {
|
|
|
|
commands.entity(id).insert(Visible::opaque());
|
|
|
|
}
|
|
|
|
commands.entity(map_entity).push_children(&[id]);
|
2021-07-14 17:52:03 +00:00
|
|
|
}
|
2021-06-04 12:58:38 +00:00
|
|
|
}
|
2021-06-04 12:39:38 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-14 17:52:03 +00:00
|
|
|
for room in &map.rooms {
|
2024-03-16 17:13:12 +00:00
|
|
|
commands.entity(map_entity).with_children(|parent| {
|
2024-08-17 15:51:46 +00:00
|
|
|
parent.spawn(ZoneBundle::from(room));
|
2024-03-16 17:13:12 +00:00
|
|
|
});
|
2021-07-14 17:52:03 +00:00
|
|
|
}
|
2021-06-29 15:31:18 +00:00
|
|
|
}
|
2021-06-04 12:39:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-15 15:37:28 +00:00
|
|
|
fn spawn_portals<D: 'static + Clone + Default + Send + Sync>(
|
2021-05-13 17:25:45 +00:00
|
|
|
mut commands: Commands,
|
2022-03-15 15:37:28 +00:00
|
|
|
map: Query<(Entity, &Map<D>, &SpawnPortals), Changed<SpawnPortals>>,
|
2021-05-13 17:25:45 +00:00
|
|
|
) {
|
2023-03-28 17:13:23 +00:00
|
|
|
for (map_entity, map, spawn_portals) in &map {
|
2021-07-14 17:52:03 +00:00
|
|
|
if **spawn_portals {
|
2022-05-11 19:41:17 +00:00
|
|
|
commands.entity(map_entity).remove::<SpawnPortals>();
|
2021-05-20 19:54:13 +00:00
|
|
|
let mut portals: Vec<(f32, f32)> = vec![];
|
2024-08-17 15:51:46 +00:00
|
|
|
for x in 0..map.width {
|
|
|
|
for y in 0..map.height {
|
|
|
|
let idx = (x, y).to_index(map.width);
|
2021-05-20 19:54:13 +00:00
|
|
|
let mut spawn_portal = false;
|
2024-08-17 15:51:46 +00:00
|
|
|
if map.get_available_exits(x, y).len() > 2 && map.tiles[idx].is_walkable() {
|
|
|
|
if (x > 1 && map.tiles[idx - 1].is_walkable())
|
2021-07-13 17:22:50 +00:00
|
|
|
&& (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())
|
2021-05-13 17:25:45 +00:00
|
|
|
{
|
2021-05-20 19:54:13 +00:00
|
|
|
spawn_portal = true;
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
2024-08-17 15:51:46 +00:00
|
|
|
if (x > 1 && map.tiles[idx - 1].is_blocked())
|
2021-07-13 17:22:50 +00:00
|
|
|
&& (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())
|
2021-05-13 17:25:45 +00:00
|
|
|
{
|
2021-05-20 19:54:13 +00:00
|
|
|
spawn_portal = true;
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-20 19:54:13 +00:00
|
|
|
if spawn_portal {
|
2022-07-11 15:26:03 +00:00
|
|
|
let x = x as f32 + 0.5;
|
|
|
|
let y = y as f32 + 0.5;
|
2021-05-20 19:54:13 +00:00
|
|
|
if !portals.contains(&(x, y)) {
|
|
|
|
portals.push((x, y));
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-11 15:26:03 +00:00
|
|
|
for (x, y) in portals {
|
2024-03-16 17:13:12 +00:00
|
|
|
commands.entity(map_entity).with_children(|parent| {
|
|
|
|
parent.spawn(PortalBundle {
|
2024-08-17 15:51:46 +00:00
|
|
|
transform: Transform::from_translation(Vec3::new(x, y, 0.)).into(),
|
2022-05-10 18:56:49 +00:00
|
|
|
..default()
|
2024-03-16 17:13:12 +00:00
|
|
|
});
|
|
|
|
});
|
2021-07-14 17:52:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-15 15:37:28 +00:00
|
|
|
fn spawn_portal_colliders<D: 'static + Clone + Default + Send + Sync>(
|
2021-07-14 17:52:03 +00:00
|
|
|
mut commands: Commands,
|
2022-07-12 17:43:43 +00:00
|
|
|
map: Query<(Entity, &SpawnColliders), With<Map<D>>>,
|
2022-06-15 15:28:21 +00:00
|
|
|
portals: Query<Entity, (With<Portal>, Without<Collider>)>,
|
2021-07-14 17:52:03 +00:00
|
|
|
) {
|
2023-03-28 17:13:23 +00:00
|
|
|
for (entity, spawn_colliders) in &map {
|
2021-07-14 17:52:03 +00:00
|
|
|
if **spawn_colliders {
|
2023-03-28 17:13:23 +00:00
|
|
|
for portal_entity in &portals {
|
2021-07-14 17:52:03 +00:00
|
|
|
commands
|
|
|
|
.entity(portal_entity)
|
2024-12-04 22:51:25 +00:00
|
|
|
.insert((Collider::rectangle(1., 1.), Sensor));
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
2022-07-12 17:43:43 +00:00
|
|
|
commands.entity(entity).remove::<SpawnColliders>();
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-20 15:15:09 +00:00
|
|
|
#[derive(Resource, Clone, Copy, Debug)]
|
|
|
|
pub struct MapPlugin<MapData: 'static + Clone + Default + Send + Sync> {
|
|
|
|
pub start_revealed: bool,
|
|
|
|
pub map_data: PhantomData<MapData>,
|
|
|
|
}
|
2022-03-15 15:37:28 +00:00
|
|
|
|
2022-12-20 15:15:09 +00:00
|
|
|
impl<MapData: 'static + Clone + Default + Send + Sync> Default for MapPlugin<MapData> {
|
2022-03-15 15:37:28 +00:00
|
|
|
fn default() -> Self {
|
2022-12-20 15:15:09 +00:00
|
|
|
Self {
|
|
|
|
start_revealed: default(),
|
|
|
|
map_data: default(),
|
|
|
|
}
|
2022-03-15 15:37:28 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-13 17:25:45 +00:00
|
|
|
|
2022-12-20 15:15:09 +00:00
|
|
|
impl<MapData: 'static + Clone + Default + Send + Sync> Plugin for MapPlugin<MapData> {
|
2022-01-10 19:50:52 +00:00
|
|
|
fn build(&self, app: &mut App) {
|
2022-12-20 15:15:09 +00:00
|
|
|
app.insert_resource(self.clone())
|
|
|
|
.register_type::<Portal>()
|
2023-03-28 16:57:37 +00:00
|
|
|
.add_systems(
|
2023-09-08 21:12:35 +00:00
|
|
|
PreUpdate,
|
2023-03-28 16:57:37 +00:00
|
|
|
(
|
|
|
|
spawn_colliders::<MapData>,
|
|
|
|
spawn_portals::<MapData>,
|
|
|
|
spawn_portal_colliders::<MapData>,
|
2023-09-08 21:12:35 +00:00
|
|
|
),
|
2023-03-28 16:57:37 +00:00
|
|
|
);
|
2021-05-13 17:25:45 +00:00
|
|
|
}
|
|
|
|
}
|