blackout/src/map.rs

317 lines
11 KiB
Rust
Raw Normal View History

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 pathfinding::prelude::*;
2021-05-13 17:25:45 +00:00
use rand::prelude::StdRng;
use crate::{
core::{Obstacle, PointLike, Zone},
exploration::Mappable,
pathfinding::CostMap,
visibility::Visible,
};
2021-05-13 17:25:45 +00:00
2022-01-11 05:05:51 +00:00
#[derive(Component, Clone, Default, Deref, DerefMut)]
2025-01-07 17:27:37 +00:00
#[require(Transform, SpawnColliders, SpawnPortals)]
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
impl<D: 'static + Clone + Default + Send + Sync> Map<D> {
pub fn find_path(
&self,
start: &UVec2,
destination: &UVec2,
cost_map: &Option<CostMap>,
) -> Option<(Vec<UVec2>, u32)> {
astar(
&start,
|p| {
let mut successors: Vec<(UVec2, u32)> = vec![];
if let Some(tile) = self.at(p.x as usize, p.y as usize) {
if tile.is_walkable() {
for tile in self.get_available_exits(p.x as usize, p.y as usize) {
let mut cost = tile.2 * 100.;
if let Some(cost_map) = cost_map {
if let Some(modifier) = cost_map.get(&p.as_ivec2()) {
cost *= modifier;
}
}
successors
.push((UVec2::new(tile.0 as u32, tile.1 as u32), cost as u32));
}
}
}
successors
},
|p| (p.distance_squared(destination) * 100.) as u32,
|p| *p == *destination,
)
}
}
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-05-13 17:25:45 +00:00
#[reflect(Component)]
2025-01-07 17:27:37 +00:00
#[require(Transform, Mappable)]
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)]
#[reflect(Component)]
pub struct SpawnColliders(pub bool);
impl Default for SpawnColliders {
fn default() -> Self {
Self(true)
}
}
2022-01-10 19:50:52 +00:00
#[derive(Component, Clone, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct SpawnPortals(pub bool);
impl Default for SpawnPortals {
fn default() -> Self {
Self(true)
}
}
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
}
}
pub struct GridBuilder {
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(
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)
{
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 {
2025-01-07 01:33:18 +00:00
// println!("({x}, {y}): ");
2022-03-21 14:14:44 +00:00
let x_offset = x * (self.room_width + 1);
let y_offset = total_height - (y + 1) * (self.room_height + 1) - 1;
2025-01-07 01:33:18 +00:00
// println!("Offsets: {x_offset}, {y_offset}");
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,
);
2025-01-07 01:33:18 +00:00
// println!("{room:?}");
2022-03-21 14:14:44 +00:00
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;
2025-01-07 01:33:18 +00:00
let y = y_offset + self.room_height + 1;
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) {
2025-01-07 01:33:18 +00:00
let x = x_offset + self.room_width + 1;
2022-03-21 14:14:44 +00:00
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>(
mut commands: Commands,
maps: Query<(Entity, &Map<D>, &SpawnColliders), Changed<SpawnColliders>>,
) {
2023-03-28 17:13:23 +00:00
for (map_entity, map, spawn_colliders) in &maps {
if **spawn_colliders {
commands.entity(map_entity).remove::<SpawnColliders>();
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() {
2025-01-07 17:27:37 +00:00
let id = commands
.spawn((
Obstacle,
Transform::from_xyz(x as f32 + 0.5, y as f32 + 0.5, 0.),
))
.id();
2022-08-27 14:11:37 +00:00
if tile.blocks_visibility() {
commands.entity(id).insert(Visible::opaque());
}
2025-01-03 19:19:57 +00:00
commands.entity(map_entity).add_children(&[id]);
}
}
}
}
for room in &map.rooms {
commands.entity(map_entity).with_children(|parent| {
2025-01-07 17:27:37 +00:00
parent.spawn((
Zone,
Transform::from_xyz(room.center().x as f32, room.center().y as f32, 0.),
Collider::rectangle(room.width() as f32 + 1., room.height() as f32 + 1.),
));
});
}
}
}
}
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 {
if **spawn_portals {
commands.entity(map_entity).remove::<SpawnPortals>();
2021-05-20 19:54:13 +00:00
let mut portals: Vec<(f32, f32)> = vec![];
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;
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
}
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 {
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
}
}
}
}
for (x, y) in portals {
commands.entity(map_entity).with_children(|parent| {
2025-01-07 17:27:37 +00:00
parent.spawn((Portal, Transform::from_xyz(x, y, 0.)));
});
}
}
}
}
2022-03-15 15:37:28 +00:00
fn spawn_portal_colliders<D: 'static + Clone + Default + Send + Sync>(
mut commands: Commands,
2022-07-12 17:43:43 +00:00
map: Query<(Entity, &SpawnColliders), With<Map<D>>>,
portals: Query<Entity, (With<Portal>, Without<Collider>)>,
) {
2023-03-28 17:13:23 +00:00
for (entity, spawn_colliders) in &map {
if **spawn_colliders {
2023-03-28 17:13:23 +00:00
for portal_entity in &portals {
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
}
}
}
#[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
impl<MapData: 'static + Clone + Default + Send + Sync> Default for MapPlugin<MapData> {
2022-03-15 15:37:28 +00:00
fn default() -> Self {
Self {
start_revealed: default(),
map_data: default(),
}
2022-03-15 15:37:28 +00:00
}
}
2021-05-13 17:25:45 +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) {
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
}
}